Почему GCC вычитает неправильное значение указателю стека при распределении большого массива без последующих вызовов функций?
Действительно причудливая gcc quirk. Проверьте это:
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. Имеет ли это что-то странное с x86_64 "redzone"? Что происходит точно?
Ответы
Ответ 1
Ваша догадка правильная. Это "красная зона". Красная зона - это пространство от rsp-128 до rsp, которое может использоваться функцией для локальных переменных и для временного хранения. Это пространство не тронуто обработчиками прерываний и исключений. Очевидно, что красная зона уничтожается вызовами функций, поэтому, если вызывается какая-либо функция, никакая локальная переменная не может находиться в красной зоне.
Красную зону можно использовать только в 64-битных Linux, BSD и Mac. Он недоступен в коде ядра.
Он может использоваться для оптимизации пространства, поскольку с красной зоной вы можете ссылаться на 512 байтов локальных переменных с короткими инструкциями на основе только rsp и ebp. Без красной зоны доступно только 384 байта. Доступ ко всем локальным переменным за пределами этого предела осуществляется с более длинным кодом или с дополнительными регистрами.
В вашем примере использование красной зоны не требуется, но gcc предпочитает использовать его для всех функций "листа". Это просто проще реализовать компилятор таким образом.
Ответ 2
ABI x86-64 задает "красную зону" из 128 байт за указатель стека, который можно использовать без изменения %rsp
. В первом примере main()
является листовой функцией, поэтому компилятор оптимизирует использование пространства стека - то есть нет вызовов функций, поэтому эта область не будет перезаписана.