为什么 va_arg() 对 x86_64 和 arm 产生不同的影响? [英] Why va_arg() produce different effects on x86_64 and arm?

查看:44
本文介绍了为什么 va_arg() 对 x86_64 和 arm 产生不同的影响?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

代码:

#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) 中运行时,没问题.

When I run in ubuntu (x86_64), It's Ok.

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) 中运行它时,它是错误的.

But when I cross-compiler and run it in openwrt(ARM 32bit), It's wrong.

[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 上得到正确的结果?

I know do not call va_arg with an argument of the incorrect type. But Why we can get right result in x86_64 not in arm?

谢谢.

推荐答案

在 x86-64 Linux 上,每个 32 位 arg 在单独的 64 位寄存器中传递(因为 x86-64 System V 调用约定需要).

On x86-64 Linux, each 32-bit arg is passed in a separate 64-bit register (because that's what the x86-64 System V calling convention requires).

调用者碰巧将 32 位 arg 零扩展到 64 位寄存器中.(这不是必需的;您程序中未定义的行为可能会用不同的调用程序咬您,这会在 arg 传递寄存器中留下大量垃圾.)

The caller happens to have zero-extended the 32-bit arg into the 64-bit register. (This isn't required; the undefined behaviour in your program could bite you with a different caller that left high garbage in the arg-passing registers.)

被调用者 (average()) 正在寻找三个 64 位 args,并在调用者放置它们的相同寄存器中查找,因此它恰好可以工作.

The callee (average()) is looking for three 64-bit args, and looks in the same registers where the caller put them, so it happens to work.

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

On 32-bit ARM, long long is doesn't fit in a single register, so the callee looking for long long args is definitely looking in different places than where the caller placed uint32_t args.

被调用者看到的第一个 64 位 arg 可能是 ((long long)t1<<32) |t2,或者反过来.但是由于被调用者正在寻找 6x 32 位的 args,它将查看调用者根本不打算作为 args 的寄存器/内存.

The first 64-bit arg the callee sees is probably ((long long)t1<<32) | t2, or the other way around. But since the callee is looking for 6x 32 bits of args, it will be looking at registers / memory that the caller didn't intend as args at all.

(请注意,这可能会导致调用者在堆栈上的局部变量损坏,因为被调用者可以破坏堆栈参数.)

(Note that this could cause corruption of the caller's locals on the stack, because the callee is allowed to clobber stack args.)

有关完整的详细信息,请使用编译器 + 编译选项查看代码的 asm 输出,以了解源代码中的 C 未定义行为究竟导致了什么行为.objdump -d ./helloworld 应该可以解决问题,或者直接查看编译器输出:如何去除噪音";来自 GCC/clang 程序集输出?.

For the full details, look at the asm output of your code with your compiler + compile options to see what exactly what behaviour resulted from the C Undefined Behaviour in your source. objdump -d ./helloworld should do the trick, or look at compiler output directly: How to remove "noise" from GCC/clang assembly output?.

这篇关于为什么 va_arg() 对 x86_64 和 arm 产生不同的影响?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