移位操作
右移时,如果被移位的是无符号数,则填 0 。如果是有符号数,那么可能填 0 或符号位。保证腾空位会置 0,就把变量声明为无符号型。
C语言中的移位不是循环移动。
内存
内存中的段包括:
.data - 已初始化全局/静态变量,在整个软件执行过程中有效;
.bss - 未初始化全局/静态变量,在整个软件执行过程中有效;
.stack - 函数调用栈,其中的内容在函数执行期间有效,并由编译器负责分配和收回;
.heap - 堆,由程序显式分配和收回,如果不收回就是内存泄漏。
编c语言代码较好的原则:自己使用的内存最好自己申请和释放。写功能接口的时候,需要内存操作时,在接口留一个内存指针,由调用者自己申请和释放。比如整形转字符串,可以写成如下接口
char *itoa (int n);
那么要么字符串的内存是静态的,要么该函数自动在堆上分配内存,此时需要我们自己注意释放内存,不是自己写的申请内存就很容易漏掉释放,不如改成如下的接口:
char *itoa(int n, char *retbuf);
typedef
定义如下结构,编译器会报错
typedef struct tagNode { char *pItem; pNode pNext; } *pNode;
上面的定义实际上的顺序是
struct tagNode { char *pItem; pNode pNext; }; typedef struct tagNode* pNode;
可以看到在定义struct tagNode的pNext时,类型pNode还没有定义,是未知的。
正确的定义方法:
struct tagNode { char * pItem; struct tagNode* pNext; }; typedef struct tagNode* pNode;
编译问题
一个数组a定义在f1.c中,但是我想在f2.c中计算它的元素个数,用sizeof可以达到这个目的吗?
不行,本质原因是sizeof操作符只是在"编译时(compile time)"起作用,而C语言的编译单位是每次单个.c文件进行编译(其它语言也都如此)。因此,sizeof可以确定同一个源文件中某个数组的大小,但是对于定义在另一个源文件中的数组它无能为力了,因为那已经是"运行时(run time)"才能确定的事情了。
解决方式:
1)、定义一个全局变量,让它记住数组的大小,在另外一个.c文件中我们通过访问这个全局变量来得到数组的大小信息(
好像有点小题大做得不偿失)。
2)、在某个.h文件中用宏定义数组的大小,例如#define ARRAY_SIZE 50,然后在两个源文件中都包含这个.h文件,通过直接访问ARRAY_SIZE来得到定义在不同.c文件中的数组的大小。
3)、设置数组的最后一个元素为特殊值,例如0,-1,NULL等,然后我们通过遍历数组来寻找这个特殊的结尾元素,从而判断数组的长度(这个办法效率低,也是笨笨的)。
声明
有一段程序存储在起始地址为0的一段内存上,如果我们想要调用这段程序,请问该如何去做?
(*(void (*)( ) )0)( );
(void (*)( )) ,是一个返回值为void,参数为空的函数指针原型。
(void (*)( ))0,把0转变成一个返回值为void,参数为空的函数指针,指针指向的地址为0。
*(void (*)( ))0,前面加上*表示整个是一个返回值为void的函数的名字。
(*(void (*)( ))0)( ),这就是一个函数了。
可变参函数
在C标准头文件stdarg.h中,定义了va_start,va_arg,va_end宏,典型的如下:
typedef char * va_list; #define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) #define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) ) #define va_arg(ap,type) \ ( *(type *)((ap += _INTSIZEOF(type)) - _INTSIZEOF(type)) ) #define va_end(ap) ( ap = (va_list)0 )
在intel+windows的机器上,函数栈的方向是向下的,栈顶指针的内存地址低于栈底指针,所以先进栈的数据是存放在内存的高地址处。
在绝大多数C编译器中,默认情况下,参数进栈的顺序是由右向左的,因此,参数进栈以后的内存模型如下图所示:最后一个固定参数的地址位于第一个可变参数之下,并且是连续存储的。
|--------------------------| ->高内存地址处
| -最后一个可变参数- |
|--------------------------|
|--------------------------|
| - 第N个可变参数 - | ->va_arg(arg_ptr,int)后arg_ptr所指的地方,
| ...... | 即第N个可变参数的地址。
|--------------------------|
|--------------------------|
| -第一个可变参数 - |
|--------------------------| ->va_start(arg_ptr,start)后arg_ptr所指的地方,
|--------------------------| 即第一个可变参数的地址
| |
| -最后一个固定参数- |
|--------------------------|
| ................. |
|-------------------------- | -> 参数的起始地址
| |
|-------------------------- | -> 低内存地址处
首先把va_list被定义成char*,目前所用的PC机上,字符指针类型可以用来存储内存单元地址。而在有的机器上va_list是被定义成void*的。
_INTSIZEOF(n)主要是为了某些需要内存的对齐的系统.这个宏的目的是为了得到最后一个固定参数的实际内存大小。
va_start的第一个参数是va_list变量指针,第二个参数应该传可变参函数的最后一个固定形参,其作用是使va_list类型的指针指向变参函数的第一个可变形参。
va_arg宏从上面的实现代码可以看出,其具有两个作用:第一是使va_list类型的指针指向下一个可变参数;第二是获取va_list类型指针指向的值。va_arg宏的第一个参数是va_list变量,第二个参数是待获取的参数的数据类型。
va_end主要是对资源释放,使va_list类型的指针不指向任何内存。
总结:
原理上讲,只要取出第一个参数地址后,后面的参数都是按4字节递增就可以了,那些宏是为了兼容性考虑的,例如到了64位后,用宏的话就按8字节递增了,还有对齐问题。
1 标准C库的中的三个宏的作用只是用来确定可变参数列表中每个参数的内存地址,编译器是不知道参数的实际数目的。
2 在实际应用的代码中,程序员必须自己考虑确定参数数目的办法,如
1) 在固定参数中设标志-- printf函数就是用这个办法。
2) 在预先设定一个特殊的结束标记,就是说多输入一个可变参数,调用时要将最后一个可变参数的值设置成这个特殊的值,在函数体中根据这个值判断是否达到参数的结尾。本文前面的代码就是采用这个办法.
无论采用哪种办法,程序员都应该在文档中告诉调用者自己的约定。
3、实现可变参数的要点就是想办法取得每个参数的地址,取得地址的办法由以下几个因素决定:
①函数栈的生长方向
②参数的入栈顺序
③CPU的对齐方式
④内存地址的表达方式
结合源代码,我们可以看出va_list的实现是由④决定的,_INTSIZEOF(n)的引入则是由③决定的,他和①②又一起决定了va_start的实现,最后va_end的存在则是良好编程风格的体现,将不再使用的指针设为NULL,这样可以防止以后的误操作。
4、取得地址后,再结合参数的类型,程序员就可以正确的处理参数了。理解了以上要点,相信稍有经验的读者就可以写出适合于自己机器的实现来。
5.float 类型传递给 "..." 时,在变长参数的参数传递时,会被强制转换成 double 类型。
short 或 char 等类型,不足 int 大小的,传递给 "..."时,在变长参数的参数传递时,会被强制转换成 int 类型。
参考:http://www.douban.com/group/topic/31402765/
http://blog.csdn.net/hackbuteer1/article/details/7558979
http://bygreencn.wordpress.com