Подтвердить что ты не робот

GCC: почему постоянные переменные не помещены в .rodata

Я всегда считал, что GCC помещает переменную static const в сегменты .rodata (или в .text сегменты для оптимизации) ELF или такого файла. Но это не так.

В настоящее время я использую gcc (GCC) 4.7.0 20120505 (prerelease) на ноутбуке с GNU/Linux. И он ставит постоянную переменную в сегмент .bss:

/*
 * this is a.c, and in its generated asm file a.s, the following line gives:
 *   .comm a,4,4 
 * which would place variable a in .bss but not .rodata(or .text)
 */
static const int a;

int main()
{
    int *p = (int*)&a;
    *p = 0;  /* since a is in .data, write access to that region */
             /* won't trigger an exception */
    return 0;
}

Итак, это ошибка или функция? Я решил записать это как ошибку для bugzilla, но лучше сначала обратиться за помощью.

Есть ли какие-либо причины, по которым GCC не может разместить константную переменную в .rodata?

ОБНОВЛЕНО:

Как проверено, постоянная переменная с явной инициализацией (например, const int a = 0;) будет помещена в .rodata GCC, а я оставил переменную неинициализированной. Таким образом, этот вопрос может быть закрыт позже - я не представил правильного вопроса.

Кроме того, в моих предыдущих словах я написал, что переменная a помещается в раздел .data, что неверно. Он фактически помещается в раздел .bss, так как не инициализирован. Текст, указанный выше, исправлен.

4b9b3361

Ответ 1

Компилятор сделал его общим, который можно объединить с другими совместимыми символами и который может идти в bss (не занимая места на диске), если он не имеет явно инициализированного определения. Ввод его в Родата был бы компромиссом; вы сохраните память (фиксацию заряда) во время выполнения, но будете использовать больше места на диске (потенциально много для огромного массива).

Если вы предпочтете пойти в родата, используйте параметр -fno-common для GCC.

Ответ 2

запись в объект, который был объявлен const квалифицированным, - это поведение undefined: все может случиться, даже это.

В C нет способа объявить сам объект не подлежащим, вы запрещаете его изменять только через определенный доступ, который у вас есть. Здесь у вас есть int*, поэтому модификация "разрешена" в том смысле, что компилятор не вынужден выдать диагностику. Выполнение броска на C означает, что вы знаете, что вы делаете.

Ответ 3

Есть ли какие-либо причины, по которым GCC не может разместить константную переменную в .rodata?

Ваша программа оптимизирована компилятором (даже в -O0 выполняются некоторые оптимизации). Выполняется постоянное распространение: http://en.wikipedia.org/wiki/Constant_folding

Попробуйте обмануть такой компилятор (обратите внимание, что эта программа по-прежнему технически undefined):

#include <stdio.h>

static const int a;

int main(void)
{
    *(int *) &a = printf("");  // compiler cannot assume it is 0

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

    return 0;
}

Ответ 4

Почему GCC это делает? Не могу ответить на этот вопрос, не спросив самих разработчиков. Если бы мне разрешили спекулировать, я бы поспорил, что это связано с оптимизацией - компиляторы не обязаны применять const.

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

Изменение константы - неопределенное поведение. Const - это контракт, и это особенно верно в C (и C++).

"Но что, если я const_cast от const_cast и все же const_cast y?" Тогда у вас есть неопределенное поведение.

Что означает неопределенное поведение, так это то, что компилятору разрешено делать буквально все, что он хочет, и то, что компилятор решит делать, не будет считаться нарушением стандарта ISO 9899.

3.4.3

1 неопределенное поведение

поведение при использовании непереносимой или ошибочной программной конструкции или ошибочных данных, для которых настоящий международный стандарт не предъявляет никаких требований

2 ПРИМЕЧАНИЕ Возможное неопределенное поведение варьируется от полного игнорирования ситуации с непредсказуемыми результатами до поведения во время перевода или выполнения программы документированным образом, характерным для среды (с выдачей диагностического сообщения или без него), до прекращения перевода или выполнения (с выдача диагностического сообщения).

ISO/IEC 9899: 1999, §3.4.3

Это означает, что, поскольку вы вызвали неопределенное поведение, все, что делает компилятор, технически правильно, поскольку оно не является некорректным. Ergo, это правильно для GCC принять...

static const int a = 0;

... и превратить его в символ .rodata, принимая...

static const int a; // guaranteed to be zero

... и превращая его в символ .bss.

В первом случае, любая попытка изменить --even по доверенности - как правило, приводит к нарушению сегментации, в результате чего ядро, чтобы принудительно убить запущенную программу. a В последнем случае программа, вероятно, будет работать без сбоев.

Тем не менее, нет смысла угадывать, что именно будет делать компилятор. Const - это контракт, и вы, программист, должны поддерживать этот контракт, не изменяя данные, которые должны быть постоянными. Нарушение этого контракта означает неопределенное поведение, а также все проблемы переносимости и программные ошибки, которые идут с ним.

Так что GCC может сделать пару вещей.

Он может записать символ в .rodata, обеспечивая защиту под ядром ОС

Он может записать объект куда-нибудь, где защита памяти не гарантируется, в этом случае...

Это может изменить значение

Это может изменить значение и сразу же изменить его обратно

Это может полностью удалить нарушающий код под логическим обоснованием того, что значение не меняется (0 → 0), что существенно оптимизирует...

int main(){
    int *p = &a;
    *p = 0;
    return 0;
}

... до...

int main(void){
    return 0;
}

Он может даже отправить модель Т-800 вовремя, чтобы уволить родителей до вашего рождения.

Все эти поведения являются законными (ну, в смысле соблюдения стандарта), поэтому сообщение об ошибке не было обоснованным.