C语言可变参数讲解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/bjbz_cxy/article/details/87871341

一.可变参数

简介:

         什么是可变参数?

         答:就是在函数声明时参数是...表示后面可以有多个不同类型的参数传递进来

最著名的就是"printf",通过格式占位符来判断后面的参数类型!

printf("%c,%d",'c',1);

但是如果给:

printf("%s%f",'c',2);

就会打印不正确值,因为printf不会智能判断类型,只能通过占位符,原因:

在运行阶段,没有类型一说,只有数据,所以这些是编译阶段来确定生成怎样的jmp来跳转可执行代码段,但是如果要强行想要其智能判断是非常复杂,会大大降低使用可变参数的源代码编译速度!

原理:

扫描二维码关注公众号,回复: 5755574 查看本文章

因为函数参数规则,默认都是从右向左入栈,而栈每次入栈都会让sp栈指针发生改变,所以栈也就是先入后出!

如果使用可变参数,在编译期间,编译器会隐式计算传的参数数量,然后申请4字节(看编译器)的指针来指向这些值!

实践:

好了知道了原理,那么我们就可以来实践了:

int test(...){

}

int main(){
    test(1,2,3);
}

这样的写法是错误的,不能直接使用可变参数,因为上面说过了,可变参数后面编译器会隐式增加指针来指向调用函数传递进来的参栈内存!

但是我们想要获取,就要获取其中一个栈的指针地址,然后根据偏移来进行运算获取下一个!

所以需要在函数第一个参数里手动声明一个变量作为参数,好用它来做偏移:

int test(int num,...){

}

int main(){
    test(1,2,3);
}

然后在获取num的地址,然后在+sizeof(int)(编译器根据位数不同会调整类型大小,这样做更加泛型)的偏移

int test(int num,...){
    int *pNum = #
    printf("%d",*(p1+1/*编译器会根据指针类型+几个类型字节单位*/));
}

int main(){
    test(1,2,3);
}

这里注意上面为什么是+1而不是+sizeof(int)?

原因:

汇编发现(vs2013):编译器在进行地址add时根据当前类型大小字节进行偏移,比如int*是4字节,那么+1就是加一个int*单位!

打印输出:

正确是我们想要的值,注意我们无法手动获取参数大小,因为如果依靠指向下一个判断是否为NULL的是完全不行的!

在内存里面根据偏移量增加也许下一个地址里的内容是NULL,万一不是则就乱了!这个完全靠运气!

一般判断NULL都是用来判断自己当前程序段的指针内容,其它栈空间里的很多地方都是程序员自己来开辟的,是编译器为了保证运行环境的正确化会额外增加一些依赖库在里面!谁也不知道自己偏移后的下一个地址是什么!因为文件到内存空间映射编译器每次都会打乱重新分配的!(详细参考pe文件到内存的映射,以及编译器的实现)

所以这也是为什么在使用可变参数时如果定义为:

int test(...){

}

会报:至少需要一个明确类型数量的错误了,编译器要求程序员指定长度!

所以我们需要在第一个参数里给定大小,然后循环偏移就可以获取到所有的值了!

编译器也为我们提供了可以获取这些值的:va_list链表

存在于:stdarg.h头文件中

va_list链表用于存储第一个参数的地址,好用于后续的偏移

va_start是将第一个参数的地址放入到链表里

va_arg可以从链表里获取首地址然后根据第二个大小进行地址偏移

va_end释放链表

用法:

int test(int num, ...){
	va_list vga;
	va_start(vga, num);
	for (int i = 0; i < num;++i){
		int c = va_arg(vga, int);
		printf("%d,", c);
	}
	va_end(vga);
	return 1;
}
int main()
{
	test(2, 2, 4);
	getchar();
}

打印:

如果有没有看懂的地方,或者不理解的地方可以在下方留言!

猜你喜欢

转载自blog.csdn.net/bjbz_cxy/article/details/87871341