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

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

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

Мы бы изменили порядок следующим образом:

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

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

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

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

Как вопрос о бонусе, применимо ли это также к компиляторам С++?

Edit

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

Изменить снова

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

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

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

Ответы

Ответ 1

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

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

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

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

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

Ответ 2

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

Ответ 3

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

Ответ 4

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

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

Ответ 5

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

Какой процессор вы компилируете?

Ответ 6

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

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

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

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

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

Ответ 7

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

Ответ 8

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

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

Ответ 9

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

Ответ 10

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

Ответ 11

Это не отвечает на ваш вопрос, но вот мои 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...
}