Где в памяти мои переменные хранятся в C?

156

Учитывая, что память разделена на четыре сегмента: данные, куча, стек и код, где находятся глобальные переменные, статические переменные, постоянные типы данных, локальные переменные (определенные и объявленные в функциях), переменные (в основной функции), указатели и динамически выделенное пространство (используя malloc и calloc) хранится в памяти?

Я думаю, что они будут распределены следующим образом:

  • Глобальные переменные -------> данные
  • Статические переменные -------> данные
  • Постоянные типы данных -----> код
  • Локальные переменные (объявленные и определенные в функциях) --------> stack
  • Переменные, объявленные и определенные в основной функции -----> heap
  • Указатели (например, char *arr, int *arr) -------> кучного
  • Динамически распределяемое пространство (используя malloc и calloc) --------> стек

Я имею в виду эти переменные только с точки зрения Си.

Пожалуйста, поправьте меня, если я ошибаюсь, поскольку я новичок в C.

starkk92
источник
4
Типы не хранятся в памяти.
5
mainэто просто другая функция. Переменные помещаются в стек, если mallocтолько они не похожи на другие.
simonc
4
указатели (обычно) хранятся в стеке. Память, на которую они указывают (обычно выделяемая через malloc / calloc), находится (обычно) в куче.
jpm
3
динамически распределяемое пространство (используя malloc, calloc) --------> heap
One Man Crew
3
переменные объявлены и определены в основной функции -----> stack
One Man Crew

Ответы:

217

Вы правильно поняли некоторые из них, но тот, кто написал вопросы, обманул вас, по крайней мере, на один вопрос:

  • глобальные переменные -------> данные (правильно)
  • статические переменные -------> данные (правильно)
  • постоянные типы данных -----> код и / или данные. Рассмотрим строковые литералы для ситуации, когда сама константа будет храниться в сегменте данных, а ссылки на нее будут встроены в код
  • локальные переменные (объявленные и определенные в функциях) --------> stack (правильно)
  • переменные объявлены и определены в mainфункции -----> heap также стек (учитель пытался обмануть вас)
  • указатели (например: char *arr, int *arr) -------> куча данных или стека, в зависимости от контекста. C позволяет вам объявить глобальный илиstatic указатель, и в этом случае сам указатель окажется в сегменте данных.
  • динамически распределяемой пространство (используя malloc, calloc, realloc) --------> стека кучи

Стоит отметить, что «стек» официально называется «классом автоматического хранения».

