Почему моя память отличается? - PullRequest
3 голосов
/ 20 октября 2010

Следующий фрагмент кода:

void (*foo)();
char X[1];
char Y[10];

может дать мне один возможный макет стека:

|  Y[10]  |
|---------|
|  X[1]   |
|---------|
|  foo    |
|---------|

Я проверил это, сгенерировав файл ASM, используя:

gcc -S -o stack stack.c

Тогда я обнаружил, что порядок нажатия этих переменных отличается. Итак, если бы я случайно выполнил X[1], я ожидал обратиться к Y[0], но в реальном макете запись чего-либо в X[1] перезаписывает первый байт ячейки памяти, выделенной для foo. Является ли реорганизация этапом оптимизации компилятора или кто-то может сказать мне, почему это происходит?

Ответы [ 6 ]

4 голосов
/ 20 октября 2010

Почему вы говорите "должен" ?

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

Таким образом, нет «следует» .


Чтобы принудительно упорядочить некоторые элементы в памяти, чтобы вы могли играть (с неопределенным поведением, совершенно небезопасным и непереносимым!) В игры с перезаписью, используйте struct и отступы компилятора #pragma s.

2 голосов
/ 20 октября 2010

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

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

Во-вторых, многие процессоры используют перевернутый стек.Если вы:

push A
push B

, тогда адрес A имеет больший адрес, чем B, даже если B находится на вершине стека (и на вершине A).Хороший способ представить это - использовать массив C:

int stk[BIG];
int stk_top = BIG;

, а затем

 void stk_push(int x) {
     stk_top--;
     stk[stk_top] = x;
 }

Как вы можете видеть, индекс stk_top действительно сжимается, так как стек получает больше элементов.

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

1 голос
/ 20 октября 2010

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

char x[11];
char *y = &x[1];
1 голос
/ 20 октября 2010

Это потому, что на большинстве архитектур стек уменьшается .

1 голос
/ 20 октября 2010
|   var   | address |
|---------|---------|
|  Y[10]  |  x      |
|---------|---------|
|  X[1]   |  x + 10 |
|---------|---------|
|  foo    |  x + 11 |
|---------|---------|
Стек

увеличивается до младших адресов, поэтому, если вы обращаетесь к следующему адресу (более высокому адресу), как к следующему элементу массива, вы получаете доступ к памяти по большему адресу. Итак X[1] = *(x + 10 + 1) = foo

1 голос
/ 20 октября 2010

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

Как правило, ЦП наиболее удачно получает многобайтовые данные по некоторому «целому» битовому выравниванию, которое почти всегда соответствует битовой ширине машины. Таким образом, 32-байтовый int будет выровнен по 32-битной границе. Чтобы это произошло, компилятор "заполнит" стек байтами, к которым никогда не осуществляется доступ.

Однако такое выравнивание бесполезно, когда вы извлекаете байт за раз.

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