Что на самом деле означает «память, выделенная во время компиляции»?

159

В языках программирования, таких как C и C ++, люди часто ссылаются на статическое и динамическое распределение памяти. Я понимаю концепцию, но фраза «Вся память была выделена (зарезервирована) во время компиляции» всегда смущает меня.

Компиляция, насколько я понимаю, преобразует код высокого уровня C / C ++ в машинный язык и выводит исполняемый файл. Как распределяется память в скомпилированном файле? Разве память не всегда выделяется в ОЗУ всеми средствами управления виртуальной памятью?

Разве распределение памяти по определению не является концепцией времени выполнения?

Если я сделаю статически размещенную переменную размером 1 КБ в своем коде C / C ++, увеличит ли это размер исполняемого файла на ту же величину?

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

Назад к основам: распределение памяти, прогулка по истории

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

Ответы:

184

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

Например, рассмотрим глобальный массив:

int array[100];

Компилятор знает во время компиляции размер массива и размер int, поэтому он знает весь размер массива во время компиляции. Также глобальная переменная имеет статическую длительность хранения по умолчанию: она размещается в области статической памяти пространства памяти процесса (раздел .data / .bss). Учитывая эту информацию, компилятор решает во время компиляции, по какому адресу этой статической области памяти будет массив .

Конечно, адреса памяти - это виртуальные адреса. Программа предполагает, что она имеет собственное пространство памяти (например, от 0x00000000 до 0xFFFFFFFF). Вот почему компилятор может делать предположения типа «Хорошо, массив будет по адресу 0x00A33211». Во время выполнения эти адреса преобразуются в реальные / аппаратные адреса MMU и ОС.

Значение инициализированного статического хранилища вещей немного отличается. Например:

int array[] = { 1 , 2 , 3 , 4 };

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

Вот два примера сборки, сгенерированной компилятором (GCC4.8.1 с целью x86):

C ++ код:

int a[4];
int b[] = { 1 , 2 , 3 , 4 };

int main()
{}

Выходная сборка:

a:
    .zero   16
b:
    .long   1
    .long   2
    .long   3
    .long   4
main:
    pushq   %rbp
    movq    %rsp, %rbp
    movl    $0, %eax
    popq    %rbp
    ret

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

8.5.9 (Инициализаторы) [Примечание]:
Каждый объект статической длительности хранения инициализируется нулями при запуске программы перед любой другой инициализацией. В некоторых случаях дополнительная инициализация выполняется позже.

Я всегда предлагаю людям разобрать свой код, чтобы посмотреть, что на самом деле делает компилятор с кодом C ++. Это относится от классов хранения / продолжительности (как этот вопрос) к расширенной оптимизации компилятора. Вы могли бы поручить вашему компилятору сгенерировать сборку, но есть замечательные инструменты, чтобы сделать это в Интернете дружественным образом. Мой любимый это GCC Explorer .

Manu343726
источник
2
Спасибо. Это многое проясняет. Таким образом, компилятор выводит что-то эквивалентное «зарезервировать память от 0xABC до 0xXYZ для массива переменных [] и т. Д.» а затем загрузчик использует это, чтобы действительно выделить его непосредственно перед запуском программы?
Талха Сайед
1
@TalhaСказал точно. Смотрите правку, чтобы посмотреть на пример
Manu343726
2
@ Сецко Я упростил вещи. Только упоминание о программе работает через виртуальную память, но поскольку вопрос не о виртуальной памяти, я не расширил тему. Я только указал, что компилятор может делать предположения об адресах памяти во время компиляции благодаря виртуальной памяти.
Manu343726
2
@ Да, да. ммм "сгенерированный" это лучший термин, я думаю.
Manu343726
2
«Это выделено в статической области молочной железы пространства памяти процесса». Чтение, которое выделило некоторые статические области молочной железы в моей области памяти процесса.
Radiodef
27

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

Разве распределение памяти по определению не является концепцией времени выполнения?

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

Если я сделаю статически размещенную переменную размером 1 КБ в своем коде C / C ++, увеличит ли это размер исполняемого файла на ту же величину?

