GCC-O2 и атрибут ((слабый))


Похоже, что GCC с -O2 и __attribute__((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( "0n" );
    }
    else
    {
        printf( "1n" );
    }

    printf( "%dn", weaksym2 );


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

$ кошачий тест.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

$ ./ испытание

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

$ ./ испытание

0
1
1
Вопрос в том, почему последний"./ тест " производит "0 1 1", а не"1 1 1"?

GCC version 5.4.0 (GCC)

2 4

2 ответа:

Похоже, что при выполнении оптимизации компилятор испытывает проблемы с символами, объявленными const и имеющими определение weak в одном и том же блоке компиляции.

Вы можете создать отдельный файл c и переместить туда определения const weak, это позволит обойти проблему:

Weak_def.c

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

Та же проблема, описанная в этом вопросе: слабый атрибут GCC на постоянных переменных

Резюме:

Слабые символы работают корректно только в том случае, если вы не инициализируете их значением. Компоновщик заботится об инициализации (и он всегда инициализирует их до нуля, если нет нормального символа с тем же именем).

Если вы попытаетесь инициализировать слабый символ до любого значения, даже до нуля, как это сделал OP, компилятор C может делать странные предположения о его значении. Компилятор не делает различий между слабыми и обычными символами; это все (динамическая) магия компоновщика.

К исправьте, удалите инициализацию (= 0) из любого символа, который вы объявите слабым:

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

Подробное описание:

Язык Си не имеет понятия"слабый символ ". Это функциональные возможности, предоставляемые файлы в формате Elf, и (динамическая) линкеры, которые используют файлы в формате ELF.

В качестве man 1 nm man page описывает в разделе "V",

Когда слабый определенный символ связан с нормальным определенным символом, то нормальным определяемым символом является используется без ошибок. Когда слабый неопределенный символ связан и символ не определен, значение слабого символа становится нулем без ошибки.

Объявление слабого символа не должно быть инициализировано каким-либо значением, поскольку оно будет иметь нулевое значение, если процесс не связан с обычным символом того же имени. ( "определенный" на странице man 1 nm обозначает символ, существующий в таблице символов ELF.)

Функция "слабый символ" была разработана для работы с существующие компиляторы Си. Помните, что компиляторы C не имеют никакого различия между" слабыми "и" нормальными " символами.

Чтобы гарантировать, что это будет работать без нарушения поведения компилятора C, "слабый" символ должен быть неинициализирован, так что компилятор C не может делать никаких предположений относительно его значения. Вместо этого он будет генерировать код, который получает адрес символа как обычно - и именно там происходит магия поиска нормального/слабого символа.

Это также означает, что слабые символы может быть только "автоинициализировано" до нуля, а не любое другое значение, если только оно не" переопределено " обычным инициализированным символом с тем же именем.