Bootstrap

C语言-可变参数

博客主页:【夜泉_ly
本文专栏:【C语言
欢迎点赞👍收藏⭐关注❤️

在这里插入图片描述

⚠️ 注意事项

本文以 Visual Studio 2022 x86 环境为例解析可变参数机制,
旨在揭示底层原理。

文中涉及的宏实现(如 _INTSIZEOF)是MSVC编译器的特定实现,
具有强平台依赖性

🔧 生产环境建议

务必通过 <stdarg.h> 抽象实现细节


💻 示例:

#include <stdarg.h>
int sum(int n, ...) {
	va_list s;
	va_start(s, n);

	int sum = 0;
	while (n--)
		sum += va_arg(s, int);

	va_end(s);
	return sum;
}
int main() {
	sum(3, 4, 5, 6);
	return 0;
}

这是标准可变参数函数的典型结构:

  1. 必须包含<stdarg.h>头文件
  2. 至少有一个固定参数(这里是 n
  3. 使用va_list类型存储参数列表
  4. 通过va_start初始化参数列表
  5. va_arg逐个读取参数
  6. 最后调用va_end清理

看看怎么执行的:
首先,sum(3, 4, 5, 6);
传参时,从右向左压栈
在这里插入图片描述
大多数系统的栈是从高地址向低地址增长的。
这意味着新压入栈的数据会存放在更低的地址:
在这里插入图片描述
在这里插入图片描述
接下来知道了参数的地址关系,
我们在来具体看看函数内部:

第一行:

va_list s;

va_list 是个宏,实际上就是 char*
好,现在我们声明了一个 char*s

定位:

va_start(s, n);

va_start 又是个宏,展开后:

((void)(s = (va_list)(&(n)) + ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))));

作用:将指针 s 指向变量 n 的下一个对齐位置,
即找到可变参数的第一个。

相关定义:

#define _ADDRESSOF(v) (&(v))
#define _INTSIZEOF(n)          ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))
#define __crt_va_start_a(ap, v) ((void)(ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v)))

关于 _INTSIZEOF(n)
((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))
用来计算 nint 对齐后的偏移量。
比如当 nchar 类型,大小为 1
由于存在内存对齐,+4 后才能找到下一个参数,
而这句话的作用就是把这个 1 变成 4(的倍数)
可简单测试:

#include <stdio.h>
int main() {
	for(int size = 1; size <= 16; size++)
		printf("%d ", ((size + sizeof(int) - 1) & ~(sizeof(int) - 1)));
	return 0;
}

输出 4 4 4 4 8 8 8 8 12 12 12 12 16 16 16 16

功能实现:

int sum = 0;
while (n--)
	sum += va_arg(s, int);

va_arg 第一个参数传刚刚定义的 va_list s;
第二个参数传对应可变参数的类型。
作用:返回当前的可变参数,
并将 s 跳到下一个可变参数。

下面假设类型为 type,展开后:
(*(type*)((s += ((sizeof(type) + sizeof(int) - 1) & ~(sizeof(int) - 1))) - ((sizeof(type) + sizeof(int) - 1) & ~(sizeof(int) - 1))));
相关定义:

#define _INTSIZEOF(n)          ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))
#define __crt_va_arg(ap, t)     (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))

末尾:

va_end(s);
return sum;

可以理解为 s = NULL
相关定义:

#define __crt_va_end(ap)        ((void)(ap = (va_list)0))

在这里插入图片描述


希望本篇文章对你有所帮助!并激发你进一步探索编程的兴趣!
本人仅是个C语言初学者,如果你有任何疑问或建议,欢迎随时留言讨论!让我们一起学习,共同进步!

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;