C语言的高级技巧

c语言是一门古老的语言,可以看下下面的C语言的介绍:

1969-1973年在美国电话电报公司(AT&T)贝尔实验室开始了C语言的最初研发。根据C语言的发明者丹尼斯·里奇 (Dennis Ritchie) 说,C 语言最重要的研发时期是在1972年。
说明:丹尼斯·里奇(Dennis Ritchie),C语言之父,UNIX之父。1978年与布莱恩·科尔尼干(Brian Kernighan)一起出版了名著《C程序设计语言(The C Programming Language)》,现在此书已翻译成多种语言,成为C语言方面最权威的教材之一。2011年10月12日(北京时间为10月13日),丹尼斯·里奇去世,享年70岁。
C语言之所以命名为C,是因为C语言源自Ken Thompson发明的 B语言,而B语言则源自BCPL语言。
C语言的诞生是和UNIX操作系统的开发密不可分的,原先的UNIX操作系统都是用汇编语言写的,1973年UNIX操作系统的核心用C语言改写,从此以后,C语言成为编写操作系统的主要语言。

C语言既简单又复杂,说它简单是因为它的关键字少,语法规则简单,看看就可以编写个hello,world程序;说它复杂是因为它接近于底层,有指针,可以直接操作内存,由此引起的一堆麻烦事情调试起来有非常的复杂,而且没有丰富的库的支持,很多东西都要自己手写,比较麻烦。

C语言的另外复杂点在于,你也许对C的语法早就了然于胸,但是仍然对开源的库代码阅读起来非常吃力,除了算法和数据结构复杂之外,C语言还有自己的奇技淫巧,常在开源的代码中应用,但是却很少有书去总结,这个文章算是对C的常用技巧做一个总结吧,资料来源于网上和自己看代码的一些体会。

一 编译器判断优化技巧

#define likely(x)       __builtin_expect(!!(x),1)
#define unlikely(x)     __builtin_expect(!!(x),0)

likely这个宏的期望x是非0,是在绝大多数情况下,x都是非0,比如我们在内存申请后判断指针是否为空可以用,而unlikely正好相反,是绝大多数情况下,x为空时候使用。

char * p =(char*)malloc(sizeof(int));
if (likely(p)) {
  do_something();
}

引入这两个宏,可以增加条件判断的分支预测准确性,cpu会提前装载后面的指令。在汇编级别的表现是预测大多数可能发生的条件是顺序的指令,而少数可能发生的情况是跳转指令,顺序指令在执行的时候可以利用CPU的缓存优势。
极端情况下,性能可以提升30%左右。

二 定长类型

在Java这种语言中,byte是8个位一个字节,short是16位,2个字节,int是32位,4个字节这些都是确定的。C语言中,经常出现同一个类型在不同的平台的字节长度是不一样的,比如long在32位系统中为4个字节,在64位系统中为8个字节。这就给我们编写跨平台的系统产生了麻烦,
为了跨平台,很多系统定义了自己的一套类型。stdint.h头文件到了确定大小的类型,比如:

1字节     uint8_t  
2字节     uint16_t  
4字节     uint32_t  
8字节     uint64_t

编程中,我们应该多使用这些类型,少使用int,long等。

三 利用宏实现日志功能

日志功能很常用,但是我们如何获取打印日志的位置和行号那,我特意和同事讨论了下,在Java中这个功能是通过定义异常来实现的,那么在C中如何实现,废话不说,看下代码吧:

