GCC編譯器提供了 -fomit-frame-pointer
和 -fno-omt-frame-pointer
兩個(gè)相對(duì)的編譯選項(xiàng)。
- GCC手冊(cè)[1]里對(duì)
-fomit-frame-pointer
的說明:
Omit the frame pointer in functions that don’t need one. This avoids the instructions to save, set up and restore the frame pointer; on many targets it also makes an extra register available.
On some targets this flag has no effect because the standard calling sequence always uses a frame pointer, so it cannot be omitted.
Note that -fno-omit-frame-pointer doesn’t guarantee the frame pointer is used in all functions. Several targets always omit the frame pointer in leaf functions.
Enabled by default at -O1 and higher.
該說明的大意就是如果函數(shù)不需要frame pointer,就不要將frame pointer保留在寄存器中爽柒。當(dāng)打開優(yōu)化選項(xiàng):-O
,-O2
, -O3
, -Os
時(shí)或者對(duì)某些平臺(tái)不打開任何優(yōu)化選項(xiàng)時(shí)菇用,-fomit-frame-pointer
會(huì)被默認(rèn)打開龄坪,可以通過設(shè)置 -fno-omit-frame-pointer
關(guān)閉 -fomit-frame-pointer
焙格。
- 什么是 frame pointer 瓮床?
所謂的 frame pointer(FP) 即 stack frame pointer 棧幀指針芜茵。每個(gè)進(jìn)程的椥鹆浚空間為一幀,F(xiàn)P指向當(dāng)前進(jìn)程椌糯空間的棧底(stack bottom)绞佩。而 stack pointer(SP) 即堆棧指針,總是指向棧頂(stack top)猪钮。
圖片來源 [2]
在多進(jìn)程環(huán)境中品山,每個(gè)進(jìn)程都有自己的棧空間烤低,但所有進(jìn)程的椫饨唬空間都在同一塊存儲(chǔ)空間,怎么確定各進(jìn)程的棧呢扑馁?這就要看FP和SP涯呻,F(xiàn)P指向棧底,SP指向棧頂腻要,這樣复罐,一個(gè)進(jìn)程的棧空間就確定了闯第。
通過回溯棧幀就可以追蹤代碼的調(diào)用過程和調(diào)用時(shí)的參數(shù)市栗。
-
-fomit-frame-pointer
對(duì)編譯結(jié)果的影響
GCC 版本 7.5.0 Target X86_64
示例代碼:
int add(int a,int b){
return a+b;
}
int mul(int a,int b){
return a*b;
}
int ma(int a,int b,int c){
return mul(add(a,b),c);
}
關(guān)閉 -fomit-frame-pointer
編譯標(biāo)識(shí)
編譯命令:
-fno-omit-frame-pointer
編譯結(jié)果:[3]
add(int, int):
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], edi
mov DWORD PTR [rbp-8], esi
mov edx, DWORD PTR [rbp-4]
mov eax, DWORD PTR [rbp-8]
add eax, edx
pop rbp
ret
mul(int, int):
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], edi
mov DWORD PTR [rbp-8], esi
mov eax, DWORD PTR [rbp-4]
imul eax, DWORD PTR [rbp-8]
pop rbp
ret
ma(int, int, int):
push rbp
mov rbp, rsp
sub rsp, 16
mov DWORD PTR [rbp-4], edi
mov DWORD PTR [rbp-8], esi
mov DWORD PTR [rbp-12], edx
mov edx, DWORD PTR [rbp-8]
mov eax, DWORD PTR [rbp-4]
mov esi, edx
mov edi, eax
call add(int, int)
mov edx, eax
mov eax, DWORD PTR [rbp-12]
mov esi, eax
mov edi, edx
call mul(int, int)
leave
ret
打開 -fomit-frame-pointer
編譯標(biāo)識(shí)
編譯命令:
-fomit-frame-pointer
編譯結(jié)果:[4]
add(int, int):
mov DWORD PTR [rsp-4], edi
mov DWORD PTR [rsp-8], esi
mov edx, DWORD PTR [rsp-4]
mov eax, DWORD PTR [rsp-8]
add eax, edx
ret
mul(int, int):
mov DWORD PTR [rsp-4], edi
mov DWORD PTR [rsp-8], esi
mov eax, DWORD PTR [rsp-4]
imul eax, DWORD PTR [rsp-8]
ret
ma(int, int, int):
sub rsp, 16
mov DWORD PTR [rsp+12], edi
mov DWORD PTR [rsp+8], esi
mov DWORD PTR [rsp+4], edx
mov edx, DWORD PTR [rsp+8]
mov eax, DWORD PTR [rsp+12]
mov esi, edx
mov edi, eax
call add(int, int)
mov edx, eax
mov eax, DWORD PTR [rsp+4]
mov esi, eax
mov edi, edx
call mul(int, int)
add rsp, 16
ret
編譯結(jié)果分析
從上面的編譯結(jié)果中可以看到,是否忽略frame pointer
會(huì)導(dǎo)致編譯生成的匯編代碼有以下的區(qū)別:
func(int, ...):
push rbp
mov rbp, rsp
...
pop rbp
ret
rbp:基址指針寄存器咳短,用于提供堆棧內(nèi)某個(gè)單元的偏移地
rsp:棧頂指針寄存器填帽,提供堆棧棧頂單元的偏移地址
由這一點(diǎn)區(qū)別可以看到,當(dāng)保留frame pointer
時(shí)咙好,首先將保存在rbp
寄存器中的FP入棧(push rbp
)篡腌;然后將rbp
寄存器設(shè)置為本函數(shù)的rsp
,即:將基址指針指向棧頂指針(mov rbp,rsp
)勾效;最后在函數(shù)執(zhí)行結(jié)束時(shí)恢復(fù)rbp
(pop rbp
)
-
GCC Command Options >> 3.11 Options That Control Optimization ?
-
《深入理解計(jì)算機(jī)系統(tǒng)(第三版)》3.7.1 The Run Time Stack ?