C语言开发过程中的一些积累

当程序对性能有极其变态要求时,可以使用GCC特有的__builtin_expect(x,boolean);x为变量,后面是该变量最可能的值(真,假).if(x)和if(__builtin_expect(x,TRUE))的编译结果是不一样的。x几乎平时都为真时,可以用后者,会快一些。 


如果结构体中有64位的变量,那么要小心了,sizeof(struct xxx)可能不是你期望的值。举例,struct A{long long x;int y;}; sizeof(struct A)默认等于16,而不是12。因为编译器会根据结构体最大的成员长度来制定对齐方式,这里最大是8个字节的long long类型变量,所以整个体积是16。这个问题,在32位的系统中不太容易暴露。如果要等于12这个我们需要的值的话,加入#pragma pack(4)就行了。 


c语言的sscanf等系列函数,如果想使用short, char类型,应该要懂得使用h, h代表half的意思, short 那么就是hd, 半个整形数, char 就是hhd, 四分之一个整形数( unsigned 只需要把d改为u即可). 如果不这么干, 而是"%d", (int *)&char_var, 这种做法会产生溢出, 下一个跟着它的变量就要倒霉了,莫名其妙被修改,你还查不出来.. 


const类型是不变的,volatile类型是易变的,两个类型放在一起定义,不仅不矛盾,还非常有用. 在嵌入式系统中,存在很多存储硬件状态的变量, 如,串口收发状态, 由硬件写入, 它一定是volatile易变的. 但是,因为是硬件信息,代码层级中又是不允许去改写它的(即const类型),所以,const volatile组合使用是不矛盾的. 



glib的哈希性能不错,使用也很方便.在获取第100个元素的情况下,glib的hash只需要0.5ms. glib的hash和python的原生字典(hash),相比,python为2.6ms,比glib的hash慢了5倍. 


采用C编写的libev库是一个高性能的事件循环库,比libevent库性能更高,支持的事件类型更多,很多著名的如Nodejs和gevent等等都基于libev库。如io类型,用于网络和存储,当fd有数据可读写时,执行回调;timer类型,延时多久执行回调;period类型,周期地执行回调;signal类型,当收到一个信号,执行回调;stat类型,当一个文件改变时,执行回调,例如我们的存储密码的文件改变,自动执行回调;甚至支持fork类型,fork出来的子进程,如果结束时,可以执行回调。太有用的库了! 


C语言中,要实现跳转,比goto更牛的是setjmp和longjmp,setjmp设置一个起点,longjmp则负责返回到setjmp的位置,不限于函数,程序全局有效。一般在C中,setjmp和longjmp的典型用法是打造C里的try,exception机制,根据不同的值跳转到不同的错误处理代码段中。注意,由于setjmp是将 stack pointer (sp), frame pointer (fp), program counter (pc)存储下来以便返回原处,因此,不宜将setjmp用函数封装起来,有兴趣的分析一下函数栈原理就知道为什么这样容易出错了。 


写文件的操作,发现了一个很易错的地方。就是fopen以a+模式打开,fseek将指针置在文件的开头,但是,写入的东西怎么都不是从头开始写入,文件写一次增大一次。易错点:原因在于a+模式“The initial file position for reading is at the beginning of the file, but output is always appended to the end of the file.”这就是fseek失效的原因。如果要实现原来的想法,应该是r+,而不是a+。 


在条件编译中,我们一般只用#ifdef,其实用#if defined,不仅可以达到相同的效果,还可以实现更复杂的条件判断,例如 #if defined(XXX) && !defined(YYY) || ZZZ > 6


今天改代码,发现很多人在变量初始化存在误区,举例:static int xxx[SC_NUM]={0,0,0,0};这样做非常没有必要而且不好。第一,静态变量初始化,如果不赋值,一定为0,因此,上面根本不用赋值;第二,就算需要赋值,{}或{0}就可以了,这样代表数组所有元素都设置为0。然而{0,0,0,0}这种初始化会带来耦合,如果SC_NUM不等于4,而是改为10,那么整篇代码都要一一去修改,所以不应该这样初始化。 另外,这种做法,只适合0的情况,如果{1},只代表第一个元素初始化为1.


sprintf的一种常见错误用法:假设buffer变量已经有内容,希望这个内容再加内容,错误的用法是:sprintf(buffer,"%s:%s",buffer,sth);这样的结果是,buffer后并不是追加:xxx,而是彻底乱掉内容。这和高级语言的用法是不同的,高级语言如python,可以buffer=buffer+":"+sth,这样是完全正确的,习惯高级语言,再回来C,有些地方确实容易错。


define大家司空见惯,那么请问A函数里的define,B函数能否使用呢?答案是:...看情况...原因是:define只和出现的顺序有关,和是否在函数里无关。在A函数里define,如果A在B前头(c文件的书写顺序,不是运行顺序),那么是没有问题的,反之,就会出错。这个问题,你思考过吗?  


当一个程序,运行时需要输入参数以改变运行属性时,可以采用glib的option功能,在Goption.c,开头有示范用例,可以非常简单的获取程序的输入参数。举例,以后需要./CsuApp -r 20 -b -s "good"这样运行时,就可以用glib的功能了。 



对于涉及长度的变量时,应该是size_t类型;两个指针相减的差值变量,应该为ptrdiff_t。这样的代码会更具有移植性,代码也会更统一和易读。


内核中,由于是模块化设计,如果A模块想调用B模块的func1,怎么做呢?绝对不是在A模块加B模块的头文件!这样绝对耦合设计了,做不到模块化。做法是:使用EXPORT_SYMBOL,但前提还是B模块在func1函数后加这个宏定义


在Linux链表功能接口中还有一系列以"_rcu"结尾的宏,RCU(Read-Copy Update)是2.5/2.6内核中引入的新技术,它通过延迟写操作来提高同步性能。传统的rwlock机制在smp环境下随着处理机增多性能会迅速下降。RCU技术的核心是写操作分为写-更新两步,允许读操作在任何时候无阻访问,当系统有写操作时,更新动作一直延迟到对该数据的所有读操作完成为止。

猜你喜欢

转载自blog.csdn.net/yyw794/article/details/78114306