2013-10-06 2 views
1

То, как меня учили довольно долго, заключается в том, что когда я запускаю программу, первое, что сразу идет в стек, - это стековый фрейм для основного метода. И если я вызываю функцию foo() из main, тогда стек стека, размер локальных переменных (автоматические объекты) и параметры также попадают в стек.Действительно ли стек стека попадает в стек при вызове функции?

Однако я столкнулся с несколькими вещами, которые противоречат этому. И я надеюсь, что кто-то сможет очистить мое замешательство или объяснить, почему на самом деле нет никаких противоречий.

Первое противоречие:

В книге «Язык программирования C++» третье издание Бьярне Страуструп, он говорит на странице 244, «Именованная автоматический объект создается каждый раз, когда его декларация встречается в исполнении программа." Если это недостаточно ясно, на следующей странице говорится: «Конструктор локальной переменной выполняется каждый раз, когда поток управления проходит через объявление локальной переменной».

Означает ли это, что общая память для фрейма стека не распределяется сразу, а скорее блокируется по мере того, как встречаются объявления переменных? Кроме того, означает ли это, что кадр стека может не быть одного и того же размера каждый раз, если объявление переменной не встречалось из-за оператора if?

Второе противоречие:

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

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

+0

Возможно, вы захотите посмотреть [Ручной рычаг и указатель кадра] (http://stackoverflow.com/questions/15752188/arm-link-register-and-frame-pointer), в основном * Мы хотим, чтобы компиляторы были такими же быстро можно *, поэтому нет общего правила о том, как они должны генерировать код ассемблера, особенно при оптимизации. Если компилятор может * вывести * вещи, как в функции листа, он воспользуется этим фактом. Компилятор должен создать пространство для объекта, если он не является ** POD **, и он не знает о реализации конструктора; он может сразу же выделить или нет, если он хочет уменьшить использование стека, выбрать кеш и т. д. –

ответ

3

Что касается вашего первого вопроса:

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

Означает ли это, что общая память для фрейма стека не распределяется сразу, а скорее блокируется по мере того, как встречаются объявления переменных?

Этот вопрос действительно специфичен для компилятора. Указатель стека - это всего лишь указатель, как он используется двоичным кодом до компилятора. Фактически некоторые компиляторы могут резервировать всю активационную запись, некоторые могут резервировать понемногу, некоторые другие могут резервировать ее динамически в соответствии с конкретным вызовом и так далее. Это даже тесно связано с оптимизацией, поэтому компилятор способен упорядочить вещи так, как он считает лучше.

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

Опять же, здесь нет строгого ответа. Обычно компиляторы полагаются на алгоритмы register allocation, которые могут выделять регистры таким образом, чтобы минимизировать «пролитые» (на стек) переменные. Конечно, если вы пишете в сборке вручную, вы можете назначить определенные регистры для определенных переменных во всей своей программе только потому, что по своему содержанию вы знаете, как вы хотите заставить ее работать.

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

1
  1. Построение объекта C++ очень мало связано с получением памяти для объекта. На самом деле было бы более точным сказать «резервирование памяти», поскольку в целом у компьютеров нет небольших групп разработчиков RAM, которые вступают в действие каждый раз, когда вы запрашиваете новый объект. Память более или менее постоянна (хотя мы можем спорить о VM). Конечно, компилятор должен организовать для своей программы возможность использовать только определенный диапазон памяти для одной вещи за раз. Это может (и, вероятно, это так) потребовать от него зарезервировать диапазон памяти до существования объекта и не использовать его для других объектов до некоторого времени после исчезновения объекта. Для эффективности компилятор может (даже в случае объектов с динамической продолжительностью хранения) оптимизировать резервирование, резервируя сразу несколько блоков памяти, если он знает, что они понадобятся. В любом случае, когда C++ говорит о «конструировании объекта», это означает только то, что: взятие диапазона памяти с неопределенным содержимым и выполнение того, что необходимо для создания представления объекта (и всего остального в состоянии мира подразумевается созданием объекта, который не может быть ограничен конкретным куском памяти.)

  2. Нет необходимости в существовании фреймов стека. Нет требования к существованию стека. Это все зависит от компилятора. Большинство компиляторов действительно генерируют код, который использует стек, конечно, и хорошие компиляторы будут выяснять, когда можно сокращать или даже опускать фрейм стека. Итак, да, кадры могут отличаться по размеру.

+0

Это действительно отличные ответы. Итак, все зависит от того, хочет ли он выделить память сразу (игнорируя тот факт, что некоторые переменные могут не понадобиться из-за операторов условий, таких как if else) или пройти через логику, чтобы увидеть, что абсолютно необходимо ? – Kacy

+1

@ KacyRaye: Абсолютно. Например, Gcc выделяет достаточное количество памяти в кадре стека для наибольшей степени автоматических переменных в функции. Например, 'void f() {{int tiny [3]; g();} {int огромный [10000]; г(); }} '; два вызова 'g' имеют один и тот же указатель стека при записи. (То есть, после достаточно места для * либо * крошечного или огромного, но не того и другого). – rici

1

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

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

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

 Смежные вопросы

  • Нет связанных вопросов^_^