Может ли компилятор C переставлять переменные стека? - PullRequest
15 голосов
/ 26 октября 2008

В прошлом я работал над проектами для встроенных систем, где мы изменили порядок объявления переменных стека, чтобы уменьшить размер получаемого исполняемого файла. Например, если бы мы имели:

void func()
{
    char c;
    int i;
    short s;
    ...
}

Мы бы изменили порядок так:

void func()
{
    int i;
    short s;
    char c;
    ...
}

Из-за проблем с выравниванием первый из них позволил использовать 12 байт стекового пространства, а второй - только 8 байт.

Это стандартное поведение для компиляторов C или просто недостаток компилятора, который мы использовали?

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

В качестве дополнительного вопроса, это также относится к компиляторам C ++?

Редактировать

Если ответ да, компиляторы C / C ++ могут переставлять переменные стека, можете ли вы привести пример компилятора, который определенно делает это? Я хотел бы видеть документацию компилятора или что-то подобное, что подтверждает это.

Редактировать снова

Спасибо всем за помощь. Что касается документации, лучшее, что мне удалось найти, - это документ Оптимальное назначение слотов стека в GCC (pdf), написанный Навином Шармой и Сандживом Кумаром Гуптой, который был представлен на материалах саммита GCC в 2003 году. .

Рассматриваемый проект использовал компилятор ADS для разработки ARM. В документации для этого компилятора упоминается, что объявления порядка, как я показал, могут улучшить производительность, а также размер стека, из-за того, что архитектура ARM-Thumb вычисляет адреса в фрейме локального стека. Этот компилятор не переставил локальные переменные, чтобы воспользоваться этим. В этой статье говорится, что с 2003 года GCC также не переставлял фрейм стека для улучшения местоположения эталона для процессоров ARM-Thumb, но это подразумевает, что вы могли бы.

Я не могу найти ничего, что определенно говорит о том, что это когда-либо было реализовано в GCC, но я думаю, что этот документ считается доказательством того, что вы все правы. Еще раз спасибо.

Ответы [ 11 ]

39 голосов
/ 04 ноября 2008

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

22 голосов
/ 26 октября 2008

Поскольку в стандарте нет ничего, запрещающего это для компиляторов C или C ++, да, компилятор может это делать.

Это отличается для агрегатов (то есть структур), где относительный порядок должен поддерживаться, но все же компилятор может вставлять байты отступа для достижения предпочтительного выравнивания.

Более новые компиляторы MSRC IIRC используют эту свободу в борьбе с переполнением буфера локальных компьютеров.

В качестве примечания, в C ++ порядок уничтожения должен быть обратным порядку объявления, даже если компилятор переупорядочивает структуру памяти.

(хотя я не могу цитировать главу и стих, хотя это по памяти.)

10 голосов
/ 26 октября 2008

Стек даже не должен существовать (фактически, в стандарте C99 нет единственного вхождения слова «стек»). Так что да, компилятор может делать все, что захочет, при условии, что он сохраняет семантику переменных с автоматической продолжительностью хранения.

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

10 голосов
/ 26 октября 2008

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

4 голосов
/ 27 октября 2008

Компилятор для Texas Instruments серии 62xx DSP способен и делает «Оптимизация всей программы». (вы можете выключить его)

Здесь ваш код перестраивается, а не только местные жители. Так что порядок исполнения оказывается не совсем тем, что вы могли ожидать.

C и C ++ не на самом деле обещают модель памяти (в смысле, скажем, JVM), так что все может быть совершенно по-другому и все же законно.

Для тех, кто их не знает, семейство 62xx - это 8 инструкций за такт DSP; на 750 МГц они достигают пика при 6e + 9 инструкциях. В любом случае, иногда. Они выполняют параллельное выполнение, но порядок команд выполняется в компиляторе, а не в процессоре, как в Intel x86.

Встраиваемые платы PIC и Rabbit не имеют стеков, если вы не спросите особенно хорошо.

4 голосов
/ 26 октября 2008

Компилятор может вообще не использовать стек для данных. Если вы работаете на платформе настолько маленькой, что беспокоитесь о размере 8 против 12 байт стека, то вполне вероятно, что найдутся компиляторы с довольно специализированными подходами. (На ум приходят некоторые компиляторы PIC и 8051)

Для какого процессора вы компилируете?

0 голосов
/ 28 октября 2008

Это не отвечает на ваш вопрос, но вот мои 2 цента о связанной проблеме ...

У меня не было проблемы оптимизации стекового пространства, но у меня была проблема неправильного выравнивания двойных переменных в стеке. Функция может быть вызвана из любой другой функции, и значение указателя стека может иметь любое невыровненное значение. Итак, я пришел с идеей ниже. Это не оригинальный код, я просто написал ...

#pragma pack(push, 16)

typedef struct _S_speedy_struct{

 double fval[4];
 int64  lval[4];
 int32  ival[8];

}S_speedy_struct;

#pragma pack(pop)

int function(...)
{
  int i, t, rv;
  S_speedy_struct *ptr;
  char buff[112]; // sizeof(struct) + alignment

  // ugly , I know , but it works...
  t = (int)buff;
  t +=  15; // alignment - 1
  t &= -16; // alignment
  ptr = (S_speedy_struct *)t;

  // speedy code goes on...
}
0 голосов
/ 27 октября 2008

Нет необходимости в спекуляциях по поводу того, что требует или не требует стандарт C: последние черновики свободно доступны в Интернете в рабочей группе ANSI / ISO .

0 голосов
/ 26 октября 2008

AFAIK В определении C или C ++ нет ничего, определяющего, как компилятор должен упорядочивать локальные переменные в стеке. Я бы сказал, что полагаться на то, что компилятор может сделать в этом случае, - плохая идея, потому что следующая версия вашего компилятора может сделать это по-другому. Если вы потратите время и силы на то, чтобы упорядочить локальные переменные, чтобы сэкономить несколько байтов стека, эти несколько байтов должны быть действительно критически важными для функционирования вашей системы.

0 голосов
/ 26 октября 2008

Приличный компилятор поместит локальные переменные в регистры, если это возможно. Переменные следует помещать в стек только в случае чрезмерного давления в регистре (недостаточно места) или если адрес переменной взят, то есть она должна находиться в памяти.

Насколько я знаю, нет ничего, что говорило бы о том, что переменные должны быть помещены в любое конкретное место или в стек для C / C ++; компилятор помещает их туда, где это лучше всего для производительности и / или того, что удобно для разработчиков компиляторов.

...