近期在看一本书,叫作《嵌入式C语言自我修养》,写的内容对我帮助很大,是一本好书。在第6章,GNU C编译器扩展语法精讲一节,这本书给出了一些变参函数的例子:c++
//1.变参函数初体验 #include<stdio.h> void print_num(int count,...) { int *args; args = &count + 1; for(int i = 0;i < count;i++) { printf("*args:%d\n",*args); args++; } } int main(void) { print_num(5,1,2,3,4,5); return 0; }
上面的代码很好理解:定义一个变参函数print_num,在函数内部先取得第一个参数的地址赋值给一指针,而后将指针后移,取得后面的参数并打印出来。在main函数中,传给print_num 6个参数,按这个逻辑,应该是打印出:ubuntu
*args:1 *args:2 *args:3 *args:4 *args:5
可是结果却出人意料:bash
打印出的值和传进去的值彻底不相等,甚至毫无规律可言。函数
上述代码中,是经过取首个参数的地址,并日后移动这个指针来得到后面参数的,那么问题极可能出在两个地方:测试
咱们一个一个来看,先暂且假定这些参数地址是连续的,且相隔同样的距离。那么咱们就能够聚焦于指针的移动方式了。指针移动是“args++”这一行语句来控制的。笔者修改了一下书上的代码:网站
#include<stdio.h> void print_num(int count,...) { int *args; args = &count; for(int i = 0;i <= count;i++) { printf("addr:%p\n",args); printf("*args:%d\n",*args); args++; } } int main(void) { print_num(5,1,2,3,4,5); return 0; }
主要增长了对于每一个参数的地址的打印,运行结果以下:操作系统
笔者发现这个"args++"每次日后移动4个字节,这是由于对于"int"型指针的移动操做,是以4(sizeof(int))为基本单位的。同理,对于"char"型指针的移动操做,以1(sizeof(char))为单位。3d
一个"int"型指针大小若是等于4,那么上述对于指针移动操做就没问题。但是"int"型指针大小真的等于4吗?指针
笔者用代码来测试下:code
#include<stdio.h> int main() { char* charPoint; int* intPoint; double* doublePoint; struct st{ int first; }; struct st *structPoint; printf("sizeof(char*):%ld\n",sizeof(charPoint)); printf("sizeof(int*):%ld\n",sizeof(intPoint)); printf("sizeof(double*):%ld\n",sizeof(doublePoint); printf("sizeof(struct*):%ld\n",sizeof(structPoint)); return 0; }
运行结果:
能够看到,不只"int"型指针是8字节大小,"char"、"double"和结构体指针也都是8字节大小。这是由于笔者电脑安装的是64位系统。因此书上代码的"int"型指针自增操做不适用于笔者,笔者将其改成“args += 2”,在dev c++这个IDE中能够获得正确的结果,但在ubuntu gcc下仍是不对。
解决了第一个指针移动步长问题,仍是得不到正确答案。笔者怀疑参数地址极可能不连续。如何看函数的参数地址信息?方法有不少,笔者就选一种比较快捷的方式——看汇编代码。
在ubuntu的终端框输入
gcc -S [源文件]
就能获得一个带".s"后缀的汇编代码文件。
咱们对比着看main函数与print_num函数中关于参数传递的部分:
在main函数中,各个参数被放入不一样的寄存器,在print_num函数中,又从寄存器中将参数取出来放入print_num的函数堆栈中。仔细看各个参数最终被放入的堆栈位置,发现第一个参数地址和第二个参数地址差了28个字节,然后面的参数地址之间都是差8个字节。这也就解释了为什么以前的代码结果不对了。
因此只要在第一个参数地址的基础上加上偏移量28便可("char*"型)。
运行结果符合预期:
可是为何第一个参数和第二个参数间隔28字节,笔者暂时还不清楚,盲猜须要去看gcc中编译器的相关知识。
以往对于固定参数个数的普通函数的传参,是这样处理的:前几个参数放入寄存器,若个数超出,则压入函数堆栈。笔者有点好奇变参函数是否也如此,就给这个print_num传了18个参数:
汇编代码以下:
这说明了变参函数的传参规则和普通函数并没有两样。
在看书的时候,我喜欢边看边敲代码,这一次照着书上敲的代码运行结果不对,就有了上面的一些探究过程。若是我没有动手实践,之后碰到相似问题时极可能会蒙圈。因此动手实践颇有必要。
另外,书上的东西并不必定全对,而且它的正确性须要有特定的前提作保证。好比,要是我使用的是32位系统,且编译器在处理变参函数时将参数连续压栈,那么书上的代码就是彻底正确的。咱们无需惧怕这些坑,咱们须要作的就是去找到这些前提条件,去找到问题的本质点,最后解决问题。
《嵌入式C语言自我修养——从芯片、编译器到操做系统》
欢迎你们转载本人的博客(需注明出处),本人另外还有一个我的博客网站:lularible的我的博客,欢迎前去浏览。