C语言可变长度参数,有时候会失效。
#include <stdarg.h>
void print(int dummy, ...) {
va_list vl;
va_start(vl,dummy );
int a = va_arg(vl, int);
int b = va_arg(vl, int);
int c = va_arg(vl, int);
va_end(vl);
}
int main()
{
char a = 0x0A;
long long b = 0x0B;
int c = 0x0C;
print(0, a, b, c);
return 0;
}
在64位系统中,print函数的int a取值10正常,int b取值11正常,int c取值12正常。但是在32系统中,int c取值为0,不正常了。
分析如下:dummy参数,以及后面紧跟的几个参数是粘在一起的连续内存分布。抓住了dummy,就等于把后续参数的地址基本抓住了。
从dummy的地址开始偏移sizeof(dummy),就是第一个变参的地址。这个偏移方向是加还是减呢?一般来说,偏移方向要加值。具体与栈的生长方向以及参数压栈顺序有关。
低地址 <<< | dummy | a | b | c | >>> 高地址
在64位系统中,一个数据的压栈最小尺寸是8字节。因此即使是char a这个一字节数据,压栈后占了8字节。
int a = va_arg(vl, int); 这句话的意思先把vl(本质是一个指针,此时指向a)地址的变量按int类型解释,显然char和int是兼容的,强制解释为int没有问题。
然后呢,vl要前进一步,加多少呢?注意,此时加的不是sizeof(int)==4,而是8字节!!因为64位意味着有个分布在8字节倍数的边界上这个隐含约束
这样看来,64位的va_xxx就不用担心错位了,因为从char,int。。。到double都是安全的,因为他们都不超过8字节,除非遇到更长的基本数据类型。
在32系统中,压栈的最小尺寸是4字节。是否就很容易出现问题了呢?也就是int b = va_arg(vl, int);这句的问题到底在哪里有bug?
此时vl指向b这个位置,b的内存布局为int64, 即 0x0B 00 00 00 00 00 00 00。我们按照int强制解释,因为数据值很小,没有截断问题,而且小端序会把0x0B排在前面,int64和int很容易就兼容了
(考虑:大端序00 00 00 00 00 00 00 0x0B, 按照前字节强制解释成int就是0值啊,所以小端序就有很大的好处了)
然后呢,vl要前进一步,加多少呢?加上sizeof(int)正好是4的整数倍,因此就加了sizeof(int)=4, 那个位置显然不是c的位子,因此下一次int c = va_arg(vl, int);就会取出0值出来了。
如何解决这个问题呢?一个办法是总是用64位的。第二办法是修改源代码:
#include <stdarg.h>
void print(int dummy, ...) {
va_list vl;
va_start(vl,dummy );
int a = va_arg(vl, char);
int b = va_arg(vl, long long);
int c = va_arg(vl, int);
va_end(vl);
}
int main()
{
char a = 0x0A;
long long b = 0x0B;
int c = 0x0C;
print(0, a, b, c);
return 0;
}
int b = va_arg(vl, long long); 在32位系统中,就是按照sizeof(long long)=8字节前进