#define LOG_PRINTF(pres, fmt, ...)                                                  \
log_printf(pres "[%s:%s:%d]" fmt "\n", __FILE__, __func__, __LINE__, ##__VA_ARGS__)

#define log_info(logLevel,fmt,...)  do { \
        if (INFO_LEVEL >= logLevel) {\
                 LOG_PRINTF_S("[INFO]", fmt, ##__VA_ARGS__);\
            }\
        }while (0);

void log_printf(const char * fmt, ...) {
    va_list ap;
    // 每条日志大小, 按照系统缓冲区走
    char str[BUFSIZ];
    int len = times_fmt("["STR_TIMES"]", str, sizeof str);
    time_t now_time = time(NULL);
    //每到新的一天切换日志
    if (!time_day(now_time,last_time)) {
        log_init(g_app_conf.app_conf.common_conf_data.log_file_name);
    }
    // 日志内容填充
    va_start(ap, fmt);
    vsnprintf(str + len, sizeof str - len, fmt, ap);
    va_end(ap);

    // 数据写入到文件中
    fputs(str, log_file);
    if (is_write_to_console == 1) {
        fputs(str, stderr);
    }
}

说明:

  1. FILE表示正在运行程序文件名,func正在执行的函数,LINE是程序文件中的行号,这些是C的编译器内置的宏。
  2. 对于#宏解释下,#用在预编译语句里面可以把预编译函数的变量直接格式成字符串。
    比如:
#define my_printf(x) printf(#x" is %d\n", x)
int a = 100;
my_printf(a);
/*打印a is 100*/

可以用在switch语句中,将enum转成相关的字符串,非常方便:

#define CASE_CODE(E)  case E: return #E
const char * PacketProfileDetectIdToString(PacketProfileDetectId id)
{
    switch (id) {
        CASE_CODE (PROF_DETECT_SETUP);
        CASE_CODE (PROF_DETECT_GETSGH);
        CASE_CODE (PROF_DETECT_IPONLY);
        default:
            return "UNKNOWN";
    }
}
  1. 对于##宏解释:
/* ## 是变量连接符,将两个字符连接成一个变量 */
#define FUN(a) printf("The square of " #a " is %d.\n",b##a)  
int bm = 2
FUN(m)

/* 打印 The square of m is 2.*/
  1. VA_ARGS是可变参数宏,表示可变参数列表。
 #define Debug(...) printf(__VA_ARGS__)

 Debug("Y = %d\n", y);
/*自动替换成:
printf("Y = %d\n", y);
*/
  1. 对于 ##__VA_ARGS__宏 作用是如果最后的可变变量为空忽略后面的逗号。
  2. vsnprintf 是按照特定格式将可变参数打印到字符串中,方便后面的输出。

顺便说一下,宏在C语言中真是离不开,虽然很多书都推荐不要用宏,因为不便调试,但是宏可以简化代码,提高效率,使用范围是相当的广。

四 变长结构体

在C语言中,本身是不支持动态数组的,但是有些技巧可以实现类似动态数组的效果,一般人可能这样定义:

typedef struct Arry {
  int arry_len;
  char * content;
} * Arry;
//申请内存
Arry*p_ptr = (Arry*)malloc(sizeof(parry));
p_ptr ->content = (char *)malloc(100);
//释放内存
free(p_ptr ->content);
free(p_ptr);

这里面我们注意到这个这个结构体有两个变量,一个是int,一个是char* ,char* 里面保存的是申请内存的指针,如果用sizeof去求的话,会发现整个结构体的大小为4+4 = 8。
p_ptr 指针和p_ptr ->content 指针指向的内存没有任何关系。

变长结构体定义如下:

typedef struct
{
    int a;
    char b[0];
} * DArray;
//申请内存 100
DArray p_darray = (DArray)malloc(sizeof(*DArray)+100);
//释放内存
free(p_darray);

相比上面的定义好处如下:
1)只需要申请和释放内存一次即可。
2)内存分配是连续的,可以减少内存碎片。
3)节省内存,sizeof(*DArray) = = 4.

  1. 可以方便用来做socket数据包传输,解析数据,等。

五 求数组和枚举的小技巧

对于一些情况,我们需要用到数组成员的个数和枚举的大小,一般对数组求大小可以这样做:
sizeof(array)/sizeof(array[0])得到;对于枚举类型,我们可以在最后定义一个最大宏,标识宏的结束:

enum { ONE,TWO,THREE,MAX};

我们在循环的时候就可以用MAX,以后添加变量在MAX前面添加即可,相关代码不用改变。

六 宏定义的小技巧

#define EXAMPLE do{
   xxxx \
   xxxxx \
 }while(0);
//这样好处把宏定义封装起来,后面多加分号不容易出错。

七 NULL 判断颠倒处理

我们判断NULL指针的时候,一般用if (p == NULL),但是这种写法,有可能少写一个=号,而程序不报错,可以改成 if (NULL == p) 这样写之后如果少写一个=号,程序显然会报错的。

八 其他提示

C语言的告警一定要多注意,尽量让代码零告警,不仅仅看起来清爽,还可以避免不少难查的Bug,还有一点就是程序写好之后,用valgrind --tool=memcheck --leak-check=full 运行检查,
看看是否有内存泄漏,还有就是invaild 读和写,这往往是程序运行core的根源,切记切记!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354

推荐阅读更多精彩内容