Search code examples
c++arraysstaticavrvolatile

C++ static array initiation: does inline initialization reserve space?


In C++, are the following two declarations equivalent?

static volatile uint16_t *ADCReadings = (uint16_t[64]){0};

or

static volatile uint16_t ADCReadings[64] = {0};

I'm reserving space for a buffer used inside and outside an ISR on AVR. But, though it would be interesting, I'm not looking here to know if this is the best way to do it - I'm wondering what the differences (if any) between the two declarations are, so that I can understand this better.

I do know that the two different declarations produce different sized binaries, so the compiler appears to be treating them differently.


Solution

  • Are the following two declarations equivalent?

    No. (uint16_t[64]){0} is a temporary array (a compound literal*), and if you try to compile the first line, you'll get a self-explanatory warning/error:

    warning: temporary whose address is used as value of local variable 'ADCReadings' will be destroyed at the end of the full-expression (Clang)

    error: taking address of temporary array (Gcc)

    So, in the first line ADCReadings becomes a dangling pointer. Using it as a pointer into a buffer invokes undefined behaviour.

    Gcc manual reads:

    In C, a compound literal designates an unnamed object with static or automatic storage duration. In C++, a compound literal designates a temporary object that only lives until the end of its full-expression. As a result, well-defined C code that takes the address of a subobject of a compound literal can be undefined in C++, so G++ rejects the conversion of a temporary array to a pointer.


    Is this true even at global scope?

    Gcc and Clang don't produce warnings/errors in this case. Gcc manual further reads:

    As an optimization, G++ sometimes gives array compound literals longer lifetimes: when the array either appears outside a function or has a const-qualified type. ... if foo were a global variable, the array would have static storage duration. But it is probably safest just to avoid the use of array compound literals in C++ code.

    So it seems that at the global scope ADCReadings won't be dangling.

    I do know that the two different declarations produce different sized binaries

    In the first case, the compound literal array goes into the .bss section, and ADCReadings goes into the .data section:

    0000000000004040 l     O .bss   0000000000000080     ._0
    0000000000004010 g     O .data  0000000000000008     ADCReadings
    

    ADCReadings is a pointer to ._0.

    In the second case, ADCReadings goes into the .bss directly:

    0000000000004040 g     O .bss   0000000000000080     ADCReadings
    

    This also translates into how ADCReadings is used. The following simple function:

    uint16_t* foo() {
        return ADCReadings;
    }
    

    compiles into:

    push   rbp
    mov    rbp,rsp
    mov    rax,QWORD PTR [rip+0x2ed8]        # 4010 <ADCReadings>
    pop    rbp
    ret    
    

    and

    push   rbp
    mov    rbp,rsp
    lea    rax,[rip+0x2f08]        # 4040 <ADCReadings>
    pop    rbp
    ret    
    

    * Compound literals are not part of the standard C++, some compilers (Gcc, Clang) allow them as an extension.