Простое объявление статического кода не увеличит размер вашего исполняемого файла более чем на несколько байтов. Объявление его с ненулевым начальным значением будет (для удержания этого начального значения). Скорее, компоновщик просто добавляет эту сумму в 1 КБ к требованию к памяти, которое системный загрузчик создает для вас непосредственно перед выполнением.

ма
источник
1
если я напишу static int i[4] = {2 , 3 , 5 ,5 }, он увеличится на исполняемый размер на 16 байт. Вы сказали: «Простое объявление статического значения не приведет к увеличению размера исполняемого файла более чем на несколько байтов. Объявление его с начальным значением, отличным от нуля». Объявление его с начальным значением будет иметь значение.
Сурадж Джейн
Ваш исполняемый файл имеет две области для статических данных - одну для неинициализированной статики и одну для инициализированной статики. Неинициализированная область - это просто указание размера; когда ваша программа запускается, этот размер используется для увеличения статической области хранения, но самой программе не нужно было хранить что-то большее, чем количество неинициализированных данных. Для инициализированной статики ваша программа должна содержать не только размер (каждого) статика, но и то, к чему она инициализируется. Таким образом, в вашем примере в вашей программе будет 2, 3, 5 и 5.
17
Его реализация определяется тем, где он находится / как он распределяется, но я не уверен, что понимаю необходимость знать.
ма
23

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

char a[32];
char b;
char c;

Эти 3 переменные «выделяются во время компиляции», это означает, что компилятор вычисляет их размер (который является фиксированным) во время компиляции. Переменная aбудет смещением в памяти, скажем, указывая на адрес 0, bбудет указывать на адреса 33 и c34 (при условии отсутствия оптимизации выравнивания). Таким образом, выделение 1 КБ статических данных не увеличит размер вашего кода , поскольку он просто изменит смещение внутри него. Фактическое пространство будет выделено во время загрузки .

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

Помните также, что мы говорим об относительных адресах . Реальный адрес, где будет расположена переменная, будет другим. Во время загрузки ядро ​​зарезервирует некоторую память для процесса, скажем, по адресу x, и все жестко закодированные адреса, содержащиеся в исполняемом файле, будут увеличены на xбайты, так что переменная aв примере будет по адресу x, b по адресу x+33и скоро.

fede1024
источник
17

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

Если 1k - строка из тысячи символов, которая объявлена ​​так

const char *c_string = "Here goes a thousand chars...999";//implicit \0 at end

и тогда vim your_compiled_binвы должны были увидеть эту строку в мусорном ведре. В таком случае, да: исполняемый файл будет на 1 кб больше, потому что он содержит строку полностью.
Однако, если вы размещаете массив ints, chars или longs в стеке и назначаете его в цикле, что-то вроде этого

int big_arr[1000];
for (int i=0;i<1000;++i) big_arr[i] = some_computation_func(i);

тогда, нет: это не увеличит корзину ... 1000*sizeof(int)
Распределение во время компиляции означает, что вы теперь поняли, что это означает (на основе ваших комментариев): скомпилированная корзина содержит информацию, необходимую системе, чтобы узнать, сколько памяти какая функция / блок понадобится при запуске, а также информация о размере стека, необходимого вашему приложению. Это то, что система выделит, когда выполнит ваш бин, и ваша программа станет процессом (ну, выполнение вашего бина - это процесс, который ... ну, вы понимаете, что я говорю).
Конечно, я не рисую полную картину здесь: корзина содержит информацию о том, какой большой стек будет на самом деле нужен корзине. Основываясь на этой информации (помимо прочего), система зарезервирует кусок памяти, называемый стеком, для которого программа получает вид свободного управления. Стековая память все еще выделяется системой, когда инициируется процесс (результат выполнения вашего бина). Затем процесс управляет памятью стека для вас. Когда функция или цикл (любой тип блока) вызывается / выполняется, переменные, локальные для этого блока, помещаются в стек и удаляются ( так сказать, память стека «освобождается» ) для использования другими функции / блоки. Так декларируяint some_array[100]добавит только несколько байтов дополнительной информации в корзину, которая сообщит системе, что для функции X потребуется 100*sizeof(int)+ некоторое дополнительное место для хранения.

