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