dasblinkenlight
источник
6
Также стоит упомянуть, что кучи официально ничего не называют вообще. Выделенная память приходит откуда-то, в стандарте нет названия для этого «где-то».
Стив Джессоп
6
В некоторых системах (а именно в Linux и * BSD) также существует система, allocaкоторая работает аналогично malloc, но выполняет распределение в стеке.
Андреас Грапентин
Куда идут переменные const, объявленные внутри метода?
Махори
@Ravi Там же, где и остальные константы (точка № 3 выше).
dasblinkenlight
Я использую GCC 4.8.1, и он не хранит переменную const local to main в сегменте DATA. Ниже приведен код и карта памяти для 3 таких программ: Код 1: int main (void) {// char a [10] = "HELLO"; // 1 // const char a [10] = "HELLO"; // 2 return 0; } КАРТА ПАМЯТИ ВЫШЕ: текстовые данные bss dec hex имя файла 7264 1688 1040 9992 2708 a.exe КАРТА ПАМЯТИ 2: текстовые данные bss dec hex имя файла 7280 1688 1040 10008 2718 a.exe КАРТА ПАМЯТИ 3: текстовые данные bss dec hex имя файла 7280 1688 1040 10008 2718 a.exe
Махори
124

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

Некоторые головы:

  1. Всякий раз, когда выполняется программа на C, в оперативной памяти выделяется некоторая память для выполнения программы. Эта память используется для хранения часто исполняемого кода (двоичные данные), программных переменных и т. Д. Сегменты памяти ниже говорят о том же:
  2. Обычно существует три типа переменных:
    • Локальные переменные (также называемые автоматическими переменными в C)
    • Глобальные переменные
    • Статические переменные
    • Вы можете иметь глобальные статические или локальные статические переменные, но вышеприведенные три являются родительскими типами.

5 сегментов памяти в C:

1. Сегмент кода

  • Сегмент кода, также называемый текстовым сегментом, является областью памяти, которая содержит часто выполняемый код.
  • Сегмент кода часто доступен только для чтения, чтобы избежать риска переопределения при программировании ошибок, таких как переполнение буфера и т. Д.
  • Сегмент кода не содержит программных переменных, таких как локальные переменные ( также называемые автоматическими переменными в C ), глобальные переменные и т. Д.
  • На основе реализации C сегмент кода может также содержать строковые литералы, доступные только для чтения. Например, когда вы делаете, printf("Hello, world")то строка «Hello, world» создается в сегменте кода / текста. Вы можете проверить это используяsize команды в ОС Linux.
  • дальнейшее чтение

Сегмент данных

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

2. Неинициализированный сегмент данных

  • Этот сегмент также известен как BSS .
  • Это часть памяти, которая содержит:
    1. Неинициализированные глобальные переменные (включая переменные-указатели)
    2. Неинициализированные постоянные глобальные переменные .
    3. Неинициализированные локальные статические переменные .
  • Любая глобальная или статическая локальная переменная, которая не инициализирована, будет сохранена в неинициализированном сегменте данных.
  • Например: глобальная переменная int globalVar;или статическая локальная переменная static int localStatic;будут храниться в неинициализированном сегменте данных.
  • Если вы объявляете глобальную переменную и инициализируете ее как 0или NULLтогда, она все равно перейдет к неинициализированному сегменту данных или bss.
  • дальнейшее чтение

3. Инициализированный сегмент данных

  • Этот сегмент магазинов:
    1. Инициализированные глобальные переменные (включая переменные-указатели)
    2. Инициализированные постоянные глобальные переменные .
    3. Инициализированные локальные статические переменные .
  • Например: глобальная переменная int globalVar = 1;или статическая локальная переменная static int localStatic = 1;будут храниться в инициализированном сегменте данных.
  • Этот сегмент может быть дополнительно классифицирован на инициализированную область только для чтения и инициализированную область чтения-записи . Инициализированные постоянные глобальные переменные попадут в инициализированную область только для чтения, а переменные, значения которых могут быть изменены во время выполнения, попадут в инициализированную область чтения-записи .
  • Размер этого сегмента определяется размером значений в исходном коде программы и не изменяется во время выполнения .
  • дальнейшее чтение

4. Сегмент стека

  • Сегмент стека используется для хранения переменных, которые создаются внутри функций ( функция может быть основной или определяемой пользователем функцией ), например, переменная
    1. Локальные переменные функции (включая переменные-указатели)
    2. Аргументы переданы в функцию
    3. Обратный адрес
  • Переменные, хранящиеся в стеке, будут удалены, как только закончится выполнение функции.
  • дальнейшее чтение

5. Сегмент кучи

  • Этот сегмент должен поддерживать динамическое распределение памяти. Если программист хочет выделить память динамически , то в C это делается с помощью malloc, callocилиrealloc методов.
  • Например, когда int* prt = malloc(sizeof(int) * 2)в куче будет выделено восемь байтов, адрес памяти этого местоположения будет возвращен и сохранен в ptrпеременной. ptrПеременный будут либо на стек данных или сегменты в зависимости от того, как она объявлена / используются.
  • дальнейшее чтение
hagrawal
источник
Не следует ли это инициализировать, а не инициализировать в 3. Инициализированный сегмент данных.
Сурадж Джейн
Re «хранится в неинициализированном сегменте данных» (несколько экземпляров): Вы имеете в виду «хранится в неинициализированном сегменте данных» ?
Питер Мортенсен
@PeterMortensen Я имею в виду обе вещи. «Любая глобальная или статическая локальная переменная, которая не инициализирована, будет сохранена в неинициализированном сегменте данных»
hagrawal
Как мы можем иметь глобальную статическую переменную в C?
в разделе "некоторые заголовки" я обнаружил этот пункт "У вас могут быть глобальные статические или локальные статические переменные, но вышеприведенные три являются родительскими типами" в котором вы упомянули термин «глобальная статика». Моя точка зрения - статическая переменная не может быть глобальной. т. е. если какая-либо переменная должна быть глобальной, она должна быть доступна до завершения выполнения программы. Пожалуйста, объясните и помогите, если я ошибаюсь.
11

Исправил ваши неправильные предложения

constant data types ----->  code //wrong

локальные постоянные переменные -----> стек

инициализированная глобальная постоянная переменная -----> сегмент данных

неинициализированная глобальная постоянная переменная -----> bss

variables declared and defined in main function  ----->  heap //wrong

переменные объявлены и определены в основной функции -----> stack

pointers(ex:char *arr,int *arr) ------->  heap //wrong

dynamically allocated space(using malloc,calloc) --------> stack //wrong

указатели (например: char * arr, int * arr) -------> размер этой переменной указателя будет в стеке.

Учтите, что вы выделяете память из n байтов (используя mallocили calloc) динамически, а затем делаете указатель переменным, чтобы указать его. Теперь, когда nбайты памяти находятся в куче, а переменная-указатель требует 4 байта (если 64-битная машина 8 байтов), которые будут в стеке для хранения начального указателя nбайтов блока памяти.

Примечание: переменные-указатели могут указывать на память любого сегмента.

int x = 10;
void func()
{
int a = 0;
int *p = &a: //Now its pointing the memory of stack
int *p2 = &x; //Now its pointing the memory of data segment
chat *name = "ashok" //Now its pointing the constant string literal 
                     //which is actually present in text segment.
char *name2 = malloc(10); //Now its pointing memory in heap
...
}

динамически распределяемое пространство (используя malloc, calloc) --------> heap

rashok
источник
указатели могут быть либо в стеке, либо в куче (см. особенно: указатели на указатели)
argentage
@airza: сейчас обновлено. На самом деле я обновлял только эти детали :)
rashok
На следующей карте памяти, не могли бы вы указать, где находится стек и куча? Я не уверен, что это правильный вопрос, так как стек и память могут быть применимы только во время выполнения. КАРТА ПАМЯТИ: «текстовые данные bss dec hex имя файла 7280 1688 1040 10008 2718 a.exe»
Махори
7

