1

I know that there are many questions like this, but this question is not about what static and volatile means from C standard's point of view. I'm interested in what is happening a bit lower - on assembly level.

static keyword for variables makes those variables to be visible statically (static storage duration), like global variables. To make it real a compiler should write those variables to .bss section or somewhere else? Also, static keyword prevents the variable/function to be used outside the file, does it happen only during compilation or there are some runtime-checks?

volatile keyword for variables makes those variables to be read from memory to make sure that if something else (like Peripheral Devices) wants to modify that variable it'll see exactly the value from that memory. Here, what does exactly mean "to be read from memory"? What is memory location used? .bss, .data, or something else?

halfer
  • 19,824
  • 17
  • 99
  • 186
k1r1t0
  • 465
  • 2
  • 11
  • 1
    `static` variables can go into `.data` if they are initialized non-zero. Can technically also go into `.rodata` or even `.text` if they are read only. `volatile` is not special regarding sections. The compiler just generates code to always use the memory instead of propagating via register or eliminating altogether. As for static (internal) linkage, that's usually reversed: externally visible symbols need to be marked global in assembly. – Jester Dec 14 '22 at 14:47
  • >volatile is not special regarding sections. Let's say I use a stack and initialize a variable with `volatile` keyword in function, how will it happen? Will it use the stack memory address or something else? – k1r1t0 Dec 14 '22 at 14:49
  • 1
    Yes it will use the stack. `volatile` has no effect on where the variable is stored. – Jester Dec 14 '22 at 14:56
  • @k1r1t0 The same kind of memory as for any other object of this storage class will be used. For automatic variables, this is indeed usually the stack. – fuz Dec 14 '22 at 14:57
  • So, if it's a global variable the memory location is usually is `.data` or `.bss` depending weather a variable is intialilized or not. And for automatic storage duration (e.g. for use case within function) it will use the stack, right? – k1r1t0 Dec 14 '22 at 14:59
  • For previous comment I'm talking only about `volatile` – k1r1t0 Dec 14 '22 at 15:00
  • @k1r1t0 Whether the variable is global or not (i.e. whether it has external or internal linkage) is irrelevant. The same kind of storage will be used as for non-volatile-qualified variables. For variables in static storage class (regardless of linkage), that is usually the data or bss segment. For variables in automatic storage class, it is usually the stack. Objects of other storage classes (such as dynamic or thread-local storage) may use other kinds of storage. – fuz Dec 14 '22 at 15:03
  • `volatile` is usually for write-only memory-mapped ports. C will optimize out code that has no effect on your function's return value. Which is a problem for interfacing with hardware, since you often have to write to memory that you'll never read from, just to make something happen on screen. So, if you have a pointer variable marked as `volatile` then C won't skip any writes to that memory. – puppydrum64 Jan 06 '23 at 12:55

2 Answers2

8

The static keyword has two meanings: (a) it conveys static storage class and (b) it conveys internal linkage. These two meanings have to be strictly distinguished.

An object having static storage class means that it is allocated at the start of the program and lives until the end of the program. This is usually achieved by placing the object into the data segment (for initialised objects) or into the bss segment (for uninitialised objects). Details may vary depending on the toolchain in question.

An identifier having internal linkage means that each identifier in the same translation unit with the same name and some linkage (i.e. the linkage is not “none”) refers to the same object as that identifier. This is usually realised by not making the symbol corresponding to the identifier a global symbol. The linker will then not recognise references of the same symbol from different translation units as referring to the same symbol.

The volatile keyword indicates that all operations performed on the volatile-qualified object in the abstract machine must be performed in the code generated. The compiler is not permitted to perform any optimisations that would discard any such operations performed on the volatile-qualified object as it usually would for non-volatile-qualified objects.

This keyword is purely an instruction to the compiler to suppress certain optimisations. It does not affect the storage class of the objects qualified such. See also my previous answer on this topic.

