⚠️ 注意事项
本文以 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;
}
这是标准可变参数函数的典型结构:
- 必须包含
<stdarg.h>
头文件 - 至少有一个固定参数(这里是
n
) - 使用
va_list
类型存储参数列表 - 通过
va_start
初始化参数列表 - 用
va_arg
逐个读取参数 - 最后调用
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))
,
用来计算 n
按 int
对齐后的偏移量。
比如当 n
为 char
类型,大小为 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语言初学者,如果你有任何疑问或建议,欢迎随时留言讨论!让我们一起学习,共同进步!