Search code examples
cstandardsundefined-behaviorcompound-literalsstorage-duration

Return compound literal


Look at this code. I return an address of the compound literal here.

#include <stdio.h>

#define FOO(bar) ((bar)->a + (bar)->b)

struct bar {
    int a;
    int b;
};

static struct bar * to_bar(int a, int b);

int main(void)
{
    int baz = FOO((struct bar *) {to_bar(1, 2)});

    printf("%d\n", baz);

    return 0;
}

static struct bar *
to_bar(int a, int b)
{
    return &(struct bar) {a, b};
}

Output:

3

ISO/IEC 9899 says:

If the compound literal occurs outside the body of a function, the object has static storage duration; otherwise, it has automatic storage duration associated with the enclosing block.

I. e., in the to_bar function the unnamed object, created by the compound literal has automatic storage duration. Thereby, it will be destroyed outside scope of to_bar. It seems, this code produces undefined behaviour (based on the standard). Is it so?


Solution

  • You are right. In your example, you immediately retrieved the fields after returning from to_bar, so you didn't have time to corrupt the stack frame of the deceased to_bar function. But here's another example:

    struct bar {
        int a;
        int b;
    };
    
    static struct bar * to_bar(int a, int b);
    
    int main(void)
    {
    
        struct bar * corrupted_bar = to_bar(1, 2);
    
        printf("this print will corrupt\n");
    
        int baz = corrupted_bar->a + corrupted_bar->b;
    
        printf("baz = %d\n", baz);
    
        return 0;
    }
    
    static struct bar *
    to_bar(int a, int b)
    {
        return &(struct bar) {a, b};
    }
    

    which when executed

    this print will corrupt
    baz = -59543507
    

    If you look at the assembly

    .LC0:
            .string "this print will corrupt"
    .LC1:
            .string "baz = %d\n"
    main:
            push    rbp
            mov     rbp, rsp
            sub     rsp, 16
            mov     esi, 2
            mov     edi, 1
            call    to_bar                    ; call to_bar
            mov     QWORD PTR [rbp-8], rax    ; save address returned to a local pointer
            mov     edi, OFFSET FLAT:.LC0     ; argument into puts()
            call    puts                      ; call puts(), which creates its own local variables that corrupts the bar struct
            mov     rax, QWORD PTR [rbp-8]    
            mov     edx, DWORD PTR [rax]
            mov     rax, QWORD PTR [rbp-8]
            mov     eax, DWORD PTR [rax+4]
            add     eax, edx
            mov     DWORD PTR [rbp-12], eax
            mov     eax, DWORD PTR [rbp-12]
            mov     esi, eax
            mov     edi, OFFSET FLAT:.LC1
            mov     eax, 0
            call    printf
            mov     eax, 0
            leave
            ret
    to_bar:
            push    rbp
            mov     rbp, rsp
            mov     DWORD PTR [rbp-20], edi
            mov     DWORD PTR [rbp-24], esi
            mov     eax, DWORD PTR [rbp-20]
            mov     DWORD PTR [rbp-8], eax     ; field 'a' gets stored, notice dest addr rbp-8 is in the stack frame of this function (local variable)
            mov     eax, DWORD PTR [rbp-24]
            mov     DWORD PTR [rbp-4], eax     ; field 'b' gets stored, same as above
            lea     rax, [rbp-8]
            pop     rbp
            ret