Может ли компилятор C переупорядочить переменные стека?

StackOverflow https://stackoverflow.com/questions/238441

Вопрос

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

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, но я думаю, что этот документ можно считать доказательством того, что вы все правы.Еще раз спасибо.

Это было полезно?

Решение

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

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

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

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

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

Другие советы

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

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

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

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

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

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

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

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

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

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

Встроенные платы PIC и Rabbit не поддерживают иметь складываются, если вы не попросите особенно вежливо.

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

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

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

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

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

Это не ответ на ваш вопрос, но вот мои 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...
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top