Зачем нужно использовать регистр ebp при организации стека программы, если можно обойтись и без него(если нет хитрых асм вставок)? Почему нельзя по умолчанию использовать такую оптимизацию? С чем это связанно?
-
2Приведите примеры того, про какое использование ebp тут речь и про какую вы оптимизацию говорите – Mike Jan 07 '17 at 18:25
-
@Mike: Ну, адресовать локальные переменные и параметры можно теоретически и через ESP, если внимательно следить за текущим количеством выделенной на стеке памяти. В вопросе, скорее всего, имеется в виду адресация локальных переменных и параметров подпрограммы через EBP. – VladD Jan 07 '17 at 20:21
-
x86_64 ABI в принципе об этом же и пишет: The conventional use of %rbp as a frame pointer for the stack frame may be avoided by using %rsp (the stack pointer) to index into the stack frame. This technique saves two instructions in the prologue and epilogue and makes one additional general-purpose register (%rbp) available. – 0andriy Jan 08 '17 at 00:13
-
Во всей этой дискуссии (вопрос, ответы, комментарии) меня удивляет одно, неужели никто никогда не слышал про магическое ABI? – 0andriy Jan 08 '17 at 00:17
-
@0andriy, а что это меняет? Компилятор сам разберётся как ему адресовать стек и нужны ли пролог/эпилог в каждом конкретном случае. Вручную же - личное дело программиста, никто не мешает ему делать как угодно. ABI говорит лишь об организации стека, но не накладывает никаких ограничений на способы работы с ним. – PinkTux Jan 08 '17 at 03:30
-
@PinkTux, компилятор следует ABI. – 0andriy Jan 08 '17 at 13:56
-
@0andriy, а где в ABI сказано, что к стеку нужно обращаться только через определённый регистр? – PinkTux Jan 08 '17 at 14:07
2 Answers
Связано это в первую очередь с удобством. Никто не мешает вам адресоваться через регистр esp. Но при этом нужно постоянно держать в голове что сам стековый указатель в процессе выполнения кода может прыгать как угодно. И постоянно менять смещение для одних и тех же сущностей (а вам было бы удобно, если сейчас переменная называется foo, а через пару строк к ней нужно обращаться как к bar?). В совсем простеньких случаях это может иметь смысл, сам так делаю :) Например:
print_uint:
pushad
push dword [esp+36]
push format_u ; "%u", 0
call printf
pop eax
pop eax
popad
ret 4
Да и компиляторы могут генерировать код без enter/leave при оптимизации. Но в общем случае это "микрооптимизация" (и то под вопросом), и не стоит она того геморроя, который за собой тащит.
- 9,056
Можно и без него (а очень жаль иногда).
Например, gcc для тестовой программки
#include <stdio.h>
int f() {
return puts("xaxa");
}
int main(void)
{
long a = 10;
long long b = 11;
int l = 100;
float e = 8.8;
f(a, b, l, e);
return 0;
}
с флагами оптимизации
avp@avp-ubu1:hashcode$ gcc -O -S t.c
avp@avp-ubu1:hashcode$ gcc --vers
gcc.real (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
avp@avp-ubu1:hashcode$
делает код без использования ebp(rbp) для организации фреймов стека
.file "t.c"
.section .rodata.str1.1,"aMS",@progbits,1
.LC0:
.string "xaxa"
.text
.globl f
.type f, @function
f:
.LFB23:
.cfi_startproc
subq $8, %rsp
.cfi_def_cfa_offset 16
movl $.LC0, %edi
call puts
addq $8, %rsp
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE23:
.size f, .-f
.globl main
.type main, @function
main:
.LFB24:
.cfi_startproc
subq $8, %rsp
.cfi_def_cfa_offset 16
movsd .LC1(%rip), %xmm0
movl $100, %edx
movl $11, %esi
movl $10, %edi
movl $1, %eax
call f
movl $0, %eax
addq $8, %rsp
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE24:
.size main, .-main
.section .rodata.cst8,"aM",@progbits,8
.align 8
.LC1:
.long 2684354560
.long 1075943833
.ident "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609"
.section .note.GNU-stack,"",@progbits
- 46,098
- 6
- 48
- 116
-
Кстати, вот что интересно стало: а может ли оптимизатор не восстанавливать стек после каждого вызова
cdecl-функции, если в этом нет явной необходимости? А сделать это, например, один раз передret. Проверить, конечно, можно, но сегодня уже лень :) – PinkTux Jan 07 '17 at 23:06 -
@PinkTux, мне вообще непонятно, зачем тут
subq $8, %rsp. Кстати, с -O3 телоf()сокращается до 2-х команд:movl $.LC0, %edi;jmp puts(однако,call fостается) – avp Jan 07 '17 at 23:33 -
-
-
@0andriy, каким образом? Если вы о `In other words, the value ( %rsp
is always a multiple of 16 ( 32 ) when control is transferred to the function entry point.`, так это (если посмотреть на код) еще больше все запутывает.
– avp Jan 08 '17 at 09:27 -
2Извиняюсь, только что дошло. Именно требование выравнивания заставляет компилятор вычитать 8 из %rsp. Стек был выровнен на 16, после call он не выровнен (поместили адрес возврата), для выравнивания достаточно вычесть 8. – avp Jan 08 '17 at 09:39