Адресация переменных стека

Я анализирую разборку следующей (очень простой) программы на C в GDB на X86_64.

int main()
{
    int a = 5;
    int b = a + 6;
    return 0;
}

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

Dump of assembler code for function main:
0x0000000000400474 <+0>:    push   %rbp
0x0000000000400475 <+1>:    mov    %rsp,%rbp
0x0000000000400478 <+4>:    movl   $0x5,-0x8(%rbp)
0x000000000040047f <+11>:   mov    -0x8(%rbp),%eax
0x0000000000400482 <+14>:   add    $0x6,%eax
0x0000000000400485 <+17>:   mov    %eax,-0x4(%rbp)
0x0000000000400488 <+20>:   mov    $0x0,%eax
0x000000000040048d <+25>:   leaveq 
0x000000000040048e <+26>:   retq   
End of assembler dump.

Я понимаю, что:

  1. Мы помещаем базовый указатель в стек.
  2. Затем мы копируем значение указателя стека в базовый указатель.
  3. Затем мы копируем значение 5 в адрес -0x8(%rbp). Поскольку в int 4 байта, не должно ли это быть по следующему адресу в стеке, который является -0x4(%rbp), а не -0x8(%rbp)?.
  4. Затем мы копируем значение в переменную a в %eax, добавляем 6, а затем копируем значение в адрес в -0x4(%rbp).

Используя этот рисунок для справки:


(источник: thegreenplace.net)

похоже, что стек имеет следующее содержимое:

|--------------|
|      rbp     | <-- %rbp
|      11      | <-- -0x4(%rbp)
|      5       | <-- -0x8(%rbp)

когда я ожидал этого:

|--------------|
|      rbp     | <-- %rbp
|      5       | <-- -0x4(%rbp)
|      11      | <-- -0x8(%rbp)

что, как кажется, имеет место в сборке "7-понимание-по-обучению", где они показывают сборку:

(gdb) disassemble
Dump of assembler code for function main:
0x0000000100000f50 <main+0>:    push   %rbp
0x0000000100000f51 <main+1>:    mov    %rsp,%rbp
0x0000000100000f54 <main+4>:    mov    $0x0,%eax
0x0000000100000f59 <main+9>:    movl   $0x0,-0x4(%rbp)
0x0000000100000f60 <main+16>:   movl   $0x5,-0x8(%rbp)
0x0000000100000f67 <main+23>:   mov    -0x8(%rbp),%ecx
0x0000000100000f6a <main+26>:   add    $0x6,%ecx
0x0000000100000f70 <main+32>:   mov    %ecx,-0xc(%rbp)
0x0000000100000f73 <main+35>:   pop    %rbp
0x0000000100000f74 <main+36>:   retq   
End of assembler dump.

Почему значение b помещается в более высокий адрес памяти в стеке, чем a, когда a явно объявлен и инициализирован первым?

Ответ 1

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

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

$ cat > foo.c
int main()
{
    int a = 5;
    int b = a + 6;
    return 0;
}
$ cc -O -c foo.c
$ objdump -S foo.o

foo.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <main>:
   0:   31 c0                   xor    %eax,%eax
   2:   c3                      retq
$

С некоторыми простыми оптимизациями компилятор понял, что вы не используете переменную 'b', поэтому нет необходимости ее вычислять. И из-за этого вы не используете переменную "a", поэтому нет необходимости ее назначать. Только компиляция без оптимизации (или очень плохой компилятор) поместит что-нибудь в стек здесь. И даже если вы используете значения, основные оптимизации будут помещать их в регистры, потому что трогать стек дорого.