C 为什么va_arg()在x86_64和arm上产生不同的效果?

C 为什么va_arg()在x86_64和arm上产生不同的效果?,c,gcc,x86,arm,x86-64,C,Gcc,X86,Arm,X86 64,代码: #include <stdio.h> #include <stdarg.h> #include <stdlib.h> typedef unsigned int uint32_t; float average(int n_values, ... ) { va_list var_arg; int count; float sum = 0; va_start(var_arg, n_values); for (

代码:

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

typedef unsigned int uint32_t;

float average(int n_values, ... )
{
    va_list var_arg; 
    int count;
    float sum = 0;

    va_start(var_arg, n_values);

    for (count = 0; count < n_values; count += 1) {
        sum += va_arg(var_arg, signed long long int);
    }   

    va_end(var_arg);

    return sum / n_values;
}

int main(int argc, char *argv[])
{
    (void)argc;
    (void)argv;

    printf("hello world!\n");

    uint32_t t1 = 1;  
    uint32_t t2 = 4;  
    uint32_t t3 = 4;  
    printf("result:%f\n", average(3, t1, t2, t3));

    return 0;
}
但当我交叉编译并在openwrtARM 32位中运行它时,它是错误的

[root@OneCloud_0723:/root/lx]#./helloworld 
hello world!
result:13952062464.000000
[root@OneCloud_0723:/root/lx]#uname -a
Linux OneCloud_0723 3.10.33 #1 SMP PREEMPT Thu Nov 2 19:55:17 CST 2017 armv7l GNU/Linux
我知道不要用不正确类型的参数调用va_arg。但为什么我们可以在x86_64中得到正确的结果,而不是在arm中

谢谢。

在我的系统x86\u 64上

#include <stdio.h>


int main(void)
{
    printf("%zu\n", sizeof(long long int));

    return 0;
}
也不要将您的uint32\u t声明为

这是不可移植的,因为int不能保证在整个系统中有4个字节长 所有架构。标准C库实际上在中声明了这种类型 stdint.h,您应该使用thos类型

所以你的程序应该是这样的:

float average(int n_values, ... )
{
    va_list var_arg; 
    int count;
    float sum = 0;

    va_start(var_arg, n_values);

    for (count = 0; count < n_values; count += 1) {
        sum += va_arg(var_arg, uint32_t);
    }   

    va_end(var_arg);

    return sum / n_values;
}
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdint.h>

float average(int n_values, ... )
{
    va_list var_arg; 
    int count;
    float sum = 0;

    va_start(var_arg, n_values);

    for (count = 0; count < n_values; count += 1) {
        sum += va_arg(var_arg, uint32_t);
    }   

    va_end(var_arg);

    return sum / n_values;
}

int main(void)
{
    printf("hello world!\n");

    uint32_t t1 = 1;  
    uint32_t t2 = 4;  
    uint32_t t3 = 4;  
    printf("result:%f\n", average(3, t1, t2, t3));

    return 0;
}
这是可移植的,并且应该在不同的应用程序中产生相同的结果 体系结构。

在x86-64 Linux上,每个32位arg在单独的64位寄存器中传递,因为这是x86-64 System V调用约定所要求的

调用方恰好将32位arg扩展到64位寄存器中。这不是必需的;程序中未定义的行为可能会让另一个调用程序在arg传递寄存器中留下高垃圾

被调用者的平均值正在寻找三个64位参数,并在调用者放置它们的相同寄存器中查找,因此它正好起作用

在32位ARM上,long-long不适合单个寄存器,因此寻找long-long参数的被叫方肯定是在不同于调用者放置uint32_t参数的地方寻找


被调用者看到的第一个64位参数可能是long long1,这可能是因为对于arm,long long int的大小与x86_64不同。您的var_arg应该在两个系统va_argvar_arg,uint32_t上,只有这样才能产生正确的结果。未定义的行为可能会导致事情看起来正常,并且可能导致事情崩溃。你很幸运,每个都有一个,所以现在你可以修复它了。@Pablo long-long int在arm上也有8个字节,我测试了它。谢谢。要符合标准,ARM上的大小必须至少为64位。我想这可能需要128位,但这不太可能。@JonathanLeffler我的办公桌上现在没有armv7处理器,我无法测试它。但是基于长的,长的是64位宽。无论如何,OPs代码使用了错误的类型。IDK为什么您认为32位ARM上的long long可能不是8。通过查看ARM编译器输出中返回sizeof:的函数,可以轻松地对其进行测试。我不知道有哪种实现的long-long比表示ISO C所需的值范围更宽,所以它通常是64位类型。这并不能回答问题。问题是到底发生了什么,而不是如何解决。OP已经知道存在类型不匹配。虽然您正确地指出,在uint32中使用您自己的typedef是一个OP似乎没有意识到的错误。@PeterCordes-您可以找到一些罕见的36位计算机,其中long-long使用72位。这是一个很好的答案,我没有处理过调用约定,所以我甚至没有考虑过这些事情。
typedef unsigned int uint32_t;
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdint.h>

float average(int n_values, ... )
{
    va_list var_arg; 
    int count;
    float sum = 0;

    va_start(var_arg, n_values);

    for (count = 0; count < n_values; count += 1) {
        sum += va_arg(var_arg, uint32_t);
    }   

    va_end(var_arg);

    return sum / n_values;
}

int main(void)
{
    printf("hello world!\n");

    uint32_t t1 = 1;  
    uint32_t t2 = 4;  
    uint32_t t3 = 4;  
    printf("result:%f\n", average(3, t1, t2, t3));

    return 0;
}