2016-11-28 42 views
1

我已經看了彙編輸出下面的一段代碼,我驚呆了:Lambdas作爲封閉環境。 RIP的重要作用註冊

int x=0, y=0; // global 
// r1, r2 are ints, local. 
std::thread t([&x, &y, &r1, &r2](){ 
    x = 1;  
    r1 = y;  
}); 

!std::thread t([&x, &y, &r1, &r2](){ 
<lambda()>::operator()(void) const+0: push %rbp 
<lambda()>::operator()(void) const+1: mov %rsp,%rbp 
<lambda()>::operator()(void) const+4: mov %rdi,-0x8(%rbp) 
<lambda()>::operator()(void) const+18: mov -0x8(%rbp),%rax 
<lambda()>::operator()(void) const+22: mov (%rax),%rax 
! x = 1;  
<lambda()>::operator()(void) const() 
<lambda()>::operator()(void) const+8: movl $0x1,0x205362(%rip)  # 0x6062ac <x> 
! r1 = y;  
<lambda()>::operator()(void) const+25: mov 0x205359(%rip),%edx  # 0x6062b0 <y> 
<lambda()>::operator()(void) const+31: mov %edx,(%rax) 
! 
!}); 
<lambda()>::operator()(void) const+33: nop 
<lambda()>::operator()(void) const+34: pop %rbp 
<lambda()>::operator()(void) const+35: retq 

爲什麼地址xy確定涉及到RIPRIP是一個指令指針,所以它似乎是瘋狂的。特別是,我從來沒有見過這樣的事情。 (也許我還沒有看到很多東西:))。

對我來說唯一的解釋是,lambda是一個閉包,並且從特定位置獲取環境變量與RIP有一些共同之處。

+7

在x86_64中引入了RIP相對尋址。內存引用是相對於指令指針的。它在創建與位置無關的代碼中很有用。你可以在這裏找到一個描述:https://www.tortall.net/projects/yasm/manual/html/nasm-effaddr.html –

+0

http://stackoverflow.com/q/18447627/995714,http:// stackoverflow .com/q/3250277/995714 –

+0

PIC在64位環境中很好,因爲立即數仍然可以是32位。 –

回答

3

代碼在運行時不會移動,一旦加載了代碼段,程序不會被複制或移動。
靜態數據在其部分加載後也佔用相同的地址。
因此,指令和靜態變量之間的距離在編譯時是已知的,並且在模塊庫的重定位下(因爲指令和數據都翻譯相同的數量),它是不變的。

所以RIP相對尋址不僅不是瘋狂的,而且它一直是一個很長一段時間缺少的功能。
在32位代碼中,像mov eax, [var]這樣的指令是無害的,在沒有RIP相對尋址的64位中,它需要9個字節,1個用於操作碼,8個用於立即數。對於RIP相對尋址,立即數仍然是32位。


C++ lamdbas是函數對象的語法糖,其中捕獲的變量變成實例變量。
引用捕獲的變量作爲指針/引用進行處理。
全局變量在捕獲時不需要任何特殊處理,因爲它們已經可以訪問。

你理所當然地指出,xy作爲0x205362(%rip)0x205359(%rip)分別訪問。
由於它們是全局的,因此它們的地址在運行時是固定的,並且使用RIP相對尋址來訪問它們。

但是您忘記檢查如何訪問本地捕獲的變量r1
它與(%rax)一起存儲和rax以前被加載爲(優化)movq (%rdi), %rax
%rdi是方法operator()的第一個參數,所以它是this,剛剛提到的指令會將第一個實例變量加載到rax中,然後使用該值訪問r1
簡單地說,它是一個指向r1的指針(或更好的參考),因爲r1位於堆棧上,其地址在運行時是動態的(取決於堆棧的狀態)。

因此,lambda使用間接尋址和RIP相對尋址,因此與RIP相對尋址在某種程度上是特殊的假設相矛盾。


注意捕獲機制不延長拍攝變量的壽命(如在ECMAScript中),通過在std::thread拉姆達引用,以便捕獲一個局部變量幾乎總是一個壞主意。

+0

「請注意,捕獲機制不會延長捕獲變量的壽命(如ECMAScript中的那樣),所以通過在std :: thread的lambda中通過引用捕獲本地var幾乎總是一個壞主意。」是的,但我抓住了當地人,我的線程在'超出範圍'之前加入。 @瑪格麗特謝謝! :) – Gilgamesz

+2

@Gilgamesz這就是爲什麼我幾乎總是使用:) –

+0

即使有NASM的'DEFAULT ABS'或其他彙編器的等價物,'mov eax,[var]'不會彙編到['A1 moffs64'](http: //www.felixcloutier.com/x86/MOV.html)。絕對'[sign-extended-disp32]'尋址在x86-64中仍然可用,並且是從'mov eax,[abs var]'得到的。 (x86-32有兩種冗餘的編碼方式,AMD64使用較短的一種進行RIP相對編碼,留下較長的一種)。我認爲在NASM中,你必須編寫'mov eax,[qword var]'來得到GAS呼叫'movabs'。 –