Why va_arg() produce different effects on x86_64 and arm?

人走茶凉 提交于 2020-01-06 04:34:29

问题


The codes:

#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;
}

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$ 

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

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?

Thank you.


回答1:


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).

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.)

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.


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.

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.)


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?.




回答2:


On my system (x86_64)

#include <stdio.h>


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

    return 0;
}

this prints 8, which tells me that long long int is 64bits wide, I don't know the size of a long long int on arm.

Regardless your va_arg call is wrong, you have to use the correct type, in this case uint32, so your function has undefined behaviour and happens to get the correct values. average should look like this:

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;
}

Also don't declare your uint32_t as

typedef unsigned int uint32_t;

this is not portable, because int is not guaranteed to be 4 bytes long across all architectures. The Standard C Library actually declares this type in stdint.h, you should use the thos types instead.

So you program should look like this:

#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;
}

this is portable and should yield the same results across different architectures.



来源:https://stackoverflow.com/questions/49041919/why-va-arg-produce-different-effects-on-x86-64-and-arm

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!