Стеки растут вверх или вниз?

У меня есть эта часть кода в c:

int q = 10;
int s = 5;
int a[3];

printf("Address of a: %d\n",    (int)a);
printf("Address of a[1]: %d\n", (int)&a[1]);
printf("Address of a[2]: %d\n", (int)&a[2]);
printf("Address of q: %d\n",    (int)&q);
printf("Address of s: %d\n",    (int)&s);

Вывод:

Address of a: 2293584
Address of a[1]: 2293588
Address of a[2]: 2293592
Address of q: 2293612
Address of s: 2293608

Итак, я вижу, что от a до a[2] адреса памяти увеличиваются на 4 байта каждый. Но от q до s адреса памяти уменьшаются на 4 байта.

Интересно 2 вещи:

  • Увеличивается ли стек вверх или вниз? (В этом случае он выглядит как для меня)
  • Что происходит между адресами памяти a[2] и q? Почему там большая разница в памяти? (20 байтов).

Примечание. Это не вопрос домашней работы. Мне интересно, как работает стек. Спасибо за любую помощь.

Ответ 1

Поведение стека (рост или рост) зависит от бинарного интерфейса приложения (ABI) и того, как организован стек вызовов (aka record record).

В течение всей своей жизни программа связана с другими программами, такими как ОС. ABI определяет, как программа может общаться с другой программой.

Стек для разных архитектур может расти в любом случае, но для архитектуры она будет последовательной. Пожалуйста, проверьте эту ссылку wiki. Но рост стека определяется ABI этой архитектуры.

Например, если вы берете MIPS ABI, стек вызовов определяется ниже.

Рассмотрим, что функция 'fn1' вызывает 'fn2'. Теперь фрейм стека, видимый как "fn2", выглядит следующим образом:

direction of     |                                 |
  growth of      +---------------------------------+ 
   stack         | Parameters passed by fn1(caller)|
from higher addr.|                                 |
to lower addr.   | Direction of growth is opposite |
      |          |   to direction of stack growth  |
      |          +---------------------------------+ <-- SP on entry to fn2
      |          | Return address from fn2(callee) | 
      V          +---------------------------------+ 
                 | Callee saved registers being    | 
                 |   used in the callee function   | 
                 +---------------------------------+
                 | Local variables of fn2          |
                 |(Direction of growth of frame is |
                 | same as direction of growth of  |
                 |            stack)               |
                 +---------------------------------+ 
                 | Arguments to functions called   |
                 | by fn2                          |
                 +---------------------------------+ <- Current SP after stack 
                                                        frame is allocated

Теперь вы можете видеть, что стек растет вниз. Итак, если переменные выделены для локального фрейма функции, адреса переменных фактически растут вниз. Компилятор может определить порядок переменных для распределения памяти. (В вашем случае это может быть либо "q", либо "s", которая является первой выделенной памятью стека. Но, как правило, компилятор выполняет распределение памяти по стеклу в соответствии с порядком объявления переменных).

Но в случае массивов распределение имеет только один указатель, и память должна быть выделена, будет на самом деле указана одним указателем. Память должна быть смежной для массива. Итак, хотя стек растет вниз, для массивов стек растет.

Ответ 2

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

Ни один из них не указан стандартом C, но ответы немного отличаются:

  • Каким образом стек растет, когда выделяется новый кадр - если функция f() вызывает функцию g(), будет ли указатель кадрового флага f больше или меньше, чем g указатель кадра? Это может идти в любом случае - это зависит от конкретного компилятора и архитектуры (смотрите "соглашение о вызове" ), но он всегда согласован в пределах определенной платформы (с несколькими странными исключениями, см. Комментарии). Вниз более распространено; это случай в x86, PowerPC, MIPS, SPARC, EE и Cell SPU.
  • Как локальные переменные функции выложены внутри фрейма стека? Это неуказано и совершенно непредсказуемо; компилятор может упорядочить свои локальные переменные, но ему нравится получать наиболее эффективный результат.

Ответ 3

Направление, в котором растут стеки, зависит от архитектуры. Тем не менее, я понимаю, что только очень немногие аппаратные архитектуры имеют стеки, которые растут.

Направление роста стека не зависит от расположения отдельного объекта. Поэтому, когда стек может расти, массивы не будут (i.e & array [n] всегда будет < & array [n + 1]);

Ответ 4

В стандарте нет ничего, что бы определяло, как вещи вообще организованы в стеке. Фактически, вы могли бы создать соответствующий компилятор, который не хранил бы элементы массива в смежных элементах в стеке, если бы у него были умственные способности, чтобы по-прежнему правильно выполнять арифметику элемента массива (чтобы он знал, например, что a 1 находился в 1K от [0] и мог подстраиваться для этого).

Причина, по которой вы можете получать разные результаты, заключается в том, что, хотя стек может расти, чтобы добавить к нему "объекты", массив является единственным "объектом" и может иметь восходящие элементы массива в обратном порядке. Но небезопасно полагаться на это поведение, так как направление может меняться, а переменные могут меняться местами по целому ряду причин, включая, но не ограничиваясь:

  • Оптимизация.
  • выравнивание.
  • капризы человека - часть управления стеком компилятора.

Смотрите здесь для моего превосходного трактата о направлении стека: -)

