[GCC入坑指南] -fomit-frame-pointer 和 -fno-omit-frame-pointer 編譯標(biāo)識(shí)

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)。

棧幀結(jié)構(gòu)

圖片來源 [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ù)rbppop rbp


  1. GCC Command Options >> 3.11 Options That Control Optimization ?

  2. 《深入理解計(jì)算機(jī)系統(tǒng)(第三版)》3.7.1 The Run Time Stack ?

  3. https://gcc.godbolt.org/z/e6Wox6jqE ?

  4. https://gcc.godbolt.org/z/aovjb5qnv ?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 引子 gcc and g++分別是gnu的c & c++編譯器。gcc/g++在執(zhí)行編譯工作的時(shí)候,總共需要4步1...
    Alfie20閱讀 3,305評(píng)論 2 0
  • 以下文章均為拜讀公眾號(hào) 源碼游記 的筆記 http://mp.weixin.qq.com/mp/homepage?...
    lucasgao閱讀 811評(píng)論 0 0
  • 前言 編譯的主要任務(wù)是將源代碼文件作為輸入,最終輸出目標(biāo)文件,這期間發(fā)生了什么?便是我們本篇文章要介紹的。在開始之...
    沐靈洛閱讀 3,402評(píng)論 0 2
  • 前言 編譯的主要任務(wù)是將源代碼文件作為輸入,最終輸出目標(biāo)文件,這期間發(fā)生了什么?便是我們本篇文章要介紹的。在開始之...
    QiShare閱讀 4,698評(píng)論 0 7
  • 靜態(tài)分析是指對(duì)二進(jìn)制包進(jìn)行反編譯,分析靜態(tài)的代碼邏輯。 本文內(nèi)容包括:app 砸殼過程、工具和環(huán)境的坑、導(dǎo)出 OC...
    黑超熊貓zuik閱讀 4,055評(píng)論 0 51

友情鏈接更多精彩內(nèi)容