C язык: как произошло выравнивание памяти в стеке для массива - PullRequest
2 голосов
/ 22 января 2020

все, у меня есть интересный вопрос о выравнивании памяти для массива в C. Моя ОС - 32-битная Ubuntu, я компилирую ее с опцией g cc -S -fno-stack-protector.

Код:

char array1[5] = "aaaaa";
char array2[8];
array2[0] = 'b';

Код сборки:

pushl %ebp
move %esp, %ebp.         # esp and ebp are pointing to the same words
subl    $16, %esp        # move esp to lower 16
movl    $1633771873, -5(%ebp)       # input "aaaa"
movb    $97, -1(%ebp).              # input 'a'
movb    $98, -13(%ebp)              # input 'b'
movl    $0, %eax
leave

У меня есть GDB для проверки памяти,

%ebp равно efe8,

%esp равно efd8,

&buf1 is efe3,

&buf2 is efdb.

В GDB я запускаю x/4bd 0xbfffefd8, он показывает

0xbfffefd8:    9  -124   4  98

, если я запускаю x / bd 0xbfffefd8, он показывает

0xbfffefd8:    9

, если я запускаю x / bd 0xbfffefdb, он показывает

0xbfffefd8:    98

Таким образом, память выглядит следующим образом

## high address ##
?                       efe8 <-- ebb
97  97  97  97          efe4 
0  -80  -5  97(a)       efe0
0    0   0   0          efdc
9 -124   4  98(b)       efd8 <-- esp
^            ^
|            |
efd8       efdb

Теперь мои вопросы:

  1. почему символ 'b' (98) имеет значение efdb, а %esp - efd8? Я думаю, что 'b' также должно быть в efd8, потому что это начало 4-байтового слова. Кроме того, если я продолжу заполнять больше «b» до buf2, который начинается с efdb, он может заполнить только 5'b, а не 8. Почему? А что насчет '\ 0'?

То же самое произошло с buf1, оно начинается с efe3, а не efe0. Что это за выравнивание? Это не имеет смысла для меня.

Из кода сборки он не показывает 16 выравнивания, которое я видел из другого места, например,
andl $-16, %esp     # this aligns esp to 16 boundary

Когда будет показана команда andl и когда нет? Это очень распространенное явление, поэтому я ожидаю увидеть его в каждой программе.

Из вышеприведенного кода сборки я не смог увидеть выравнивание памяти. Это всегда правда? Насколько я понимаю, ассемблерный код просто интерпретирует код высокого уровня (очень читаемый) в не очень читаемый код, но все же преобразует точное сообщение, поэтому char[5] не интерпретируется способом, учитывающим выравнивание памяти. Тогда выравнивание памяти должно произойти во время работы. Я прав? Но отладка GDB показывает то же самое, что и код сборки. Никакого выравнивания.

Спасибо.

1 Ответ

2 голосов
/ 22 января 2020

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

Копаем немного дальше. На моей 64-битной машине, используя G CC 7 с опцией -m32, я запускаю и отлаживаю один и тот же код и получаю одинаковые результаты:

(gdb) x/4bd $esp+12
0xffffcdd4:     97      97      97      97
(gdb) x/4bd $esp+8 
0xffffcdd0:     0       -48     -7      97
(gdb) x/4bd $esp+4
0xffffcdcc:     0       0       0       0
(gdb) x/4bd $esp+0 
0xffffcdc8:     41      85      85      98

Конечно, адреса разные, и это хорошо. Теперь позвольте мне попытаться объяснить. Во-первых, $esp выровнен по 4 байта, как и ожидалось:

(gdb) p $esp
$9 = (void *) 0xffffcdc8

Пока все хорошо. Теперь, поскольку мы знаем, что в массивах char по умолчанию используется 1 , давайте попробуем выяснить, что произошло во время компиляции. Сначала компилятор увидел array1[5] и поместил его в стек, но, поскольку он имел ширину 5 байт, он расширил его до второго слова. Итак, первый меч полон 'a', в то время как использовался только 1 байт второго меча. Теперь array2[8] помещается сразу после (или раньше, в зависимости от того, как вы выглядите) array1[5]. Он распространяется на 3 слова и заканчивается словом, обозначенным $esp.

Итак, у нас есть:

[esp +  0] <3 bytes of garbage /* no var */>, 'b' /* array2 */,
[esp +  4] 0x0, 0x0, 0x0, 0x0, /* still array2 */
[esp +  8] <3 bytes of garbage /* still array2 */>, 'a' /* array1 */,
[esp + 12] 'a', 'a', 'a', 'a', /* still array1 */.

Если вы добавите массив char[2] после array2 you ' Вы увидите его, используя то же двойное слово, на которое указывает $esp, и все равно у вас будет 1 байт мусора от $esp до вашего array3[2].

Компилятору абсолютно разрешено это делать. Если вы хотите, чтобы ваши char массивы были выровнены по 4 байта (но вам нужна хорошая причина для этого!), Вы должны использовать специальные атрибуты компилятора, такие как:

__attribute__ ((aligned(4)))

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