Странное поведение стека в C

Я беспокоюсь, что я неправильно понимаю что-то о поведении стека в C.

Предположим, что у меня есть следующий код:

int main (int argc, const char * argv[]) 
{
    int a = 20, b = 25;
    {
        int temp1;
        printf("&temp1 is %ld\n" , &temp1);
    }

    {
        int temp2;
        printf("&temp2 is %ld\n" , &temp2);
    }
    return 0;
}

Почему я не получаю один и тот же адрес в обеих распечатках? Я получаю, что temp2 находится в одном int от temp1, как если бы temp1 никогда не перерабатывался.

Мое ожидание заключается в том, что стек содержит 20 и 25. Затем установите temp1 сверху, затем удалите его, затем установите temp2 сверху, затем удалите его.

Я использую gcc в Mac OS X.

Обратите внимание, что я использую флаг -O0 для компиляции без оптимизации.

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

Ответ 1

Компилятор полностью в пределах своих прав не оптимизировать temp1 и temp2 в одном месте. Прошло много лет с тех пор, как компиляторы генерировали код для одной операции стека за раз; в эти дни вся рамка стека выкладывается за один раз. (Несколько лет назад коллега, и я придумал особенно умный способ сделать это.) Наивное расположение стека, вероятно, ставит каждую переменную в свою слот, даже если, как в вашем примере, их время жизни не перекрывается.

Если вам интересно, вы можете получить разные результаты с помощью gcc -O1 или gcc -O2.

Ответ 2

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

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

Ответ 3

Я полагаю, что стандарт C просто говорит о масштабах и времени жизни переменных, определенных в блоке. Он не делает promises о том, как переменные взаимодействуют со стеклом или даже существует стек.

Ответ 4

Я помню что-то читал. Все, что у меня есть, это скрытая ссылка.

Просто чтобы все знали (и ради архивов), похоже, что наше расширение ядра работает с известным ограничением GCC. Просто для того, чтобы повторить, у нас есть функция в очень портативной, очень легкой библиотеке, которая по какой-то причине скомпилируется с 1600+ байтным стеком при компиляции в/для Darwin. Независимо от того, какие параметры компилятора я пытался и какие уровни оптимизации я использовал, стек был не меньше, чем 1400 "машинная проверка" паники в довольно воспроизводимых (но не часто встречающихся) ситуациях.

После многого поиска в Интернете, изучая сборку i386 и разговаривая с некоторыми людьми, которые намного лучше собрались, я узнал, что GCC несколько известен тем, что имеет ужасное распределение стека....]

По-видимому, это gcc грязный маленький секрет, за исключением того, что он немного секрет для некоторых - Линус Торвальдс несколько раз жаловался на различные списки о распределении стека gcc (поиск lkml.org для "использования стека gcc" ). После того, как я знал, что искать, было много проблем с gcc подпарам распределения стековых переменных и, в частности, невозможностью повторного использования пространства стека для переменных в разных областях.

С учетом сказанного, моя версия Linux gcc должным образом повторяет использование пространства стека, я получаю тот же адрес для обеих переменных. Не уверен, что об этом говорит стандарт C, но строгое соблюдение правил важны только для правильности кода в С++ (из-за разрушения в конце области), но не в C.

Ответ 5

Нет стандарта, который устанавливает, как переменные помещаются в стек. Что происходит в компиляторе, гораздо сложнее. В вашем коде компилятор может даже выбрать полностью игнорировать и подавлять переменные a и b.

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

Стековое пространство очень дешево, в том смысле, что время для выделения двух или 20 переменных является постоянным. Кроме того, пространство стека очень динамично для большинства вызовов функций, поскольку, за исключением нескольких функций (те, которые ближе main() и функции ввода потоков, с длинными циклами событий или около того), они, как правило, быстро завершаются. Таким образом, вы просто не беспокоитесь о них.

Ответ 6

Это полностью зависит от компилятора и того, как он настроен.