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§) {
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 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 * Trans(PacketProfileDetectId id)
{
switch (id) {
CASE_CODE (PROF_DETECT_SETUP);
CASE_CODE (PROF_DETECT_GETSGH);
CASE_CODE (PROF_DETECT_IPONLY);
default:
return “UNKNOWN”;
}
}
3) 对于##宏解释:
/* ## 是变量连接符,将两个字符连接成一个变量 */
#define FUN(a) printf(“The square of " #a " is %d.\n”,b##a)
int bm = 2
FUN(m)
/* 打印 The square of m is 2.*/
4) VA_ARGS是可变参数宏,表示可变参数列表。
#define Debug(…) printf(VA_ARGS)
Debug(“Y = %d\n”, y);
/*自动替换成:
printf(“Y = %d\n”, y);
*/
5) 对于 ##__VA_ARGS__宏 作用是如果最后的可变变量为空忽略后面的逗号。
6) vsnprintf 是按照特定格式将可变参数打印到字符串中,方便后面的输出。
顺便说一下,宏在C语言中真是离不开,虽然很多书都推荐不要用宏,因为不便调试,但是宏可以简化代码,提高效率,使用范围是相当的广。
四 变长结构体
在C语言中,本身是不支持动态数组的,但是有些技巧可以实现类似动态数组的效果,一般人可能这样定义:
typedef struct Arry {
int arry_len;
char * content;
} * Arry;
//申请内存
Arryp_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.
4) 可以方便用来做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的根源,切记切记!