Порядок выделения памяти в С - PullRequest
2 голосов
/ 28 сентября 2019

Я пытаюсь понять, как компьютер / ОС / компилятор (не уверен, кому принадлежит выделение памяти, поэтому мой вопрос по noob-ish) назначает адреса памяти локальным переменным.

У меня есть эта простая программа:

#include <stdio.h>

int main(int argc, char** argv) {

    printf("hello, world\n");
    int arr[10];
    int a = 1;
    int b = 2;
    int c;
    for (int i = 0; i < 10; i++) {

        printf("Variable i: %p\n", &i);
        printf("Variable arr[i]: %p\n", &arr[i]);
    }
    printf("Variable a: %p\n", &a);
    printf("Variable b: %p\n", &b);
    printf("Variable c: %p\n", &c);
}

Есть две основные вещи, которые я не понимаю.

  1. Почему переменная i получает более ранний адрес памяти, чем переменная arr, и переменная a / b даже раньше, чем?Похоже, что он должен что-то делать, когда вы фактически используете переменную или присваиваете ей значение.

  2. Как / Почему ОС (или кто-либо ответственный) использует один и тот же адрес памяти для переменнойс, а переменная я?Очевидно, я выхожу из области видимости, но c был объявлен ранее.

Вот вывод из программы:

hello, world
Variable i: 0x7ffd60b1696c
Variable arr[i]: 0x7ffd60b16970
Variable i: 0x7ffd60b1696c
Variable arr[i]: 0x7ffd60b16974
Variable i: 0x7ffd60b1696c
Variable arr[i]: 0x7ffd60b16978
Variable i: 0x7ffd60b1696c
Variable arr[i]: 0x7ffd60b1697c
Variable i: 0x7ffd60b1696c
Variable arr[i]: 0x7ffd60b16980
Variable i: 0x7ffd60b1696c
Variable arr[i]: 0x7ffd60b16984
Variable i: 0x7ffd60b1696c
Variable arr[i]: 0x7ffd60b16988
Variable i: 0x7ffd60b1696c
Variable arr[i]: 0x7ffd60b1698c
Variable i: 0x7ffd60b1696c
Variable arr[i]: 0x7ffd60b16990
Variable i: 0x7ffd60b1696c
Variable arr[i]: 0x7ffd60b16994
Variable a: 0x7ffd60b16964
Variable b: 0x7ffd60b16968
Variable c: 0x7ffd60b1696c

Я работаю на Ubuntu 18, gccкомпилятор c99 7.4.0.

Ответы [ 3 ]

1 голос
/ 29 сентября 2019

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

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

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

Так как компилятор читает всю функцию,он должен помнить все объекты, которые вы определяете.В вашем примере это включает arr, a, b и c.Для этого компилятор использует некоторую структуру данных, чтобы запомнить их.Одна из первых структур данных, о которой вы узнаете, - это простой список.Компилятор может хранить список определенных объектов и имен.Он может хранить список в том порядке, в котором компилятор видит имена - arr, a, b, c - или он может сохранять список в алфавитном порядке - a, arr, b, c.Или он может поддерживать список в порядке по размеру или другим функциям, например, a, b, c, arr, если отсортирован по размеру.

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

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

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

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

0 голосов
/ 28 сентября 2019

Компилятор определяет расположение переменных в исполняемом файле.Фактические адреса определяются операционной системой.

Почему переменная i получает более ранний адрес памяти, чем переменная arr, а переменная a / b даже раньше, чем?Похоже, что он имеет какое-то отношение, когда вы фактически используете переменную или присваиваете ей значение.

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

Как / Почему ОС (или кто-либо ответственный) использует один и тот же адрес памяти для переменной c и переменной i?Очевидно, что я вышел из области видимости, но c был объявлен ранее.

Переменная c не используется, поэтому поведение программы не зависит от адреса i иc быть другим.Если вы назначите c значение, адрес, вероятно, изменится.

0 голосов
/ 28 сентября 2019

C не определяет ничего из этого.Весь вопрос касается внутренних деталей какого-то конкретного компилятора на какой-то конкретной платформе.

То, что говорит стандарт, это то, что разные объекты (переменные и т. Д.) Должны иметь разные адреса.Как распределяются эти адреса или даже какие адреса на самом деле: это детали реализации.

Почему переменная дает более ранний адрес памяти, чем переменная arr

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

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

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

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