Странное поведение стека в C - PullRequest
10 голосов
/ 22 марта 2009

Я беспокоюсь, что неправильно понимаю поведение стека в 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 для компиляции без оптимизации.

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

Ответы [ 6 ]

19 голосов
/ 22 марта 2009

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

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

4 голосов
/ 22 марта 2009

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

4 голосов
/ 22 марта 2009

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

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

2 голосов
/ 22 марта 2009

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

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

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

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

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

1 голос
/ 22 марта 2009

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

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

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

0 голосов
/ 22 марта 2009

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...