В ответ на ваши конкретные вопросы:

  • Увеличивается ли стек вверх или вниз?
    Это не имеет значения вообще (с точки зрения стандарта), но, поскольку вы спросили, он может расти вверх или вниз в памяти, в зависимости от реализации.
  • Что происходит между адресами памяти [2] и q? Почему там большая разница в памяти? (20 байт)?
    Это не имеет значения (с точки зрения стандарта). См. Выше по возможным причинам.

Ответ 5

На x86 "распределение" памяти в кадре стека состоит просто из вычитания необходимого количества байтов из указателя стека (я считаю, что другие архитектуры похожи). В этом смысле, я думаю, что стек вырос "вниз", поскольку адреса становятся все меньше по мере того, как вы более глубоко вписываетесь в стек (но я всегда предполагаю, что память начинается с 0 в левом верхнем углу и получает большие адреса при перемещении направо и обернуть, так что в моем ментальном образе стек растет...). Порядок объявляемых переменных может не иметь никакого отношения к их адресам - я считаю, что стандарт позволяет компилятору переупорядочить их, если он не вызывает побочных эффектов (кто-то, пожалуйста, исправьте меня, если я ошибаюсь), Они просто застревают где-то в этом промежутке в используемых адресах, созданных при вычитании количества байтов из указателя стека.

Разрыв вокруг массива может быть своего рода дополнением, но он таинственный для меня.

Ответ 6

Прежде всего, его 8 байтов неиспользуемого пространства в памяти (его не 12, помните, что стек растет вниз, поэтому пространство, которое не выделено, составляет от 604 до 597). и почему?. Поскольку каждый тип данных занимает пространство в памяти, начиная с адреса, делящегося на его размер. В нашем случае массив из 3 целых чисел занимает 12 байтов пространства памяти, а 604 не делится на 12. Таким образом, он оставляет пустые пространства, пока не встретит адрес памяти, который делится на 12, это 596.

Таким образом, пространство памяти, выделенное для массива, составляет от 596 до 584. Но поскольку распределение массивов продолжается, то первый элемент массива начинается с адреса 584, а не из 596.

Ответ 7

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

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

Причина стека, растущего вниз, - это возможность разыменования с точки зрения указателя стека или базы.

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

Это одна из причин, почему так много языков программирования и сценариев используют виртуальную машину на основе стека, а не на основе регистров.

Ответ 8

Мой стек, по-видимому, распространяется на более низкие нумерованные адреса.

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

$ cat stack.c
#include <stdio.h>

int stack(int x) {
  printf("level %d: x is at %p\n", x, (void*)&x);
  if (x == 0) return 0;
  return stack(x - 1);
}

int main(void) {
  stack(4);
  return 0;
}
$ /usr/bin/gcc -Wall -Wextra -std=c89 -pedantic stack.c
$ ./a.out
level 4: x is at 0x7fff7781190c
level 3: x is at 0x7fff778118ec
level 2: x is at 0x7fff778118cc
level 1: x is at 0x7fff778118ac
level 0: x is at 0x7fff7781188c

Ответ 9

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

int f(int *x)
{
  int a;
  return x == NULL ? f(&a) : &a - x;
}

int main(void)
{
  printf("stack grows %s!\n", f(NULL) < 0 ? "down" : "up");
  return 0;
}

Ответ 10

Стек растет (на x86). Тем не менее, стек распределяется в одном блоке, когда функция загружается, и у вас нет гарантии того, какой порядок элементов будет в стеке.

В этом случае ему выделено пространство для двух ints и трехстрочного массива в стеке. Он также выделил дополнительные 12 байтов после массива, поэтому он выглядит так:

a [12 байт]
padding (?) [12 bytes]
s [4 байта]
q [4 байта]

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

Если вы хотите знать, почему, скомпилируйте код на ассемблере, я считаю, что он -S на gcc и /S на компиляторе MS C. Если вы посмотрите на инструкции открытия этой функции, вы увидите, что старый указатель стека будет сохранен, а затем 32 (или что-то еще!) Вычитается из него. Оттуда вы можете увидеть, как код обращается к этому 32-байтовому блоку памяти и выясняет, что делает ваш компилятор. В конце функции вы можете увидеть восстановленный указатель стека.

Ответ 11

Это зависит от вашей операционной системы и вашего компилятора.

Ответ 12

Стек растет. Итак, f (g (h())), стек, выделенный для h, начнется с более низкого адреса, тогда g и g будут ниже, чем f. Но переменные в стеке должны следовать спецификации C,

http://c0x.coding-guidelines.com/6.5.8.html

1206 Если объекты, на которые указывают, являются членами одного и того же совокупного объекта, указатели на элементы структуры, объявленные позже, сравниваются больше, чем указатели на члены, объявленные ранее в структуре, и указатели на элементы массива с большими значениями индекса сравниваются больше, чем указатели на элементы того же массива с более низкими значениями индекса.

& a [0] & a [1], всегда должно быть истинным, независимо от того, как 'a' выделяется

Ответ 13

Я не думаю, что это детерминировано. Кажется, что массив "растет", потому что память должна быть распределена смежно. Однако, поскольку q и s вообще не связаны друг с другом, компилятор просто привязывает каждую из них в произвольной свободной ячейке памяти в стеке, возможно, те, которые лучше всего подходят для целочисленного размера.

То, что произошло между [2] и q, состоит в том, что пространство вокруг q не было достаточно большим (т.е. не более 12 байт), чтобы выделить 3 целых массива.