3
votes

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.

1

1 Answers

4
votes

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.