簡體   English   中英

編譯器如何確定具有編譯器生成的臨時函數的函數所需的堆棧大小?

[英]How does the compiler determine the needed stack size for a function with compiler generated temporaries?

考慮以下代碼:

class cFoo {
    private:
        int m1;
        char m2;
    public:
        int doSomething1();
        int doSomething2();
        int doSomething3();
}

class cBar {
    private:
        cFoo mFoo;
    public:
        cFoo getFoo(){ return mFoo; }
}

void some_function_in_the_callstack_hierarchy(cBar aBar) {
    int test1 = aBar.getFoo().doSomething1();
    int test2 = aBar.getFoo().doSomething2();
    ...
}

在調用getFoo()的行中,編譯器將生成cFoo的臨時對象,以便能夠調用doSomething1()。 編譯器是否重用用於這些臨時對象的堆棧內存? “some_function_in_the_callstack_hierarchy”的調用將保留多少堆棧內存? 是否為每個生成的臨時存儲內存?

我的猜測是編譯器只為cFoo的一個對象保留內存,並將重用內存用於不同的調用,但如果我添加

    int test3 = aBar.getFoo().doSomething3();

我可以看到“some_function_in_the_callstack_hierarchy”所需的堆棧大小更多,而且不僅僅是因為附加的本地int變量。

另一方面,如果我然后更換

cFoo getFoo(){ return mFoo; }

帶引用(僅用於測試目的,因為返回對私有成員的引用不好)

const cFoo& getFoo(){ return mFoo; }

它需要的堆棧內存少於一個cFo​​o的大小。

所以對我來說,似乎編譯器為函數中的每個生成的臨時對象保留了額外的堆棧內存。 但這樣效率很低。 有人可以解釋一下嗎?

優化編譯器正在將您的源代碼轉換為一些內部表示,並對其進行規范化。

使用免費軟件編譯器(如GCCClang / LLVM ),您可以查看內部表示(至少通過修補編譯器代碼或在某些調試器中運行它)。

BTW,有時候,臨時值甚至不需要任何堆棧空間,例如因為它們已被優化,或者因為它們可以位於寄存器中。 而且他們經常會在當前的調用幀中重用一些不需要的插槽。 另外(特別是在C ++中)很多(小)函數都是內聯的 - getFoo你的getFoo可能是 - (所以他們自己沒有任何調用框架)。 最近的GCC甚至有時能夠進行尾調用優化(實質上是重用調用者的調用幀)。

如果您使用GCC(即g++ )進行編譯,我建議您使用優化選項開發人員選項 (以及其他一些選項 )。 也許使用-Wstack-usage=48 (或其他一些值,每個調用幀的字節數)和/或-fstack-usage

首先,如果可以讀取匯編代碼,編譯yourcode.ccg++ -S -fverbose-asm -O yourcode.cc和窺視發射yourcode.s

(不要忘記使用優化標志,所以將-O替換為-O2-O3 ....)

然后,如果您對編譯器的優化方式更加好奇,請嘗試g++ -O -fdump-tree-all -c yourcode.cc ,您將獲得許多所謂的“轉儲文件”,其中包含部分文本呈現與GCC相關的內部陳述。

如果您更加好奇,請查看我的GCC MELT ,特別是其文檔頁面(其中包含大量幻燈片和參考文獻)。

所以對我來說,似乎編譯器為函數中的每個生成的臨時對象保留了額外的堆棧內存。

當然不是,在一般情況下(當然假設你啟用了一些優化)。 即使保留了一些空間,也可以很快地重復使用。

順便說一句:請注意,C ++ 11標准沒有提到堆棧。 可以想象一些C ++程序在沒有使用任何堆棧的情況下編譯(例如,整個程序優化檢測到沒有遞歸的程序,其堆棧空間和布局可以優化以避免任何堆棧。我不知道任何這樣的編譯器,但我知道編譯器可以很聰明....)

隨着優化策略變得更加激進,嘗試分析編譯器如何處理特定代碼片段變得越來越困難。

編譯器所要做的就是實現C ++標准並編譯代碼而不引入或取消任何副作用(有一些例外,例如返回和命名返回值優化)。

您可以從代碼中看到,由於cFoo不是多態類型且沒有成員數據,因此編譯器可以完全優化對象的創建,並直接調用基本上是static函數的東西。 我想,即使在我寫作的時候,一些編譯器已經在做了。 您可以隨時檢查輸出組件。

編輯:OP現在已經引入了類成員。 但由於這些從未初始化並且是private ,因此編譯器可以刪除它們而不必過於考慮。 因此,這個答案仍然適用。

臨時對象的生命周期一直持續到完整包含表達式結尾 ,請參閱標准的“12.2臨時對象”段落。

即使使用最低優化設置,編譯器也不太可能在臨時對象的生命周期結束后重用該空間。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM