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

Действительно странная причудливость 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 (строка 'a').

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

Ответы [ 2 ]

13 голосов
/ 01 декабря 2011

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

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

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

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

5 голосов
/ 01 декабря 2011

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

...