Элиас Ван Отегем
источник
Большое спасибо. Еще один вопрос: локальные переменные для функций также распределяются одинаково во время компиляции?
Талха Сайед
@TalhaSayed: Да, именно это я и имел в виду, когда сказал: «Информация, которую система должна знать, сколько памяти потребуется для функции / блока». В тот момент, когда вы вызываете функцию, система выделяет необходимую память для этой функции. В тот момент, когда функция возвращается, эта память снова будет освобождена.
Элиас Ван Отегем
Что касается комментариев в вашем C-коде: это не совсем / обязательно то, что происходит. Например, строка, скорее всего, будет выделена только один раз, во время компиляции. Таким образом, он никогда не «освобождается» (также я думаю, что терминология обычно используется только тогда, когда вы выделяете что-то динамически), iне «освобождается» или то и другое. Если бы он iнаходился в памяти, он просто был бы помещен в стек, что-то, что не освобождается в этом смысле слова, игнорируя это iили cбудет храниться в регистрах все время. Конечно, все зависит от компилятора, а это значит, что он не такой уж черно-белый.
phant0m
@ phant0m: я никогда не говорил, что строка размещается в стеке, только указатель тоже будет, сама строка будет находиться в постоянной памяти. Я знаю, что память, связанная с локальными переменными, не освобождается в смысле free()вызовов, но используемая ими стековая память свободна для использования другими функциями, когда функция, которую я перечислил, возвращает. Я удалил код, так как он может вводить некоторых в заблуждение
Элиас Ван Отегем
Ах я вижу. В этом случае примите мой комментарий так, чтобы он означал: «Я был озадачен вашей формулировкой».
phant0m
16

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

int a;
const int b[6] = {1,2,3,4,5,6};
char c[200];
const int d = 23;
int e[4] = {1,2,3,4};
int f;

он сказал бы компоновщику, что ему нужно 208 байтов для bss, 16 байтов для «данных» и 28 байтов для «const». Кроме того, любая ссылка на переменную будет заменена селектором области и смещением, поэтому a, b, c, d и e будут заменены на bss + 0, const + 0, bss + 4, const + 24, data +0 или bss + 204 соответственно.

Когда программа связана, все области bss из всех модулей объединяются вместе; аналогично данным и константным областям. Для каждого модуля адрес любых относительных к bss переменных будет увеличен на размер областей bss всех предыдущих модулей (опять же, аналогично данным и const). Таким образом, когда компоновщик завершен, любая программа будет иметь одно распределение bss, одно распределение данных и одно постоянное распределение.

Когда программа загружается, в зависимости от платформы обычно происходит одна из четырех вещей:

  1. Исполняемый файл будет указывать, сколько байтов ему нужно для каждого типа данных и - для области инициализированных данных, где может быть найдено начальное содержимое. Он также будет включать в себя список всех инструкций, которые используют адрес bss, data или const. Операционная система или загрузчик будет выделять соответствующий объем пространства для каждой области, а затем добавлять начальный адрес этой области к каждой инструкции, которая в этом нуждается.

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

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

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

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

Supercat
источник
13

Суть вашего вопроса заключается в следующем: «Как распределяется память» в скомпилированном файле? Разве память не всегда выделяется в ОЗУ всеми средствами управления виртуальной памятью? Разве распределение памяти по определению не является концепцией времени выполнения? »

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

  • Некоторая система используется для определения виртуального адреса, по которому элемент будет храниться
  • Виртуальный адрес сопоставлен с физическим адресом

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

  • Компилятор видит исходный файл, содержащий строку, которая выглядит примерно так:

    int c;
  • Он производит вывод для ассемблера, который инструктирует его зарезервировать память для переменной 'c'. Это может выглядеть так:

    global _c
    section .bss
    _c: resb 4
  • Когда ассемблер работает, он сохраняет счетчик, который отслеживает смещения каждого элемента с начала сегмента памяти (или «секции»). Это похоже на части очень большой «структуры», которая содержит все во всем файле, в настоящее время ей не выделено никакой фактической памяти, и она может находиться где угодно. Это отмечает в таблице, что_c конкретное смещение (скажем, 510 байт от начала сегмента), а затем увеличивает его счетчик на 4, поэтому следующая такая переменная будет иметь значение (например, 514 байт). Для любого кода, которому нужен адрес _c, он просто помещает 510 в выходной файл и добавляет примечание, что для вывода нужен адрес сегмента, который содержит _cдобавление к нему позже.

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

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

