Как и все остальные, это зависит от цели и компилятора, но для вас они остаются постоянными, и в этом коде нет других вещей, которые выглядят так, как будто они вводят случайность в стек (условно говоря), поэтому он будет делать то же самое каждый раз.
Системный стек обычно растет с высокого адреса на более низкий адрес.Если указатель стека равен 0x1234, и вы вводите значение (в 32-битной {4-байтной системе)), то указатель стека становится 0x1230.
Массивы адресуются от самого низкого адреса к самому высокому адресу.Если у вас есть
char a[2];
и [0] в 0x0122, тогда [1] будет в 0x0123.
Ваш массив в doit2
является автоматической переменной, что означает, что онсоздается при входе в функцию и удаляется при выходе из функции.Автоматические переменные должны жить либо в стеке, либо в регистрах.Поскольку это массив, компилятору гораздо проще сложить его в оперативную память, а не в регистры (это упрощает индексацию, поскольку он просто добавляет размер индекса * к адресу первого члена массива).Поскольку стек находится в ОЗУ, компилятор помещает массив в стек.
Выделение пространства в стеке для этого массива означает, что указатель стека на sizeof(int)*16
меньше, чем если бы этот массив отсутствовал.Указатель стека, скорее всего, указывает на overflowme[0]
в то время как в doit2
.
Есть другие вещи, которые могут быть в стеке, и пара вещей, которые должны были быть в стеке.Вещи, которые должны были быть в стеке, - это указатели возврата, которые помещались туда при вызове функций.В 32-битной системе они должны занимать 4 байта каждый.Вещи, которые могли бы быть в стеке (если бы компилятор хотел его использовать), это указатель предыдущего кадра.(Явный *) стековые фреймы на x86-32 - это просто пространство между ESP и EBP, но они не нужны, поэтому часто они не используются, а EBP используется вместо этого как регистр общего назначения (доступны более общие регистры общего назначения.в общем хорошо).Однако использование стековых фреймов полезно, поскольку они значительно упрощают отладку, поскольку ESP и EBP действуют как маркеры для краев локальных переменных.Иногда требуются стековые фреймы, например, когда вы используете автоматические массивы alloca
или C99 с переменным размером, потому что они позволяют отбрасывать пространство локальных переменных для функции mov EPB, ESP
или эквивалентным инструкциям, а не sub size_of_local_variable, ESP
, поэтому компилятор не 'Я должен знать размер кадра.Они также позволяют адресовать локальные переменные относительно EBP, а не ESP, который в случае alloca
изменяется.EBP в этом случае не изменится до конца текущей функции, если только она не была изменена и восстановлена путем вызова функций.
При компиляции без включенных оптимизаций компиляторы часто всегда используют стековые фреймы из-за упрощения отладки.Также проще смоделировать код с помощью стековых фреймов, а затем преобразовать код, чтобы он не использовался, после того как он доказал, что они не нужны.
Таким образом, предыдущее значение EBP может находиться или не находиться в стеке.между обратным адресом (где-то в problem2
) и последним элементом в doit2
overflowme
.Компилятор также может свободно помещать в стек все что угодно, так что, кто знает, что еще может быть там? * Локальная переменная
problem2
int x
может находиться в регистре или встек.При компиляции без включенных оптимизаций локальные переменные часто попадают в стек, даже когда они могут попадать в регистры.
Итак, давайте предположим, что существует массив doit2
overflowme
, старый указатель кадра,адрес возврата и problem2
x
в стеке (и еще кое-что в разделе {который на самом деле находится по более высокому адресу}).
Поскольку &(overflowme[i])
- это то же самое, что и добавление адреса первого элемента overflowme
в (i * {размер int
}), а старый EBP находится после последнего элемента overflowme
и адрес возврата лежит после старого EBP, а int x
- после адреса возврата, x
определенно стоит прямо на пути переполнения буфера.
Почему это происходит для индекса 37, не ясно. Математическая схема указателя (при условии, что только элементы, которые я описал выше, находится в стеке между массивом и x
), не предполагает, что он должен основываться на 4-байтовых указателях (32-битная машина), хотя, если это 8-байтовый указатель система (64-битная машина), то математика ближе к адресу, по которому я ожидал бы x
, если sizeof(int) == 8
. Компилятор также мог бы пойти дальше и выделить место в стеке для вызовов printf
(аргументы переменной после строки формата должны идти в стек), что могло бы повлиять на математику (а также побудить компилятор поместить x
в стека, потому что это все равно пришлось бы толкать туда).
Если вы хотите получить более подробный ответ на свой вопрос, посмотрите сборку для этого кода и определитесь с точной адресацией.
- Можно подумать, что кадр стека существует, даже если EBP не используется в качестве базового указателя кадра, но тогда кадр не будет в рамке.