Почему стековая память выделяется, когда она не используется?

14

Рассмотрим следующий пример:

struct vector {
    int  size() const;
    bool empty() const;
};

bool vector::empty() const
{
    return size() == 0;
}

Сгенерированный код сборки для vector::empty(по clang, с оптимизацией):

push    rax
call    vector::size() const
test    eax, eax
sete    al
pop     rcx
ret

Почему он выделяет пространство стека? Это не используется вообще. pushИ popможет быть опущен. Оптимизированные сборки MSVC и gcc также используют пространство стека для этой функции (см. Godbolt ), поэтому должна быть причина.

Доктор гут
источник
7
Вы учли неявный thisпараметр?
Dan04
1
@Bob__: Нет. Почему я должен? vector::size()не определен в примере, чтобы имитировать, что он не встроен.
Доктор Гут
1
Итак, как компилятор может оптимизировать то, чего не знает?
Bob__
1
@Bob__: Я думаю, что знание реализации vector::size()не имеет отношения к выделению или не выделению стекового фрейма vector::empty(). В empty()нем просто называется, что бы это ни было.
Доктор Гут
1
Ну, вы вызываете функцию, которая возвращает что-то, вам нужно место для этого (если вы не знаете ничего лучше).
Bob__

Ответы:

11

Он выделяет пространство стека, поэтому стек выравнивается по 16 байтов. Это необходимо, потому что адрес возврата занимает 8 байтов, поэтому для сохранения 16-байтового стека требуется дополнительное 8-байтовое пространство.

Выравнивание стековых фреймов можно настроить с помощью аргументов командной строки для некоторых компиляторов.

  • MSVC : в документации сказано, что стек всегда выровнен по 16 байтов. Никакой аргумент командной строки не может изменить это. Пример Godbolt показывает, что 40 байтов вычитаются из rspначала функции, что означает, что что-то еще также влияет на это.
  • clang : -mstack-alignmentопция определяет выравнивание стека. Кажется, что по умолчанию 16, хотя не задокументировано. Если вы установите его на 8, выделение стека ( pushи pop) исчезнет из сгенерированного кода сборки.
  • gcc : -mpreferred-stack-boundaryопция определяет выравнивание стека. Если заданное значение равно N, это означает 2 ^ N байтов выравнивания. Значение по умолчанию 4, что означает 16 байтов. Если вы установите значение 3 (т.е. 8 байт), распределение стека ( subи addдля rsp) исчезнет из сгенерированного кода сборки.

Проверьте на Годболт .

Геза
источник
Вот почему гуру c ++, эксперты всегда предупреждали: расположите членов структуры / класса в порядке от самого длинного / самого большого размера к наименьшему ... только так, чтобы это было правильно эффективно
nonock
@geza: Спасибо. Я провел некоторые исследования для двух других компиляторов и написал их к вашему ответу. Вам это нравится?
Доктор Гут
1
@ Dr.Gut: спасибо, вы сделали ответ намного лучше и полнее. Обратите внимание, что выравнивание стека обычно документируется в ABI для системы (например, для некоторых систем вот документы: github.com/hjl-tools/x86-psABI/wiki/X86-psABI ).
Геза
@geza: Спасибо.
Доктор Гут