时间:2022-12-13 09:30:26 | 栏目:C代码 | 点击:次
近期在看一本书,叫做《嵌入式C语言自我修养》,写的内容对我帮助很大,是一本好书。在第6章,GNU 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个参数,按这个逻辑,应该是打印出:
*args:1
*args:2
*args:3
*args:4
*args:5
但是结果却出人意料:
打印出的值和传进去的值完全不相等,甚至毫无规律可言。
上述代码中,是通过取首个参数的地址,并往后移动这个指针来获得后面参数的,那么问题很可能出在两个地方:
我们一个一个来看,先暂且假定这些参数地址是连续的,且相隔一样的距离。那么我们就可以聚焦于指针的移动方式了。指针移动是“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))为单位。
一个"int"型指针大小如果等于4,那么上述对于指针移动操作就没问题。可是"int"型指针大小真的等于4吗?
笔者用代码来测试下:
#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语言自我修养――从芯片、编译器到操作系统》