代码:
#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;
}
当我在ubuntu(x86_64)中运行时,没关系。
lix@lix-VirtualBox:~/test/c$ ./a.out
hello world!
result:3.000000
lix@lix-VirtualBox:~/test/c$ uname -a
Linux lix-VirtualBox 4.4.0-116-generic #140-Ubuntu SMP Mon Feb 12 21:23:04 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
lix@lix-VirtualBox:~/test/c$
但是当我在openwrt(ARM 32bit)中交叉编译并运行它时,这是错误的。
[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中获得正确的结果呢?
谢谢。
答案 0 :(得分:7)
在x86-64 Linux上,每个32位arg都在一个单独的64位寄存器中传递(因为这就是x86-64 System V调用约定所需要的)。
调用者碰巧将32位arg零扩展到64位寄存器中。 (这不是必需的;程序中未定义的行为可能会让你遇到一个不同的调用者,它会在arg传递寄存器中留下高垃圾。)
被调用者(average()
)正在寻找三个64位args,并查看调用者放置它们的相同寄存器,因此它恰好起作用。
在32位ARM上,long long
不适合单个寄存器,因此寻找long long
args的被调用者肯定在不同的地方寻找调用者放置了uint32_t
args。
被调用者看到的第一个64位arg可能是((long long)t1<<32) | t2
,反之亦然。但由于被调用者正在寻找6x 32位的args,它将查看调用者根本不打算作为args的寄存器/内存。
(请注意,这可能会导致调用者的本地人在堆栈上损坏,因为被调用者被允许破坏堆栈args。)
有关完整的详细信息,请使用编译器+编译选项查看代码的asm输出,以查看源中C未定义行为的确切行为。 objdump -d ./helloworld
应该做到这一点,或者直接查看编译器输出:How to remove "noise" from GCC/clang assembly output?。
答案 1 :(得分:1)
在我的系统上(x86_64)
#include <stdio.h>
int main(void)
{
printf("%zu\n", sizeof(long long int));
return 0;
}
这打印8,告诉我long long int
是64位宽,我不知道
手臂上long long int
的大小。
无论您的va_arg
电话是否有误,您都必须使用正确的类型
这种情况uint32
,所以你的函数有未定义的行为,碰巧得到
正确的价值观。 average
应如下所示:
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;
}
也不要将您的uint32_t
声明为
typedef unsigned int uint32_t;
这不可移植,因为int
不能保证长4个字节
所有架构。标准C库实际上声明了这种类型
stdint.h
,您应该使用thos类型。
所以你的程序应该是这样的:
#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;
}
这是可移植的,应该在不同的结果中产生相同的结果 架构。