Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

__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)

like image 699
bsheikh24 Avatar asked Sep 18 '26 22:09

bsheikh24


2 Answers

As told by the gcc manual:

noinit

Any data with the noinit attribute will not be initialized by the C runtime startup code, or the program loader. Not initializing data in this way can reduce program startup times.

This attribute is specific to ELF targets and relies on the linker script to place sections with the .noinit prefix in the right location.

  • __attribute__((noinit)) tells the compiler that this shouldn't be initialized and additionally the linker needs to place the variable in a special .noinit memory section.

  • __attribute__((section(".noinit"))) simply places the variable in the .noinit section.

This will likely give the same result, depending on how the "CRT" (C runtime) start-up code looks like - something that isn't handled by the gcc compiler internally, but rather by the specific compiler port. Sometimes the CRT is attached to the standard lib, sometimes it isn't.

Most (all?) CRTs perform various actions solely based on where a variable is stored, Normally it would go like compiler -> linker -> CRT. For example all variables at file scope will get zero-initialized and end up in .bss:

  • the compiler marks a variable as "zero init" in the generated object file.
  • the linker will place that variable inside the .bss section.
  • the CRT will perform zero initialization on everything allocated in the .bss section.

As long as it is carried out like above, then there won't be any difference between the two mentioned attributes.

In theory however, I suppose the CRT code could as well be generated for each executable based on compiler output. If that would happen then __attribute__((noinit)) might hold a different meaning than __attribute__((section.... But I have never seen a toolchain like that, gcc-like (ELF) or otherwise. More likely, it might not even have a noinit section by default and will need to be created or enabled through the linker script or some other setting.

like image 95
Lundin Avatar answered Sep 21 '26 07:09

Lundin


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 of __attribute__((constructor(...))).

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)

like image 31
Mike Kinghan Avatar answered Sep 21 '26 07:09

Mike Kinghan



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!