[英]Difference in for loops of old and new GCC's generated assembly code
我正在閱讀有關匯編代碼的章節,其中有一個例子。 這是C程序:
int main()
{
int i;
for(i=0; i < 10; i++)
{
puts("Hello, world!\n");
}
return 0;
}
這是書中提供的匯編代碼:
0x08048384 <main+0>: push ebp
0x08048385 <main+1>: mov ebp,esp
0x08048387 <main+3>: sub esp,0x8
0x0804838a <main+6>: and esp,0xfffffff0
0x0804838d <main+9>: mov eax,0x0
0x08048392 <main+14>: sub esp,eax
0x08048394 <main+16>: mov DWORD PTR [ebp-4],0x0
0x0804839b <main+23>: cmp DWORD PTR [ebp-4],0x9
0x0804839f <main+27>: jle 0x80483a3 <main+31>
0x080483a1 <main+29>: jmp 0x80483b6 <main+50>
0x080483a3 <main+31>: mov DWORD PTR [esp],0x80484d4
0x080483aa <main+38>: call 0x80482a8 <_init+56>
0x080483af <main+43>: lea eax,[ebp-4]
0x080483b2 <main+46>: inc DWORD PTR [eax]
0x080483b4 <main+48>: jmp 0x804839b <main+23>
這是我的版本的一部分:
0x0000000000400538 <+8>: mov DWORD PTR [rbp-0x4],0x0
=> 0x000000000040053f <+15>: jmp 0x40054f <main+31>
0x0000000000400541 <+17>: mov edi,0x4005f0
0x0000000000400546 <+22>: call 0x400410 <puts@plt>
0x000000000040054b <+27>: add DWORD PTR [rbp-0x4],0x1
0x000000000040054f <+31>: cmp DWORD PTR [rbp-0x4],0x9
0x0000000000400553 <+35>: jle 0x400541 <main+17>
我的問題是,為什么在本書的版本中它將0分配給變量( mov DWORD PTR [ebp-4],0x0
)並在此之后與cmp
進行比較,但在我的版本中,它分配然后它執行jmp 0x40054f <main+31>
cmp
在哪里?
在沒有任何jump
情況下分配和比較似乎更合乎邏輯,因為它就像在for循環中一樣。
為什么你的編譯器做的不同於本書中使用的不同編譯器? 因為它是一個不同的編譯器。 沒有兩個編譯器會編譯所有相同的代碼,即使是非常簡單的代碼也可以被兩個不同的編譯器甚至同一個編譯器的兩個版本編譯得大不相同。 很明顯,兩者都是在沒有任何優化的情況下編譯的,通過優化,結果會更加不同。
讓我們來看看for循環的作用。
for (i = 0; i < 10; i++) {
code;
}
讓我們把它寫得更接近第一個編譯器生成的匯編程序。
i = 0;
start: if (i > 9) goto out;
code;
i++;
goto start;
out:
現在“我的版本”也是一樣的:
i = 0;
goto cmp;
start: code;
i++;
cmp: if (i < 10) goto start;
這里明顯不同的是,在“我的版本”中,只有一個跳轉在循環中執行,而書籍版本有兩個。 由於CPU對分支的敏感程度,在更現代的編譯器中生成循環是一種非常常見的方法。 許多編譯器即使沒有任何優化也會生成這樣的代碼,因為它在大多數情況下表現更好。 較舊的編譯器沒有這樣做,因為要么他們沒有考慮它,要么這個技巧是在編譯本書中的代碼時未啟用的優化階段執行的。
請注意,與任何一種優化的編譯器啟用甚至不會做,首先goto cmp
,因為它會知道,這是不必要的。 嘗試編譯你的代碼並啟用優化(你說你使用gcc,給它-O2標志),看看它后面會有多大的不同。
你沒有從你的教科書中引用該函數的完整匯編語言體,但我的精神力量告訴我它看起來像這樣(為了清楚起見,我也用標簽替換了文字地址):
# ... establish stack frame ...
mov DWORD PTR [rbp-4],0x0
cmp DWORD PTR [rbp-4],0x9
jle .L0
.L1:
mov rdi, .Lconst0
call puts
add DWORD PTR [rbp-0x4],0x1
cmp DWORD PTR [rbp-0x4],0x9
jle .L1
.L0:
# ... return from function ...
GCC注意到它可以通過將無條件的jmp
替換為循環底部的cmp
來消除初始cmp
和jle
,這就是它所做的。 這是一種稱為循環反轉的標准優化。 顯然,即使優化器關閉,它也能做到這一點; 在優化的情況下,它也會注意到初始比較必須是假的,提升地址加載,將循環索引放在寄存器中,並轉換為倒計時循環,這樣它就可以完全消除cmp
; 這樣的事情:
# ... establish stack frame ...
mov ebx, 10
mov r14, .Lconst0
.L1:
mov rdi, r14
call puts
dec ebx
jne .L1
# ... return from function ...
(上面的內容實際上是由Clang生成的。我的GCC版本做了別的事, 同樣合理但難以解釋 。)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.