Returning struct containing array

后端 未结 3 1147
温柔的废话
温柔的废话 2020-12-03 02:46

The following simple code segfaults under gcc 4.4.4

#include

typedef struct Foo Foo;
struct Foo {
    char f[25];
};

Foo foo(){
    Foo f =          


        
3条回答
  •  醉话见心
    2020-12-03 03:40

    printf is a bit funny, because it's one of those functions that takes varargs. So let's break it down by writing a helper function bar. We'll return to printf later.

    (I'm using "gcc (Ubuntu 4.4.3-4ubuntu5) 4.4.3")

    void bar(const char *t) {
        printf("bar: %s\n", t);
    }
    

    and calling that instead:

    bar(foo().f); // error: invalid use of non-lvalue array
    

    OK, that gives an error. In C and C++, you are not allowed to pass an array by value. You can work around this limitation by putting the array inside a struct, for example void bar2(Foo f) {...}

    But we're not using that workaround - we're not allowed to pass in the array by value. Now, you might think it should decay to a char*, allowing you to pass the array by reference. But decay only works if the array has an address (i.e. is an lvalue). But temporaries, such as the return values from function, live in a magic land where they don't have an address. Therefore you can't take the address & of a temporary. In short, we're not allowed to take the address of a temporary, and hence it can't decay to a pointer. We are unable to pass it by value (because it's an array), nor by reference (because it's a temporary).

    I found that the following code worked:

    bar(&(foo().f[0]));
    

    but to be honest I think that's suspect. Hasn't this broken the rules I just listed?

    And just to be complete, this works perfectly as it should:

    Foo f = foo();
    bar(f.f);
    

    The variable f is not a temporary and hence we can (implicitly, during decay) takes its address.

    printf, 32-bit versus 64-bit, and weirdness

    I promised to mention printf again. According to the above, it should refuse to pass foo().f to any function (including printf). But printf is funny because it's one of those vararg functions. gcc allowed itself to pass the array by value to the printf.

    When I first compiled and ran the code, it was in 64-bit mode. I didn't see confirmation of my theory until I compiled in 32-bit (-m32 to gcc). Sure enough I got a segfault, as in the original question. (I had been getting some gibberish output, but no segfault, when in 64 bits).

    I implemented my own my_printf (with the vararg nonsense) which printed the actual value of the char * before trying to print the letters pointed at by the char*. I called it like so:

    my_printf("%s\n", f.f);
    my_printf("%s\n", foo().f);
    

    and this is the output I got (code on ideone):

    arg = 0xffc14eb3        // my_printf("%s\n", f.f); // worked fine
    string = Hello, World!
    arg = 0x6c6c6548        // my_printf("%s\n", foo().f); // it's about to crash!
    Segmentation fault
    

    The first pointer value 0xffc14eb3 is correct (it points to the characters "Hello, world!"), but look at the second 0x6c6c6548. That's the ASCII codes for Hell (reverse order - little endianness or something like that). It has copied the array by value into printf and the first four bytes have been interpreted as a 32-bit pointer or integer. This pointer doesn't point anywhere sensible and hence the program crashes when it attempts to access that location.

    I think this is in violation of the standard, simply by virtue of the fact that we're not supposed to be allowed to copy arrays by value.

提交回复
热议问题