变长个数参数,在64位和32位系统的差异

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字节前进

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值