fuz
  • 88,405
  • 25
  • 200
  • 352
  • >The compiler is not permitted to perform any optimisations... What a kind of optimisations are you talking about? I know that compiler may use a register to optimize the speed of memory access, but for `volatile` it doesn't, so what will it do then? If `volatile` discards this optimisation then what's happening, what memory location compiler may use? – k1r1t0 Dec 14 '22 at 14:57
  • 2
    @k1r1t0 For example, if you have code like `int i; i = 0; i = 1;` the compiler may discover that the statement `i = 0;` has no effect and may discard it. It is not permitted to do so for volatile objects. The kind of memory used is the same as for a not-volatile-qualified variable of the same storage class. For automatic variables, that is usually the stack. – fuz Dec 14 '22 at 14:59
  • @k1r1t0: It also means `volatile int i;` can't be optimized into a register and thus not have an address; C variables always have an address in the abstract machine, even if the address is never taken. https://godbolt.org/z/cv4z9ejaj shows an example of using it on a loop counter, forcing store/reload and defeating auto-vectorization. (i.e. an example of how *not* to use `volatile` for benchmarking if you just want to stop a loop optimizing away without destroying performance.) – Peter Cordes Dec 14 '22 at 16:01
  • @PeterCordes Unless the variable is `register` qualified. – fuz Dec 14 '22 at 16:05
  • @fuz: I was trying to remember whether the C standard says that `register` just stops you from *taking* the address, allowing even simplistic compilers to make the "as-if" optimization, or whether it actually says such a variable really doesn't have an address in the abstract machine. I decided just to not mention it, but I suspect the former so they don't have to make special rules about layout and object representation. – Peter Cordes Dec 14 '22 at 16:15
  • @PeterCordes It's a bit fuzzy on that. The intent seems to be that `register` qualified variables do not have addresses. – fuz Dec 14 '22 at 16:16
  • `volatile` means that the object is side-effects prone. – 0___________ Dec 14 '22 at 16:44
  • @PeterCordes you can have `register volatile` object – 0___________ Dec 14 '22 at 16:48
  • @0___________: good point, I was thinking you couldn't. That's not definitive, though; it could just prevent you from taking the address without actually stopping it from having one in the abstract machine. In practice, GCC and clang treat `register volatile` the same as `volatile`, giving it a memory address in the target machine, unfortunately not just forcing the value to be materialized in a register, but that tells us nothing about the abstract machine. https://godbolt.org/z/Thjd98bMe – Peter Cordes Dec 14 '22 at 17:04
  • @PeterCordes using extension you can https://godbolt.org/z/6TKafq13T, but the volatile does not do anything in this case as register can't be side effects prone. – 0___________ Dec 14 '22 at 17:15
  • Some memory mapped hardware requires you to simply access its memory location in order to operate it (the value at the address is irrelevant). The C compiler doesn't understand this concept and is likely to optimize out the operation, so it's usually necessary to make that action `volatile` – puppydrum64 Dec 15 '22 at 13:02
2

You can also try it and see.

C code:

static unsigned int x;
unsigned int y;
unsigned int z = 1;
static volatile unsigned int j;
static volatile const unsigned int k = 11;

void fun ( void )
{
    x = 5;
    y = 7;
    z ++ ;
    j+=2;
}

Assembler:

