当前位置:网站首页>可变长参数

可变长参数

2022-07-06 08:38:00 飞天_

 

目录

为什么可以实现可变长参数?

程序的内存布局

函数调用惯例


#include <stdio.h>
#include <stdarg.h>

void MultiArg(int prev_param, ...)
{
	va_list arg_ptr;
	//va_start将arg_ptr设置为传递到此函数的参数列表中的第一个可选参数。参数arg_ptr必须拥有va_list类型
	//在首次使用va_arg前必须使用va_start
	//va_arg从arg_ptr指定的位置中检索type的值,并增加arg_ptr以通过使用type的大小来确定下一个参数的开始位置
	//来指向列表中的下一个参数
	//prev_param是固定参数在内存中的地址,在调用va_start后,arg_ptr指向第一个可变参数。这个宏的作用就是在prev_param的内存地址上增加prev_param所占的内存大小,这样就得到了第一个可变参数的地址。
	va_start(arg_ptr, prev_param);
	for (int i = 0; i < prev_param; i++)
	{
		int value = va_arg(arg_ptr, int);
		printf("第%d个可选参数=%d\n", i, value);
	}
	//检索所有参数后,重置va_end指向的指针NULL,在函数返回前必须在使用了va_start或va_copy初始化的各个参数列表上调用va_end
	va_end(arg_ptr);
}

//最后一个参数需要是-1
void MulitArg_2(int prev_param,...)
{
	va_list var_ptr;
	va_start(var_ptr, prev_param);
	int value = 0;
	int i = 0;
	//依次取出堆栈中的可选参数
	while ((value = va_arg(var_ptr, int)) != -1)
	{
		printf("第%d个可选参数=%d\n", i++, value);
	}
	va_end(var_ptr);
}

int main()
{

	MultiArg(5, 1, 2, 3, 4, 5);
	MulitArg_2(0, 1, 2, 3, 4, 5, -1);

	getchar();
	return 0;
}

为什么可以实现可变长参数?

由栈结构和函数调用惯例决定的

程序的内存布局

现代的应用程序都运行在内存空间里,如果是32的操作系统,有32根地址线,最大可用地址空间为4G,2的32次方。大多数操作系统都会将4GB的内存空间中的一部分挪用给内核使用,应用程序无法直接访问这一段内存,这一部分内存地址称为内核空间。

Windows默认高地址的2GB作为内核空间

Linux默认高地址的1GB作为内核空间

剩下的3GB或2GB称为用户空间。

用户空间一般默认分类如下:

栈:最高地址处,通常用数兆字节

堆:栈下面,一般有几十数百兆容量

可执行文件映像:存储可执行文件在内存中的映像

保留区:内存中禁止访问的区域,在大多数操作系统中极小的地址是不被允许访问的,例如常见的NULL地址

Linux下一个进程里典型的内存布局:

 

栈最重要的两个寄存器:ebp、esp。

ebp在栈中是固定不变的,始终指向栈底。(ebp寄存器又称为帧指针)

esp是一直变换的始终指向栈顶。

esp增大,栈空间减小,esp减小,栈空间增大。

用于保存一个函数调用所需要的维护信息的栈称为堆栈帧也叫活动记录。

堆栈帧一般包括:

1、函数的返回地址和参数

2、临时变量(非静态局部变量以及编译器自动生成的临时变量)

3、上下文(函数调用前后需要保持不变的寄存器)

一个常见的帧指针/活动记录如下所示:

 

函数调用惯例

函数的调用方和被调用方需要有一个明确的约定,参数是怎么传递的,函数名字是怎么修饰的,只有提前约定好了,才能保持正确的调用,这种约定就称为函数调用惯例。

 变长参数的实现正是得益于C语言默认的cdecl调动惯例的自右向左压栈传递方式

调用可变长参数函数栈结构为:

MulitArg_2(0, 1, 2, 3, 4, 5, -1);

 

 

 

原网站

版权声明
本文为[飞天_]所创,转载请带上原文链接,感谢
https://blog.csdn.net/qq_36769722/article/details/124950328