Когда / где размещены локальные массивы? - PullRequest
1 голос
/ 20 апреля 2019

https://www.gnu.org/software/libc/manual/html_node/Memory-Allocation-and-C.html описывает автоматическое распределение локальных переменных. Я понимаю, что локальные переменные обычно размещаются в стеке. Я могу себе представить, как int может быть выделено в стеке; просто подтолкнуть его значение. Но как разместить массив?

Например, если вы объявите массив char str[10];, будут ли эти 10 байт пространства помещаться в стек или он будет выделен где-то еще, и в стек помещается только указатель str? Если последнее, где выделено 10 байт пространства?

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

Редактировать: Я хотел бы подчеркнуть первую половину этого вопроса. Меня больше всего интересует понимание, когда и где выделяется место для локальных массивов. В стеке? Где-то еще?

Редактировать 2: Я допустил ошибку, когда изначально включил тег C ++ для этого вопроса. Я хотел спросить только о языке C и его реализациях. Прошу прощения за путаницу.

Ответы [ 3 ]

2 голосов
/ 20 апреля 2019

В стандарте C 2018, п. 6.2.4, параграфы 6 и 7 говорится о времени жизни объектов с автоматическим сроком хранения.Параграф 6 охватывает такие объекты, которые не являются массивами переменной длины:

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

Таким образом, если у нас есть этот код:

{
    PointA;
    int x = 3;
    PointB;
}

, тогда x существует в модели C, как только выполнение достигает PointA - его блок был введен, и это когда время жизни xначинается.Однако, хотя x уже существует в PointA, его значение является неопределенным.Инициализация происходит только при достижении определения.

В параграфе 7 говорится о массивах переменной длины:

… его время жизни продолжается от объявления объекта до тех пор, пока не завершится выполнение программы.объем декларации.

Итак, если у нас есть этот код:

{
    PointA;
    int x[n]; // n is some variable.
    PointB;
}

, то x не существует в PointA.Его время жизни начинается, когда достигается int x[n];.

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

0 голосов
/ 20 апреля 2019

Например, если вы объявляете массив char str [10] ;, эти 10 байт пространства уходят в стек или он выделяется где-то еще, и только указатель str помещается в стек? Если последнее, где выделено 10 байт пространства?

Обычно хранилище массива выделяется в стеке, как и любая другая локальная переменная. Это зависит от компилятора и цели. Даже на компьютере с архитектурой x86_64 массив в 4 миллиарда байт, вероятно, не выделяется в стеке. Я ожидаю одного из: ошибки компиляции, ошибки ссылки, ошибки времени выполнения, или это работает как-то. В последней альтернативе он может вызвать new[] или malloc() и оставить указатель на массив в стеке вместо массива.

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

0 голосов
/ 20 апреля 2019

То, что вы просите, зависит от языковой реализации (компилятор).Чтобы ответить на ваш вопрос, это (упрощенный обзор) того, что компиляторы обычно делают для компилируемых языков (например, C / C ++):

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

Таким образом, автоматические переменные, такие как ваш массив символов, полностью размещаются в стеке в этом (общем) сценарии.

[EDIT] Массивы переменной длины (до C99)

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

void f () {
    char n[10];
    ....
}

Если мы останемся в терминах языка C (до C99) массивы переменной длины (массивы, длины которых неизвестны во время компиляции, а скорее во время выполнения) объявляются как указатель так:

void f() {
    char *n;
    ... //array is later allocated using some kind of memory allocation construct
}

Это, фактически, просто объявляетуказатель на массив.Размер указателей известен компилятору.Итак, как я сказал выше, компилятор сможет зарезервировать необходимое хранилище для указателя на стек (только указатель, а не реальный массив) независимо от того, каким будет размер массива во время выполнения.Когда выполнение достигает строки, которая выделяет массив (например, с помощью malloc), хранилище массива выделяется динамически в куче, а его адрес сохраняется в локальной автоматической переменной n.В языках без сборки мусора это требует освобождения (освобождения) зарезервированного хранилища из кучи вручную (т.е. программист должен добавить инструкцию, чтобы сделать это в программе, когда массив больше не нужен).Это не является необходимым для массива постоянного размера (который размещается в стеке), поскольку компилятор удаляет кадр стека перед возвратом из функции, как я говорил ранее.

[EDIT2]

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

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