Стек времени выполнения хранится в сегменте данных памяти? - PullRequest
2 голосов
/ 29 апреля 2009

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

У меня есть фундаментальная программа - имеет 2 функции, одна из которых foo, а другая главная (точка входа).

void foo(){
    // do something here or dont
}

int main(){

    int i = 0;

    printf("%p %p %p\n",foo, &i, main);

    system("PAUSE");
    return EXIT_SUCCESS;
};

Вывод программы показан ниже, локальная переменная main находится полностью в несвязанном положении. integer является типом значения, но проверил его снова с указателем char *, локальным для main, и получил аналогичные результаты.

00401390 0022FF44 00401396
Press any key to continue . . .

Я в основном понимаю, что код и переменные размещены в разных сегментах памяти (сегмент кода / сегмент данных). Так что в целом правильно сказать, что стек вызовов сворачивает основную информацию о выполнении функций (их локальные переменные, параметры, возвращаемые точки) и сохраняет их в сегменте данных?

Ответы [ 5 ]

3 голосов
/ 29 апреля 2009

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

Но основной ответ, как сказали @Richie и @Paul, "да". Когда ваш компилятор и компоновщик завершают работу с кодом, он разбивается на так называемые сегменты «текст» и «данные» в UNIX. текстовый сегмент содержит инструкции и некоторые виды статических данных; сегмент данных содержит, ну, данные.

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

Так что да, когда программа запускается, счетчик программ активно извлекает инструкции из сегмента, отличного от данных. Теперь мы попадаем в некоторые архитектурные зависимости, но в целом, если у вас есть сегментированная память, ваши инструкции построены таким образом, что выборка байта из сегментов максимально эффективна. В старой архитектуре 360 они имели базовых регистров , в x86 есть куча волос, которые росли по мере того, как адресное пространство переходило на старые 8080-е годы для современных процессоров, но все инструкции очень тщательно оптимизированы, поскольку, как вы можете себе представить, выборка инструкций и их операндов очень интенсивна б.

Теперь перейдем к более современным архитектурам с виртуальной памятью и блоком управления памятью s. Теперь у машины есть специальное оборудование, которое позволяет программе обрабатывать адресное пространство как большой плоский диапазон адресов; различные сегменты просто помещаются в это битовое виртуальное адресное пространство. Работа MMU состоит в том, чтобы взять виртуальный адрес и преобразовать его в физический адрес, в том числе, что делать, если этот виртуальный адрес вообще не находится в физической памяти в данный момент. Опять же, аппаратное обеспечение MMU очень сильно оптимизировано, но это не означает, что с производительностью связаны нет производительности. Но поскольку процессоры стали быстрее, а программы стали больше, это становится все менее и менее важным.

1 голос
/ 29 апреля 2009

Да, это точно. Код и данные находятся в разных частях памяти с разными разрешениями. Стек хранит параметры, адреса возврата и локальные («автоматические») переменные и живет с данными.

0 голосов
/ 29 апреля 2009

Ну, я могу говорить за SPARC:

Да. Когда вы запускаете программу, программа читается дважды (по крайней мере, в SPARC). Программа загружается в память, и любые распределения массива / стека загружаются впоследствии. На втором проходе программы стеки выделяются в отдельную память.

Я не уверен, что процессоры на базе CISC, но я подозреваю, что он не сильно отличается

0 голосов
/ 29 апреля 2009

Ваша программа демонстрирует неопределенное поведение именно потому, что:

  • вы не можете включить <stdio.h> или <cstdio> в зависимости от языка, на котором вы компилируете код, как
  • printf и все функции переменных аргументов не имеют возможности проверять тип своих аргументов. Следовательно, с вашей стороны обязательно передать правильно введенные аргументы. Вы действительно должны сделать:
  • system() не имеет декларации в области видимости. Включите <stdlib.h> или <cstdlib> в зависимости от обстоятельств.

Введите свой код как:

   #include <stdio.h>

   int main() {
      /* ... */
      printf("%p %p %p\n", (void *)foo, (void *)&i, (void *)main);
      /* ... */
   }

Также обратите внимание, что:

  • Определение void foo() не является прототипом в C, но в C ++. Однако, если бы вы написали void foo(void), вы бы получили прототип на обоих языках.
  • system() зависит от реализации - ваш код может работать неправильно на разных платформах.

Собственно язык (C или C ++) не накладывает никаких ограничений на организацию памяти. У этого даже нет понятия стека или кучи. Они определяются реализациями так, как они считают нужным. В идеале вам следует обратиться к документации, предоставленной вашей реализацией, чтобы получить представление о том, что они делают.

0 голосов
/ 29 апреля 2009

Да.

Представьте, что ваша память кода - это ПЗУ, а память данных - это ОЗУ (обычная архитектура с небольшим количеством микросхем). Затем вы видите, что стек должен находиться в памяти данных.

...