Популярная настольная архитектура делит виртуальную память процесса на несколько сегментов :

  • Текстовый сегмент: содержит исполняемый код. Указатель инструкции принимает значения в этом диапазоне.

  • Сегмент данных: содержит глобальные переменные (то есть объекты со статической связью). Подразделяются на данные только для чтения (например, строковые константы) и неинициализированные данные («BSS»).

  • Сегмент стека: содержит динамическую память для программы, т.е. свободное хранилище («куча») и локальные кадры стека для всех потоков. Традиционно стек C и куча C врастали в сегмент стека с противоположных концов, но я считаю, что от практики отказались, потому что это слишком небезопасно.

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

На других платформах, таких как старый реальный режим x86 или на встроенных устройствах, очевидно, что все может быть совершенно иначе.

Керрек С.Б.
источник
«Я считаю, что от практики отказались, потому что она слишком небезопасна» - и делает невозможным внедрение потоков, так как тогда вам нужно более одного стека на программу, и все они не могут быть в конце :-)
Стив Джессоп
@SteveJessop: Да, я тоже так думал. Но потоки существовали долгое время - я не знаю, выросли ли все стеки потоков назад или же они выросли бы как куча ... во всяком случае, в настоящее время все идет в одном направлении, и есть охрана страницы.
Керрек С.Б.
6

Я имею в виду эти переменные только с точки зрения Си.

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

Джон Боде
источник
3

Одна вещь, которую нужно помнить о хранилище - это правило « как будто» . Компилятору не требуется помещать переменную в определенное место - вместо этого он может размещать ее там, где ему заблагорассудится, до тех пор, пока скомпилированная программа ведет себя так, как если бы она выполнялась на абстрактной машине C в соответствии с правилами абстрактной машины C. Это относится ко всем хранения длительностей . Например:

  • переменная, к которой не обращаются все, может быть полностью удалена - она ​​не имеет хранения ... нигде. Пример - посмотрите, как есть 42в сгенерированном коде сборки, но нет признаков 404.
  • переменная с автоматическим сроком хранения, для которой не указан адрес, вообще не должна храниться в памяти. Примером может быть переменная цикла.
  • переменная, которая есть constили фактически constне должна быть в памяти. Пример - компилятор может доказать это fooэффективно constи встраивает его использование в код. barимеет внешнюю связь, и компилятор не может доказать, что он не будет изменен вне текущего модуля, поэтому он не встроен.
  • объект, выделенный без mallocнеобходимости, не должен находиться в памяти, выделенной из кучи! Пример - обратите внимание, что в коде нет вызова mallocи значение 42 никогда не сохраняется в памяти, оно сохраняется в регистре!
  • таким образом, объект, который был выделен mallocи ссылка потеряна без освобождения объекта, free не нуждается в утечке памяти ...
  • mallocнеобязательный объект не должен быть в куче ниже программы break ( sbrk(0)) в Unixen ...
Антти Хаапала
источник
1

указатели (например: char * arr, int * arr) -------> heap

Нет, они могут быть в стеке или в сегменте данных. Они могут указывать куда угодно.

