В чем идея использования стека для локальных переменных?

В C, как многие из вас знают, все локальные переменные находятся в стеке. Стек, являющийся структурой данных «первым пришел последним», означает, что вы можете получить доступ только к тому, что было помещено в него последним. Итак, учитывая следующий код:

int k = 5;
int j = 3;
short int i;

if (k > j) i = 1;

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

Для короткого объявления int i я предполагаю, что в стеке выделяется 2 байта. Для int k и int j для обоих 4 байта выделяются значения 5 и 3. Таким образом, стек будет выглядеть следующим образом

----------   <- stack pointer
int i
----------
int k = 5
----------
int j = 3
----------

поэтому для оператора if вам нужно будет вставить int i, чтобы перейти к условиям k и j, и если да, то куда идет int i? Все это кажется очень трудоемким и утомительным, если C использует локальные переменные именно так.

Так это на самом деле то, как это делает C, или я все испортил?


person Anthony    schedule 12.11.2014    source источник
comment
на самом деле ваша компоновка элементов в стеке неверна, так как я короткий, а не int. и стек растет в памяти вниз, а не вверх, и локальные переменные помещаются в стек в обратном порядке. Несмотря на это, любое значение в локальном стеке НЕ извлекается, а используется смещение от указателя стека, поэтому обращение к «k» приводит к чтению слова (какого-то регистра) из sp[(смещение к k) аналогично для переменной «j» . и «i» устанавливается как запись полуслова sp[offset to i] from (нижняя половина регистра, которая содержит результат k + j), в общем, вы можете думать о стеке как о длинном массиве с разделами.   -  person user3629249    schedule 13.11.2014


Ответы (4)


Ты как-то все портишь.

Да, локальные (auto) переменные обычно хранятся в стеке. Однако при чтении они не извлекаются из стека; на них ссылается смещение от указателя стека.

Возьмите следующий код:

x = y + z;

где каждый из x, y и z выделяется в стеке. Когда компилятор генерирует эквивалентный машинный код, он будет ссылаться на каждую переменную по смещению от заданного регистра, примерно так:

mov -8(%ebp), %eax   
add -12(%ebp), %eax
mov %eax, -4(%ebp)

В архитектурах x86 %ebp – это указатель кадра; стек разбит на фреймы, где каждый фрейм содержит параметры функции (если есть), адрес возврата (то есть адрес инструкции, следующей за вызовом функции) и локальные переменные (если есть). ). В системах, с которыми я знаком, стек растет «вниз» к 0, а локальные переменные хранятся «ниже» указателя кадра (нижние адреса), отсюда и отрицательное смещение. Приведенный выше код предполагает, что x соответствует -4(%ebp), y соответствует -8(%ebp), а z соответствует -12(%ebp).

Все будет извлечено из стека1, когда функция вернется, но не раньше.

ИЗМЕНИТЬ

Обратите внимание, что ничего из этого не требуется определением языка C. Язык не требует использования стека среды выполнения вообще (хотя компилятор был бы сукой без него). Он просто определяет время жизни auto переменных как от конца их объявления до конца их области видимости. Стек делает это простым, но не обязательным.

<ч> 1. Что ж, указатели стека и фрейма будут установлены в новые значения; данные останутся там, где они были, но теперь эта память доступна для чего-то другого.

person John Bode    schedule 12.11.2014

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

person Kerrek SB    schedule 12.11.2014
comment
Действительно, если вы посмотрите на дизассемблирование функции C, вы не найдете push и pop для локальных переменных, вы найдете постоянное смещение от указателя базового стека - переменные находятся в очень предсказуемом месте в дополнение к тому, что они в оперативной памяти. Инструкция для загрузки k в примере OP, например, будет выглядеть как mov eax, [ebp-4] - сам указатель стека, являющийся переменной внутри функции, фактически вообще не используется для локальных переменных. - person Adam D. Ruppe; 13.11.2014
comment
Похоже, что несколько источников не согласны с вами, вот один из них: gribblelab.org/CBootcamp/7_Memory_Stack_vs_Heap.html обратите внимание, что стек представляет собой структуру данных FILO (первым пришел, последним вышел), которая управляется и оптимизируется ЦП довольно тщательно. Я должен указать, что я не говорю, что он использует указатель аппаратного стека, но на самом деле это стек FILO. - person Anthony; 13.11.2014
comment
Это может помочь посмотреть на сгенерированный код, запустив gcc -c foo.c, а затем objdump -d foo.o (при желании добавив -M intel в objdump, если вы похожи на меня и ненавидите синтаксис AT&T), и вы увидите, что на самом деле сгенерировано. Хотя я предполагаю, что это не поможет, если вы не умеете читать на ассемблере... но суть в том, что инструкция вызова помещает адрес возврата в стек, затем функция использует память рядом с этой для локальных переменных, затем возвращение снова сбрасывает все это. Так что это похоже на стек массивов, а не на чистый стек. - person Adam D. Ruppe; 13.11.2014

Стек вызовов является стеком, но он используется не так, как вы себе представляете. Каждый раз, когда выполняется вызов функции, адрес возврата (программный счетчик) помещается в стек вместе с локальными переменными. Когда каждая функция возвращает значение, стек извлекается так называемым «фреймом стека», который включает в себя переменные. Внутри каждой функции память обрабатывается как произвольный доступ. Компилятор, сгенерировав код, упорядочивающий локальные переменные в стеке, точно знает, на каком расстоянии они находятся от указателя фрейма стека, и поэтому ему не нужно помещать и извлекать отдельные локальные переменные.

person brycem    schedule 12.11.2014

На вершину стека в процессорах Intel и многих других ссылается адрес, хранящийся в регистре процессора, назовите его SP, который скопирован в базовый указатель, назовите его BP; многие машинные инструкции допускают адресное выражение, состоящее из текущего BP в сочетании со смещением в байтах. поэтому в вашем примере я будет иметь смещение 0, j будет смещением -2, а k будет смещением -6.

if просто разрешит сравнение содержимого адресов -6(BP) и -4(BP). фактические значения смещения могут отличаться от реализации к реализации; но это общая идея...

person IRocks    schedule 12.11.2014