Жюль
источник
9

Исполняемый файл описывает, какое пространство выделить статическим переменным. Это распределение выполняется системой при запуске исполняемого файла. Таким образом, ваша статическая переменная 1 КБ не увеличит размер исполняемого файла на 1 КБ:

static char[1024];

Если, конечно, вы не укажете инициализатор:

static char[1024] = { 1, 2, 3, 4, ... };

Таким образом, в дополнение к «машинному языку» (т. Е. Инструкциям процессора) исполняемый файл содержит описание требуемой структуры памяти.

осмысленные вопросы
источник
5

Память может быть распределена разными способами:

  • в куче приложения (целая куча выделяется для вашего приложения ОС при запуске программы)
  • в куче операционной системы (так что вы можете получить все больше и больше)
  • в сборщике мусора контролируется куча (такая же как и выше)
  • в стеке (так что вы можете получить переполнение стека)
  • зарезервировано в сегменте кода / данных вашего двоичного файла (исполняемый файл)
  • в удаленном месте (файл, сеть - и вы получите дескриптор, а не указатель на эту память)

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

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

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

exebook
источник
1
Этот ответ сбивает с толку (или вводит в заблуждение) то, что в нем говорится о «куче приложений», «куче ОС» и «куче GC», как будто это все значимые понятия. Я заключаю, что под номером 1 вы пытались сказать, что некоторые языки программирования могут (гипотетически) использовать схему «выделения кучи», которая выделяет память из буфера фиксированного размера в разделе .data, но это кажется нереальным, чтобы быть вредным к пониманию ФП. Что касается № 2 и № 3, присутствие GC на самом деле ничего не меняет. И в отношении № 5, вы пропустили ГЛАВНОЕ более важное различие между .dataи .bss.
Quuxplusone
4

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

Ив Дауст
источник
2

Я думаю, тебе нужно немного отступить. Память выделяется во время компиляции .... Что это может означать? Может ли это означать, что память на чипах, которые еще не были изготовлены, для компьютеров, которые еще не были спроектированы, каким-то образом резервируется? Нет, путешествие во времени, нет компиляторов, которые могут манипулировать вселенной.

Таким образом, это должно означать, что компилятор генерирует инструкции для выделения этой памяти каким-либо образом во время выполнения. Но если вы посмотрите на это с правильной стороны, компилятор генерирует все инструкции, так в чем же может быть разница. Разница в том, что компилятор принимает решение, и во время выполнения ваш код не может изменить или изменить свои решения. Если он решил, что ему нужно 50 байтов во время компиляции, во время выполнения вы не можете заставить его принять решение выделить 60 - это решение уже принято.

