gccarmembeddedstm32bare-metal

__attribute__((noinit)) vs __attribute__((section(".noinit")))


I am trying to understand the difference between __attribute__((noinit)) and __attribute__((section(".noinit"))) in GCC.

I know that __attribute__((section(".noinit"))) will place the variable in the .noinit section defined in a linker script.

However, I'm unsure if __attribute__((noinit)) does the same thing, or if it just tells the program not to initialize the specific variable, without placing it in the .noinit section.

I'm asking because I'd like to have certain variables in their own section which are not initialized. E.g in my application code I would like to declare the following:

// "mySection" is defined in the linker script with NOLOAD.
`uint32_t myVariable __attribute__((section(".mySection"), noinit));

This compiles with a warning warning: ignoring attribute 'noinit' because it conflicts with attribute 'section' [-Wattributes], and in the object dump I can tell that the variable is placed in .mySection and not .noinit

However, the below does not compile due to conflicting section placements:

uint32_t myVariable __attribute__((section(".noinit"), (section(".mySection")));

Results in: error: section of 'mySection' conflicts with previous declaration

So I am unsure how __attribute__((noinit)) and __attribute__((section(".noinit"))) vary.

I am using GCC 13.3.1 (arm-none-eabi-gcc)


Solution

  • I have:

    $ arm-none-eabi-gcc --version | head -n1
    arm-none-eabi-gcc (15:13.2.rel1-2) 13.2.1 20231009
    

    The difference in diagnostics that you see here:

    $ cat foobar.c 
    int foo __attribute__((section(".mysect"),noinit));
    int bar __attribute__((section(".noinit"), section(".mySection")));
    
    $ arm-none-eabi-gcc -c foobar.c 
    foobar.c:1:1: warning: ignoring attribute 'noinit' because it conflicts with attribute 'section' [-Wattributes]
        1 | int foo __attribute__((section(".mysect"),noinit));
          | ^~~
    foobar.c:2:5: error: section of 'bar' conflicts with previous declaration
        2 | int bar __attribute__((section(".noinit"), section(".mySection")));
          |     ^~~
          
    

    comes from the fact that in:

    __attribute__((section(".noinit"), section(".mySection")));
    

    you are giving the compiler flatly contradictary instructions: Put it in section.noinit and put it in section .mySection. It can't do both and you can't fail to see that. Hence error. Whereas:

    __attribute__((section(".mysect"),noinit));
    

    implies

    __attribute__((section(".mysect"), section(".noinit")));
    

    but you may not know or assume that - as in fact you didn't - because the implication is an implementation detail. So the compiler cuts you slack. You haven't demanded anything impossible per spec or simply impossible at face value. It tells you:

    warning: ignoring attribute 'noinit' because it conflicts with attribute 'section'
    

    and gives you an object file the same as per __attribute__((section(".mysect"))).

    If you swap the order of foo's attributes:

    $ cat foo.c
    int foo __attribute__((noinit,section(".mysect")));
    
    $ arm-none-eabi-gcc -c foo.c 
    foo.c:1:1: warning: ignoring attribute 'section' because it conflicts with attribute 'noinit' [-Wattributes]
        1 | int foo __attribute__((noinit,section(".mysect")));
          | ^~~
          
    

    It still cuts you slack and you see that the way it cuts you slack is to accept the first of the implicitly conficting attributes and ignore the second.

    In fact:

    __attribute__((noinit));
    

    is semantically equivalent for the compiler to:

    __attribute__((section(".noinit")))
    

    But this equivalence is an implementation detail of __attribute__((noinit)), just as:

    __attribute__((constructor(101)))
    void foo(){...};
    

    is in fact semantically equivalent to:

    __attribute__((section(".init_array.00101")))
    

    as applied to &foo, and that's an implementation detail.

    Semantic equivalence here means having the same effect in the object code. You can observe that with:

    $ cat raboof.c
    int rab __attribute__((section(".noinit")));
    int oof __attribute__((noinit));
    
    $ arm-none-eabi-gcc -c raboof.c 
    $ readelf -sW raboof.o | egrep \(rab\|oof\) 
         1: 00000000     0 FILE    LOCAL  DEFAULT  ABS raboof.c
         9: 00000000     4 OBJECT  GLOBAL DEFAULT    4 rab
        10: 00000004     4 OBJECT  GLOBAL DEFAULT    4 oof
        
    

    oof follows 4 bytes after rab in section 4, which is:

    $ readelf -SW raboof.o | grep 4] 
      [ 4] .noinit           NOBITS          00000000 000034 000008 00  WA  0   0  4
      
    

    And likewise:

    $ cat oofrab.c 
    int oof __attribute__((noinit));
    int rab __attribute__((section(".noinit")));
    
    $ arm-none-eabi-gcc -c oofrab.c 
    $ readelf -sW oofrab.o | egrep \(rab\|oof\) 
         1: 00000000     0 FILE    LOCAL  DEFAULT  ABS oofrab.c
         9: 00000000     4 OBJECT  GLOBAL DEFAULT    4 oof
        10: 00000004     4 OBJECT  GLOBAL DEFAULT    4 rab
        
    

    rab follows 4 bytes after oof in section 4 (.noinit)