GCC編譯器提供了 -fomit-frame-pointer 和 -fno-omt-frame-pointer 兩個(gè)相對(duì)的編譯選項(xiàng)。
- GCC手冊[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)程的??臻g為一幀,F(xiàn)P指向當(dāng)前進(jìn)程??臻g的棧底(stack bottom)。而 stack pointer(SP) 即堆棧指針,總是指向棧頂(stack top)。

圖片來源 [2]
在多進(jìn)程環(huán)境中,每個(gè)進(jìn)程都有自己的棧空間,但所有進(jìn)程的??臻g都在同一塊存儲(chǔ)空間,怎么確定各進(jìn)程的棧呢?這就要看FP和SP,F(xiàn)P指向棧底,SP指向棧頂,這樣,一個(gè)進(jìn)程的??臻g就確定了。
通過回溯棧幀就可以追蹤代碼的調(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 ?