Стив Велленс
источник
Утверждения о mainи динамически распределенных переменных тоже неверны
simonc
Не только в стеке или сегменте данных. Подумайте о указателе, который указывает на массив указателей. В этом случае указатели в массиве хранятся в куче.
Sebi2020
1
  • Переменные / автоматические переменные ---> секция стека
  • Динамически размещенные переменные ---> раздел кучи
  • Инициализированные глобальные переменные -> раздел данных
  • Неинициализированные глобальные переменные -> раздел данных (bss)
  • Статические переменные -> раздел данных
  • Строковые константы -> текстовая секция / секция кода
  • Функции -> текстовая секция / секция кода
  • Текстовый код -> текстовый раздел / кодовый раздел
  • Регистры -> регистры процессора
  • Ввод командной строки -> раздел среды / командной строки
  • Переменные среды -> раздел среды / командной строки
Prashad
источник
Что такое раздел среды / командной строки? Они существуют в Linux?
Haoyuan Ge
-1

Linux минимально работоспособные примеры с анализом дизассемблирования

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

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

Все они представлены в различных версиях Ubuntu / GCC, и результаты, вероятно, довольно стабильны во всех версиях, но если мы найдем какие-либо варианты, давайте уточним более точные версии.

Локальная переменная внутри функции

Будь то mainили любая другая функция:

void f(void) {
    int my_local_var;
}

Как показано на: Что означает <value optimized out> в gdb?

  • -O0: stack
  • -O3: регистрирует, если они не проливаются, укладываются иначе

Чтобы узнать, почему стек существует, смотрите: Какова функция инструкций push / pop, используемых для регистров в сборке x86?

Глобальные переменные и staticфункциональные переменные

/* BSS */
int my_global_implicit;
int my_global_implicit_explicit_0 = 0;

/* DATA */
int my_global_implicit_explicit_1 = 1;

void f(void) {
    /* BSS */
    static int my_static_local_var_implicit;
    static int my_static_local_var_explicit_0 = 0;

    /* DATA */
    static int my_static_local_var_explicit_1 = 1;
}
  • если инициализирован 0или не инициализирован (и, следовательно, неявно инициализирован 0):.bss section, см. также: Почему требуется сегмент .bss?
  • иначе: .dataраздел

char * и char c[]

Как показано на: Где хранятся статические переменные в C и C ++?

void f(void) {
    /* RODATA / TEXT */
    char *a = "abc";

    /* Stack. */
    char b[] = "abc";
    char c[] = {'a', 'b', 'c', '\0'};
}

TODO будут помещать в стек очень большие строковые литералы? Или.data ? Или компиляция не удалась?

Аргументы функции

void f(int i, int j);

Должны пройти соответствующее соглашение о вызовах, например: https://en.wikipedia.org/wiki/X86_calling_conventions для X86, в котором указываются либо конкретные регистры, либо места в стеке для каждой переменной.

Тогда, как показано в разделе Что означает <value optimized out> в gdb? , -O0То хлебает все в стек, в то время как-O3 пытается максимально использовать регистры.

Однако, если функция встроена, они обрабатываются как обычные локальные.

const

Я полагаю, что это не имеет значения, потому что вы можете настроить его.

И наоборот, если компилятор может определить, что некоторые данные никогда не записываются, он теоретически может поместить их, .rodataдаже если не const.

Анализ ТОДО.

указатели

Они являются переменными (которые содержат адреса, которые являются числами), так же, как и все остальные :-)

таНос

Вопрос не имеет особого смысла malloc, так как mallocявляется функцией, и в:

int *i = malloc(sizeof(int));

*i переменная, которая содержит адрес, поэтому она попадает в приведенный выше случай.

Что касается того, как malloc работает внутренне, когда вы называете его ядром Linux, он помечает определенные адреса как доступные для записи в своих внутренних структурах данных, и когда программа касается их изначально, происходит сбой, и ядро ​​активирует таблицы страниц, что дает доступ без segfaul: как работает подкачка x86?

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

Точный системный вызов используется для mallocэто mmapв современных реализациях 2020, а в прошлом brkбыл использован: Имеет ли таНос () использование битый () или ММАП ()?

Динамические библиотеки

Как правило, получить mmapдоступ к памяти: /unix/226524/what-system-call-is-used-to-load-libraries-in-linux/462710#462710

переменные окружения и mainsargv

Выше начального стека: /unix/75939/where-is-the-environment-string-actual-stored TODO, почему не в .data?

Сиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
источник