Home > Software engineering >  Do external variables always need to be volatile when compiled with gcc?
Do external variables always need to be volatile when compiled with gcc?

Time:09-07

I tried to set an external variable and get its value afterwards, but the value I got was not correct. Do external variables always need to be volatile when compiled with gcc?

The code is as follows (updated the complete code, the previous access to the memory address 0x00100000 is changed to the another variable):

main.c

extern unsigned int ex_var;
extern unsigned int another;

int main ()
{
    ex_var = 56326;

    unsigned int *pos=&ex_var 16;

    for (int i = 0; i < 6; i   )
    {
        *pos   = 1;
    }
    another = ex_var;
}

another.c

unsigned int ex_var;  // the address of this variable is set to right before valid_address
unsigned int valid_address[1024];  // to indicate valid memory address
unsigned int another;

And the value set to another is not 56326.

Note: another.c seems to be strange to indicate that the memory region after ex_var is valid. For the actual running example on bear metal, please refer to this post.

Here is the disassembly of main.c. It is compiled with x86-64 gcc 12.2 with -O1 option:

main:
        mov     eax, OFFSET FLAT:ex_var 64
.L2:
        add     rax, 4
        mov     DWORD PTR [rax-4], 1
        cmp     rax, OFFSET FLAT:ex_var 88
        jne     .L2
        mov     eax, DWORD PTR ex_var[rip]
        mov     DWORD PTR another[rip], eax
        mov     eax, 0
        ret

It can be found that the code for setting the external variable ex_var is optimized out.

I tried several versions of gcc, including x86-64 gcc, x86 gcc, arm64 gcc, and arm gcc, and it seems that all tested gcc versions above 8.x have such issue. Note that optimization option -O1 or above is needed to reproduce this issue.

The code can be found at this link at Compiler Explorer.

CodePudding user response:

The calculation &ex_var 16 is not defined by the C standard (because it only defines pointer arithmetic within an object, including to the address just beyond its end) and the assignment *pos = 1 is not defined by the C standard (because, for the purposes of the standard, pos does not point to an object). When there is behavior not defined by the C standard on a code path, the standard does not define any behavior on the code path.

You can make the behavior defined, to the extent the compiler can see, by declaring ex_var as an array of unknown size, so that the address calculation and the assignments would be defined if this translation unit were linked with another that defined ex_var to be an array of sufficient size:

extern unsigned int ex_var[];

int main ()
{
    ex_var[0] = 56326;

    unsigned int *pos = ex_var 16;

    for (int i = 0; i < 6; i   )
    {
        *pos   = 1;
    }
    *(volatile unsigned int*)(0x00100000) = ex_var[0];
}

(Note that *(volatile unsigned int*)(0x00100000) = remains not defined by the C standard, but GCC is intended for some use in bare-metal environments and appears to work with this. Additional compilation switches might be necessary to ensure it is defined for GCC’s purposes.)

This yields assembly that sets ex_var[0] and uses it in the assignment to 0x00100000:

main:
        mov     DWORD PTR ex_var[rip], 56326
…
        mov     eax, DWORD PTR ex_var[rip]
        mov     DWORD PTR ds:1048576, eax
        mov     eax, 0
        ret
  •  Tags:  
  • c gcc
  • Related