C语言中变参函数传参探究

背景引入

近期在看一本书,叫作《嵌入式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

在这里插入图片描述

打印出的值和传进去的值彻底不相等,甚至毫无规律可言。函数

问题分析

上述代码中,是经过取首个参数的地址,并日后移动这个指针来得到后面参数的,那么问题极可能出在两个地方:测试

  1. 指针移动的方式不正确
  2. 参数的地址排布可能不是连续的

咱们一个一个来看,先暂且假定这些参数地址是连续的,且相隔同样的距离。那么咱们就能够聚焦于指针的移动方式了。指针移动是“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的我的博客,欢迎前去浏览。

相关文章
相关标签/搜索