GCC -O2 and __attribute__((weak))
Asked Answered
M

2

5

It looks like GCC with -O2 and __attribute__((weak)) produces different results depending on how you reference your weak symbols. Consider this:

$ cat weak.c

#include <stdio.h>

extern const int weaksym1;
const int weaksym1 __attribute__(( weak )) = 0;

extern const int weaksym2;
const int weaksym2 __attribute__(( weak )) = 0;

extern int weaksym3;
int weaksym3 __attribute__(( weak )) = 0;

void testweak(void)
{
    if ( weaksym1 == 0 )
    {
        printf( "0\n" );
    }
    else
    {
        printf( "1\n" );
    }

    printf( "%d\n", weaksym2 );


    if ( weaksym3 == 0 )
    {
        printf( "0\n" );
    }
    else
    {
        printf( "1\n" );
    }
}

$ cat test.c

extern const int weaksym1;
const int weaksym1 = 1;

extern const int weaksym2;
const int weaksym2 = 1;

extern int weaksym3;
int weaksym3 = 1;

extern void testweak(void);

void main(void)
{
    testweak();
}

$ make

gcc  -c weak.c
gcc  -c test.c
gcc  -o test test.o weak.o

$ ./test

1
1
1

$ make ADD_FLAGS="-O2"

gcc -O2 -c weak.c
gcc -O2 -c test.c
gcc -O2 -o test test.o weak.o

$ ./test

0
1
1

The question is, why the last "./test" produces "0 1 1", not "1 1 1"?

gcc version 5.4.0 (GCC)

Mortgagor answered 3/4, 2017 at 5:41 Comment(3)
The second run would have printed "0 0 0" in that case.Mortgagor
The gcc manual gives almost no information about what weak attribute does for variablesTrophoblast
@user3234859: You should not initialize the weak variables, they will be "initialized" to zero if there is no non-weak version of the symbol. That's what is throwing the compiler off. See man 1 nm, the description for "V". If you remove the zero initializations, you'll see that your code will perform predictably regardless of optimizations or type/method of reference.Timon
H
3

Looks like when doing optimizations, the compiler is having trouble with symbols declared const and having the weak definition within the same compilation unit.

You can create a separate c file and move the const weak definitions there, it will work around the problem:

weak_def.c

const int weaksym1 __attribute__(( weak )) = 0;
const int weaksym2 __attribute__(( weak )) = 0;

Same issue described in this question: GCC weak attribute on constant variables

Haggar answered 3/4, 2017 at 7:11 Comment(6)
Thanks for the link, however, it can be seen that the issue happens with -O2 only (and I think that was their case, too, just missed, probably), and, in my sample code, of course, weaksym3 was intentionally declared non-constMortgagor
In fact, it's like this with any optimization level.Mortgagor
@nnn: You should not initialize weak variables at all. They will be zero if no non-weak symbol with the same name is linked in the binary. Initializing them to zero confuses the compiler, because the compiler has no understanding of weak symbol behaviour; the magic happens at (dynamic) link time.Timon
OK, moving weak variables to a separate unit effectively disables their optimization regardless whether they are initialized or not and allows to keep const - which is what I need (I wonder if this is true for weak functions?).Mortgagor
The initialization to zero here is not required, but you might want in specific cases the weak default to be some non-zero value.Haggar
@nnn: Initialization of a weak variable to a nonzero variable must be done in a separate compilation unit to any accesses to it, because otherwise the compiler may make assumptions about its value. (This can only be avoided by omitting the initialization altogether, and rely on the linker to set it to zero.) This is the very root of OP's problem.Timon
T
4

Summary:

Weak symbols only work correctly if you do not initialize them to a value. The linker takes care of the initialization (and it always initializes them to zero if no normal symbol of the same name exists).

If you try to initialize a weak symbol to any value, even to zero as OP did, the C compiler is free to make weird assumptions about its value. The compiler has no distinction between weak and normal symbols; it is all (dynamic) linker magic.

To fix, remove the initialization (= 0) from any symbol you declare weak:

extern const int weaksym1;
const int weaksym1 __attribute__((__weak__));

extern const int weaksym2;
const int weaksym2 __attribute__((__weak__));

extern int weaksym3;
int weaksym3 __attribute__((__weak__));

Detailed description:

The C language has no concept of a "weak symbol". It is a functionality provided by the ELF file format, and (dynamic) linkers that use the ELF file format.

As the man 1 nm man page describes at the "V" section,

When a weak defined symbol is linked with a normal defined symbol, the normal defined symbol is used with no error. When a weak undefined symbol is linked and the symbol is not defined, the value of the weak symbol becomes zero with no error.

the weak symbol declaration should not be initialized to any value, because it will have value zero if the process is not linked with a normal symbol of the same name. ("defined" in the man 1 nm page refers to a symbol existing in the ELF symbol table.)

The "weak symbol" feature was designed to work with existing C compilers. Remember, the C compilers do not have any distinction between "weak" and "normal" symbols.

To ensure this would work without running afoul of the C compiler behaviour, the "weak" symbol must be uninitialized, so that the C compiler cannot make any assumptions as to its value. Instead, it will generate code that obtains the address of the symbol as usual -- and that's where the normal/weak symbol lookup magic happens.

This also means that weak symbols can only be "auto-initialized" to zero, and not any other value, unless "overridden" by a normal, initialized symbol of the same name.

Timon answered 3/4, 2017 at 8:7 Comment(1)
what i'm seeing is that the address of the weak symbol becomes 0, not its value. which is also seen here: #40756826Chaoan
H
3

Looks like when doing optimizations, the compiler is having trouble with symbols declared const and having the weak definition within the same compilation unit.

You can create a separate c file and move the const weak definitions there, it will work around the problem:

weak_def.c

const int weaksym1 __attribute__(( weak )) = 0;
const int weaksym2 __attribute__(( weak )) = 0;

Same issue described in this question: GCC weak attribute on constant variables

Haggar answered 3/4, 2017 at 7:11 Comment(6)
Thanks for the link, however, it can be seen that the issue happens with -O2 only (and I think that was their case, too, just missed, probably), and, in my sample code, of course, weaksym3 was intentionally declared non-constMortgagor
In fact, it's like this with any optimization level.Mortgagor
@nnn: You should not initialize weak variables at all. They will be zero if no non-weak symbol with the same name is linked in the binary. Initializing them to zero confuses the compiler, because the compiler has no understanding of weak symbol behaviour; the magic happens at (dynamic) link time.Timon
OK, moving weak variables to a separate unit effectively disables their optimization regardless whether they are initialized or not and allows to keep const - which is what I need (I wonder if this is true for weak functions?).Mortgagor
The initialization to zero here is not required, but you might want in specific cases the weak default to be some non-zero value.Haggar
@nnn: Initialization of a weak variable to a nonzero variable must be done in a separate compilation unit to any accesses to it, because otherwise the compiler may make assumptions about its value. (This can only be avoided by omitting the initialization altogether, and rely on the linker to set it to zero.) This is the very root of OP's problem.Timon

© 2022 - 2024 — McMap. All rights reserved.