Почему GCC вычитает неправильное значение указателю стека при выделении большого массива без последующих вызовов функций?

Действительно странная причудливость GCC. Проверь это:

main() { int a[100]; a[0]=1; }

производит эту сборку:

   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   48 81 ec 18 01 00 00    sub    $0x118,%rsp
   b:   c7 85 70 fe ff ff 01    movl   $0x1,-0x190(%rbp)
  12:   00 00 00 
  15:   c9                      leaveq 
  16:   c3                      retq

Вершина стека явно 400, так как это массив 100 * 4. Поэтому, когда он пишет в первую запись, он делает rbp - 400 (строка 'b'). Хорошо. Но почему он вычитает 280 из стекового (строка '4') указателя? Разве это не указывает на середину массива?

Если после этого мы добавим вызов функции, gcc сделает все правильно:

b() {}
main() { int a[100]; a[0]=1; b(); }

производит эту сборку:

0000000000000000 <b>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   c9                      leaveq 
   5:   c3                      retq   

0000000000000006 <main>:
   6:   55                      push   %rbp
   7:   48 89 e5                mov    %rsp,%rbp
   a:   48 81 ec 90 01 00 00    sub    $0x190,%rsp
  11:   c7 85 70 fe ff ff 01    movl   $0x1,-0x190(%rbp)
  18:   00 00 00 
  1b:   b8 00 00 00 00          mov    $0x0,%eax
  20:   e8 00 00 00 00          callq  25 <main+0x1f>
  25:   c9                      leaveq 
  26:   c3                      retq 

Здесь он правильно вычитает 400 (строка "а").

Почему изменения, когда вы добавляете вызов функции? GCC просто ленив и не делает это правильно, потому что это не имеет значения? Что происходит? Очевидно, это происходит только при компиляции для x86_64, но не для простого x86. Это как-то странно связано с "redzone" в x86_64? Что именно происходит?

2 ответа

Решение

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

Красная зона может использоваться только в 64-битных Linux, BSD и Mac. Это не доступно в коде ядра.

Его можно использовать для оптимизации пространства, поскольку с красной зоной вы можете ссылаться на 512 байтов локальных переменных с помощью коротких инструкций, основанных только на rsp и ebp. Без красной зоны доступно только 384 байта. Все локальные переменные вне этого предела доступны с более длинным кодом или с дополнительными регистрами.

Для вашего примера использование красной зоны не обязательно, но gcc предпочитает использовать ее для всех "листовых" функций. Так проще реализовать компилятор.

ABI x86-64 предписывает "красную зону" в 128 байт за пределы указателя стека, которую можно использовать без изменения %rsp, В первом примере main() является листовой функцией, поэтому компилятор оптимизирует использование стекового пространства - т.е. нет вызовов функций, поэтому эта область не будет перезаписана.

Другие вопросы по тегам