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



memory in c (9)

У меня есть эта часть кода в 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 вещи:

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

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

https://ffff65535.com


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

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

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

См. 1 мой прекрасный трактат о направлении стека :-)

Отвечая на ваши конкретные вопросы:

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

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

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;
}

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

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


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

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

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

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

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

Стек для разных архитектур может расти в любом случае, но для архитектуры она будет последовательной. Проверьте this ссылку в вики. Но рост стека определяется 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», которая является первой выделенной стековой памятью. Но, как правило, компилятор выполняет распределение памяти по стеклу в соответствии с порядком объявления переменных).

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


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

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


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

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

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

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


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


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

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

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

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

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





stack