jmoreno
источник
Мне нравятся ответы, в которых используется метод Сократа, но я все же отказался от вас за ошибочный вывод, что «компилятор генерирует инструкции для того, чтобы как-то выделить эту память во время выполнения». Посмотрите ответ с наибольшим количеством голосов, чтобы увидеть, как компилятор может «распределять память», не генерируя никаких «инструкций» во время выполнения. (Обратите внимание, что «инструкции» в контексте языка ассемблера имеют особое значение, то есть исполняемые коды операций. Возможно , вы использовали слово в разговорной речи для обозначения чего-то вроде «рецепт», но в этом контексте это просто запутает ОП. )
Quuxplusone
1
@Quuxplusone: я прочитал (и проголосовал) этот ответ. И нет, мой ответ не касается конкретно инициализированных переменных. Это также не касается самоизменяющегося кода. Хотя этот ответ превосходен, он не затрагивает то, что я считаю важной проблемой - поместить вещи в контекст. Отсюда мой ответ, который, я надеюсь, поможет ОП (и другим) остановиться и подумать о том, что происходит или может происходить, когда у них есть проблемы, которые они не понимают.
Jmoreno
@Quuxplusone: Извините, если я здесь выдвигаю ложные обвинения, но я так понимаю, вы были одним из тех, кто тоже ответил на мой вопрос. Если да, не могли бы вы ужасно указать, какая часть моего ответа была основной причиной для этого, и не могли бы вы также проверить мою правку? Я знаю, что пропустил несколько подробностей об истинных внутренних принципах управления стековой памятью, поэтому теперь я добавил немного о том, что я в любом случае не на 100% точен к своему ответу :)
Элиас Ван Оотегем,
@jmoreno Смысл, который вы высказали по поводу «Может ли это означать, что память на чипах, которые еще не были изготовлены, для компьютеров, которые еще не были спроектированы, каким-то образом резервируется? Нет». это именно то ложное значение, которое подразумевает слово «распределение», которое смутило меня с самого начала. Мне нравится этот ответ, потому что он касается именно той проблемы, на которую я пытался указать. Ни один из ответов здесь действительно не затрагивал этот конкретный момент. Спасибо.
Талха Сайед
2

Если вы изучите программирование на ассемблере, вы увидите, что вам нужно выделить сегменты для данных, стека, кода и т. Д. Сегмент данных - это место, где живут ваши строки и числа. Сегмент кода - это место, где живет ваш код. Эти сегменты встроены в исполняемую программу. Конечно, размер стека также важен ... вы бы не хотели переполнения стека !

Таким образом, если ваш сегмент данных имеет длину 500 байт, ваша программа имеет область размером 500 байт. Если вы измените сегмент данных на 1500 байт, размер программы будет на 1000 байт больше. Данные собраны в фактическую программу.

Это то, что происходит, когда вы компилируете языки более высокого уровня. Фактическая область данных выделяется, когда она компилируется в исполняемую программу, увеличивая размер программы. Программа также может запрашивать память на лету, и это динамическая память. Вы можете запросить память из ОЗУ, и ЦП предоставит ее вам для использования, вы можете отпустить ее, и ваш сборщик мусора вернет ее обратно в ЦП. При необходимости, он может быть перенесен на жесткий диск хорошим менеджером памяти. Эти функции - то, что предоставляют языки высокого уровня.

инженер
источник
2

Я хотел бы объяснить эти понятия с помощью нескольких диаграмм.

Это правда, что память не может быть выделена во время компиляции, наверняка. Но то, что происходит на самом деле во время компиляции.

Вот и объяснение. Скажем, например, программа имеет четыре переменные x, y, z и k. Теперь, во время компиляции, он просто создает карту памяти, где определяется местоположение этих переменных относительно друг друга. Эта диаграмма проиллюстрирует это лучше.

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

пустое поле

Далее выполняется первый экземпляр этой программы. Вы можете визуализировать это следующим образом. Это время, когда фактически выделяется память.

первый случай

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

второй экземпляр

И третий ..

третий экземпляр

Так далее и тому подобное.

Я надеюсь, что эта визуализация хорошо объясняет эту концепцию.

user3258051
источник
2
Если бы эти диаграммы показали разницу между статической и динамической памятью, они были бы более полезными ИМХО.
Бартек Банахевич
Я сознательно избегал этого, чтобы все было просто. Моя цель состоит в том, чтобы объяснить эту основу с ясностью без особых технических помех. Насколько это предназначено для статической переменной. Эта точка была хорошо установлена ​​предыдущими ответами. Так что я пропустил это.
user3258051
1
Э-э, эта концепция не особенно сложна, поэтому я не понимаю, почему сделать ее проще, чем нужно, но, поскольку она предназначена только как дополнительный ответ, хорошо.
Бартек Банахевич
1

В принятом ответе дано очень хорошее объяснение. На всякий случай я опубликую ссылку, которую я нашел полезной. https://www.tenouk.com/ModuleW.html

user6882413
источник