mov ip, #7
ldr r3, .L3
ldr r0, .L3+4
ldr r2, [r3, #4]
ldr r1, [r0]
add r2, r2, #2
add r1, r1, #1
str r1, [r0]
str r2, [r3, #4]
str ip, [r3]
bx  lr


    .global z
    .global y
    .data
    .align  2
    .set    .LANCHOR1,. + 0
    .type   z, %object
    .size   z, 4
z:
    .word   1
    .type   k, %object
    .size   k, 4
k:
    .word   11
    .bss
    .align  2
    .set    .LANCHOR0,. + 0
    .type   y, %object
    .size   y, 4
y:
    .space  4
    .type   j, %object
    .size   j, 4
j:
    .space  4

x was not expected to survive in an example like this and in any file it will maybe land in .bss since I did not put an initial value.

y is .bss as expected

z is .data as expected

volatile prevents j from being optimized out despite it being dead code/variable.

k could have ended up in .rodata but looks like .data here.

You guys are using fancy words but static in C just means it is limited in scope limited to that function or file. Global, local, initialized or not, const or not, can affect if it is .data, .bss, or .rodata (could even land in .text instead of .rodata if you play the alphabet game with the (rwx) stuff in the linker script (suggestion: never use those).

volatile is implied to mean some flavors of do not optimize out this variable/operation, do it in this order do not move it outside the loop, etc. You can find discussions about how it is not what you think it is and we have seen on this site that llvm/clang and gnu/gcc have a different opinion on what volatile actually means (when used to describe a pointer that is intended to access a control or status register in a peripheral, based on some arguments as what volatile was invented for (not for sharing variables between interrupts and foreground code)).

Like static volatile does not imply what segment it is in can even be used with asm volatile (stuff); to tell the compiler I do not want you to move this code around I want it to happen right here in this order. (which is an aspect of using it on a variable, or so we believe).

static unsigned int x;
void fun ( void )
{
    x = 5;
}

Disassembly of section .text:

00000000 <fun>:
   0:   e12fff1e    bx  lr

no .rodata, .data, nor .bss optimized away.

but

static unsigned int x;
void fun ( void )
{
    x += 5;
}

Disassembly of section .text:

00000000 <fun>:
   0:   e59f200c    ldr r2, [pc, #12]   ; 14 <fun+0x14>
   4:   e5923000    ldr r3, [r2]
   8:   e2833005    add r3, r3, #5
   c:   e5823000    str r3, [r2]
  10:   e12fff1e    bx  lr
  14:   00000000    andeq   r0, r0, r0

Disassembly of section .bss:

00000000 <x>:
   0:   00000000    andeq   r0, r0, r0

How fun is that, ewww... let's not optimize out the dead code, let's put it in there. It is not global, nobody else can see it...

fun.c

static unsigned int x;
void fun ( void )
{
    x += 5;
}

so.c

static unsigned int x;
void more_fun ( void )
{
    x += 3;
}

linked

Disassembly of section .text:

00008000 <more_fun>:
    8000:   e59f200c    ldr r2, [pc, #12]   ; 8014 <more_fun+0x14>
    8004:   e5923000    ldr r3, [r2]
    8008:   e2833003    add r3, r3, #3
    800c:   e5823000    str r3, [r2]
    8010:   e12fff1e    bx  lr
    8014:   00018030    andeq   r8, r1, r0, lsr r0

00008018 <fun>:
    8018:   e59f200c    ldr r2, [pc, #12]   ; 802c <fun+0x14>
    801c:   e5923000    ldr r3, [r2]
    8020:   e2833005    add r3, r3, #5
    8024:   e5823000    str r3, [r2]
    8028:   e12fff1e    bx  lr
    802c:   00018034    andeq   r8, r1, r4, lsr r0

Disassembly of section .bss:

00018030 <x>:
   18030:   00000000    andeq   r0, r0, r0

00018034 <x>:
   18034:   00000000    andeq   r0, r0, r0

each x is static so as expected there are two of them... well expectations are they are optimized out but...

and they are .bss as expected since I did not initialize them.

and on that note

static unsigned int x=3;
void fun ( void )
{
    x += 5;
}

Disassembly of section .text:

00000000 <fun>:
   0:   e59f200c    ldr r2, [pc, #12]   ; 14 <fun+0x14>
   4:   e5923000    ldr r3, [r2]
   8:   e2833005    add r3, r3, #5
   c:   e5823000    str r3, [r2]
  10:   e12fff1e    bx  lr
  14:   00000000    andeq   r0, r0, r0

Disassembly of section .data:

00000000 <x>:
   0:   00000003    andeq   r0, r0, r3



static const unsigned int x=3;
unsigned int fun ( void )
{
    return(x);
}

Disassembly of section .text:

00000000 <fun>:
   0:   e3a00003    mov r0, #3
   4:   e12fff1e    bx  lr

static const unsigned int x=3;
const unsigned int y=5;
unsigned int fun ( void )
{
    return(x+y);
}

Disassembly of section .text:

00000000 <fun>:
   0:   e3a00008    mov r0, #8
   4:   e12fff1e    bx  lr

Disassembly of section .rodata:

00000000 <y>:
   0:   00000005    andeq   r0, r0, r5

Okay I finally got a .rodata.

static const unsigned int x=3;
volatile const unsigned int y=5;
unsigned int fun ( void )
{
    return(x+y);
}

Disassembly of section .text:

00000000 <fun>:
   0:   e59f3008    ldr r3, [pc, #8]    ; 10 <fun+0x10>
   4:   e5930000    ldr r0, [r3]
   8:   e2800003    add r0, r0, #3
   c:   e12fff1e    bx  lr
  10:   00000000    andeq   r0, r0, r0

Disassembly of section .data:

00000000 <y>:
   0:   00000005    andeq   r0, r0, r5

There is only so much you can do with words and their (perceived) definitions, the topic as I understand it is C vs (generated) asm. At some point you should actually try it and you can see how trivial it was, do not need to write elaborate code. gcc, objdump and sometimes ld. Hmm I just noticed y moved to .data from .rodata in that case... That is interesting.

And this just try it will test the compiler and other tool authors interpretation. Things like what does register mean what does volatile mean, etc (and to find that it is subject to different interpretations like so much of the C language (implementation defined)). It is important sometimes to know what your favorite/specific compilers interpretation of the language is, but be very mindful of actual implementation defined things (bitfields, unions, how structs are constructed (packing them causes as many problems as it solves) and so on)...

Go to the spec read whatever definition, then go to your compiler and see how they interpreted it, then go back to the spec and see if you can figure it out.

As far as static goes essentially means scope, stays within the function or file (well compile domain for a single compile operation). and volatile implies please do this in this order and please do not optimize out this item and/or its operations. in both cases it is what you used them with that determines where they are .text, .data, .bss, .rodata, etc.

halfer
  • 19,824
  • 17
  • 99
  • 186
old_timer
  • 69,149
  • 8
  • 89
  • 168