2013-10-31 128 views
2

您好我一直在閱讀這些東西在不同的文檔爲什麼編譯器將變量存儲在寄存器中?

寄存器

告訴編譯器來存儲變量在CPU寄存器正在申報。

在標準C方言,關鍵字寄存器使用以下語法:

寄存器數據定義; 寄存器類型修飾符告訴編譯器將正在聲明的變量存儲在> CPU寄存器(如果可能)中以優化訪問。例如,

register int i; 請注意,當開啓優化時,TIGCC會自動將經常使用的變量存儲在CPU寄存器中,但關鍵字寄存器將強制存儲在寄存器中,即使關閉優化。然而,如果編譯器認爲在這個地方沒有足夠的空閒寄存器供使用,那麼在寄存器中存儲數據的請求可能會被拒絕。

我的觀點不僅僅是註冊。我的觀點是編譯器爲什麼要將變量存儲在內存中。編譯器業務僅僅是編譯並生成一個目標文件。運行時會發生實際的內存分配。編譯器爲什麼要做這件事。我的意思是不運行目標文件只是通過編譯文件本身的內存分配發生在c的情況下?

+1

我不明白你在問什麼。變量必須在寄存器中才能執行指令。這正是CPU工作的方式。 – Kevin

+1

目前還不清楚你究竟在問什麼。您正在閱讀的文檔討論編譯器生成的代碼。即來自您的C代碼,編譯器需要生成告訴CPU如何操作的代碼,其中包括變量駐留在內存中的位置,執行指令時使用哪個CPU寄存器等等。一旦編譯器生成了該代碼,就會在運行編譯代碼時發生實際的內存/寄存器分配和使用。 – nos

回答

5

編譯器生成機器碼是?機器碼運行以實際運行你的程序。所以編譯器決定它生成的機器代碼,因此它決定了在運行時會發生什麼樣的分配。當你輸入gcc foo.c時,它不會執行它們,但是當你運行可執行文件時,它會生成正在運行的代碼GCC。

這意味着編譯器希望生成可能的禁用代碼並在編譯時做出儘可能多的決定,這包括如何分配內容。

+0

除此之外,'register'不會強制編譯器將變量實際分配給一個寄存器,但這只是一個有用的指示。如果我讀到的一些評論是真實的,那麼現代編譯器就是這樣優化的,它們在猜測哪些變量作爲「註冊」更有用,而且大多忽略關鍵字時做得更好。 – SJuan76

+0

任何編譯器都是一樣的。運行時總是運行實際業務的代碼,它只是運行編譯器生成的代碼(可能在運行時會發生很少的決定),但沒有其他語言的文檔說編譯器會這樣做並執行此操作。他們說編譯器生成代碼,運行時運行代碼。即使使用解釋器語言也是如此。我知道c生成機器代碼,但我的觀點是編譯器只是將源代碼編譯成機器可理解的,但如果實際執行必須發生,它必須運行。爲什麼C文檔與其他文檔有如此不同? –

+0

@MuralidharYaragalla因爲在每個C編譯器中編譯器都決定生成那些代碼。這就像是說「指揮官要攻擊那個島」,指揮官可能不會親自去做,但他給出的命令是 – jozefg

1

編譯器需要將代碼轉換爲機器指令,並告訴計算機如何運行代碼。這包括如何進行操作(如乘以兩個數字)以及如何存儲數據(堆棧,堆或寄存器)。

1

編譯器不運行代碼(除非它執行幾輪分析和更好的代碼執行),但它必須準備它 - 這包括如何保持程序定義的變量,是否使用快速和作爲寄存器的高效存儲,或者使用較慢(並且更易於產生副作用)的內存。

最初,您的本地變量將被簡單地分配到堆棧幀的位置(當然除了您明確使用動態分配的內存外)。如果你的函數分配了一個int,你的編譯器可能會告訴堆棧增加幾個額外的字節,並使用該存儲器地址來存儲該變量並將其作爲操作數傳遞給代碼正在對該變量進行的任何操作。但是,由於內存速度較慢(即使在緩存時),並且操作它會對CPU造成更多限制,但在稍後的階段,編譯器可能會決定嘗試將某些變量移入寄存器。這種分配是通過一種複雜的算法完成的,該算法試圖選擇能夠適應現有邏輯寄存器數量的最重用和等待時間的關鍵變量(同時確認了各種限制,例如某些指令要求操作數存在於該登記冊)。

還有一個複雜因素 - 一些內存地址可能會在編譯時未知的方式與外部指針混淆,在這種情況下,您無法將它們移動到寄存器中。編譯器通常是一個非常謹慎的一羣,他們中的大多數會避免危險的優化(否則他們需要進行一些特殊的檢查以避免令人討厭的事情)。

畢竟是,編譯器仍然不夠禮貌讓你建議哪個變量是重要的,你的關鍵,如果他錯過了它,並通過與register關鍵字你基本上要他這些標記嘗試通過使用寄存器來優化此變量,只要有足夠的寄存器可用並且不會出現混疊。

這裏有一個小例子:看看下面的代碼,做同樣的事情兩次,但略有不同的情況:

#include "stdio.h" 

int j; 
int main() { 
    int i; 
    for (i = 0; i < 100; ++i) { 
     printf ("i'm here to prevent the loop from being optimized\n"); 
    } 
    for (j = 0; j < 100; ++j) { 
     printf ("me too\n"); 
    } 
} 

請注意,我是本地的,J是全球性的(因此編譯器沒有按」不知道其他人是否可以在運行期間訪問他)。

在GCC編譯與-O3產生以下代碼爲主要:

0000000000400540 <main>: 
    400540:  53      push %rbx 
    400541:  bf 88 06 40 00   mov $0x400688,%edi 
    400546:  bb 01 00 00 00   mov $0x1,%ebx 
    40054b:  e8 18 ff ff ff   callq 400468 <[email protected]> 
    400550:  bf 88 06 40 00   mov $0x400688,%edi 
    400555:  83 c3 01    add $0x1,%ebx   # <-- i++ 
    400558:  e8 0b ff ff ff   callq 400468 <[email protected]> 
    40055d:  83 fb 64    cmp $0x64,%ebx 
    400560:  75 ee     jne 400550 <main+0x10> 
    400562:  c7 05 80 04 10 00 00 movl $0x0,1049728(%rip)  # 5009ec <j> 
    400569:  00 00 00 
    40056c:  bf c0 06 40 00   mov $0x4006c0,%edi 
    400571:  e8 f2 fe ff ff   callq 400468 <[email protected]> 
    400576:  8b 05 70 04 10 00  mov 1049712(%rip),%eax  # 5009ec <j> (loads j) 
    40057c:  83 c0 01    add $0x1,%eax   # <-- j++ 
    40057f:  83 f8 63    cmp $0x63,%eax 
    400582:  89 05 64 04 10 00  mov %eax,1049700(%rip)  # 5009ec <j> (stores j back) 
    400588:  7e e2     jle 40056c <main+0x2c> 
    40058a:  5b      pop %rbx 
    40058b:  c3      retq 

正如你可以看到,第一循環計數器坐在EBX,和遞增在每次迭代並抵靠限制進行比較。
然而,第二個循環是危險的,gcc決定通過內存傳遞索引計數器(每次迭代將其加載到rax中)。這個例子用來說明使用寄存器時會有多好,以及有時候不能。

+0

謝謝你。它確實有幫助。 –

相關問題