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



у меня есть этот кусок кода в C:



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

printf("Address of a: %dn", (int)a);
printf("Address of a[1]: %dn", (int)&a[1]);
printf("Address of a[2]: %dn", (int)&a[2]);
printf("Address of q: %dn", (int)&q);
printf("Address of s: %dn", (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 байт).


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

910   13  

13 ответов:

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

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

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

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

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

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

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

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

направление роста стека не зависит от расположения отдельного объекта. Поэтому, хотя стек может вырасти, массивы не будут (т. е. & array[n] всегда будет

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

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

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

посмотреть здесь за мой превосходный Трактат о направлении стека: -)

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

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

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

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

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

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

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

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

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

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

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

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

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

$ 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

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

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). Однако стек выделяется в один блок при загрузке функции, и у вас нет гарантии, в каком порядке элементы будут находиться в стеке.

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

a [12 байт]
обивка(?) [12 байтов]
s [4 байта]
q [4 байта]

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

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

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

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

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

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

&a[0]

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

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

Comments

    Ничего не найдено.