C++中左值、右值與寄存器的關係是怎樣的?

我用下面一個求階乘的例子說明我對右值與寄存器之間關係的理解:

int qiuJieCheng(int n) {

if (n == 1) {return 1; }

else {return n*qiuJieCheng(n-1);}

}

在遞歸調用時候,由於 (n-1) 是 n 做運算,結果存在寄存器中,而寄存器並沒有內存單元地址,所以編譯器將其認為是右值,無法被引用綁定。通過修改使得引用可綁定,有下面程序:

int qiuJieCheng(int n) {

int a = n;

if (a == 1) {return 1; }

else {return n*qiuJieCheng(a);}

}

這裡利用變數 a 在內存空間中存儲 n 的數據,遞歸調用時,由於 a 具有內存單元地址,所以編譯器認為它是左值,綁定成功。

那麼,我們是否能認為,所謂的左值與右值的區別就是前者存儲在內存空間中,右者存儲在寄存器中呢?


先說結論:C語言中的左值,右值的概念與寄存器之間基本上沒關係,如果有什麼對應關係,也純屬巧合。

在C中,左右值的區別是指是否可以獲取得到某個變數的地址,從而對其進行賦值(這些都是指的語言層面,即使用C中的運算符可以得到地址的都是左值),右值一般是編譯器在編譯期間生成的臨時變數,這種變數並不能在語言層面得到它的地址,如你給的代碼中的n-1的結果所在的位置,以及函數的返回值。

在C中,某個程序變數(包括程序中聲明的變數和編譯器生成的臨時變數)在運行時保持在當前stack frame或register中是由C編譯器計算出來的。實現這一功能的就是寄存器分配(Register allocation)。以你給的代碼為例(修改傳參方式為值傳遞,不需要引用傳遞),使用LLVM 3.4,運行在64bit Ubuntu 16.04,使用clang -S -emit-llvm factorial.c; llvm-as factorial.ll; opt -mem2reg factorial.bc | llvm-dis &> factorial.ll

生成的LLVM IR如下所示:

define i32 @factorial(i32 %n) #0 {
entry:
%cmp = icmp sle i32 %n, 1
br i1 %cmp, label %if.then, label %if.end

if.then: ; preds = %entry
br label %return

if.end: ; preds = %entry
%sub = sub nsw i32 %n, 1
%call = call i32 @factorial(i32 %sub)
%mul = mul nsw i32 %n, %call
br label %return

return: ; preds = %if.end, %if.then
%retval.0 = phi i32 [ 1, %if.then ], [ %mul, %if.end ]
ret i32 %retval.0
}

如上述代碼所示,%n是一個左值,因為它是一個顯示變數。但是根據X86 System V ABI,它剛開始存放在棧上。%sub是一個臨時變數,該變數在語言層面是一個臨時變數,屬於右值,但是編譯器可以將其分配在register(當寄存器充足的時候)也可以溢出(spill out)到棧幀(stack frame)里(當物理寄存器不夠時)。看到這裡,提主是不是有點感覺了?

至於決定某個變數在某個程序點(程序某一行)放置在棧還是寄存器中,這就是寄存器分配的工作了。你可以詳細了解下

  1. https://en.wikipedia.org/wiki/Register_allocation

2. 寄存器分配問題?

3. 推薦部分學習寄存器分配的材料


左值右值和寄存器沒有絕對的關係。

左值右值是從語言角度定義的,標準並沒有涉及和底層實現有關的規定(比如什麼寄存器,cache,棧,堆…的概念)。既左值右值的存儲細節是和cpu,指令集,ABI,編譯器等有關的。

而且即使是在特定平台上(比如說常見的X86-64),這個結論也不正確。

比如int a = 1,1是prvalue,但1可能作為立即數,並沒有用到寄存器。或者某個右值用的位元組比較多,寄存器放不下,那自然也不可能都存儲在寄存器。而且即使是左指,也可能只存儲在寄存器(比如說某些調用慣例中,函數的參數會放在寄存器),不一定真正存儲在內存。


開個玩笑。

左值右值跟寄存器的關係就跟卡巴斯基和巴基斯坦的關係一樣。

就跟納愛斯和維納斯的關係

就跟維塔士和vista的關係

就跟張涵予和張馨予。。。


不是,左值也可以沒有對應的內存地址,右值變數也不見得就不能在內存中,左右值和是否在內存中沒有任何決定性的關係。

我的理解是在代碼里左值是有名字的,你在作用域都可以通過這個名字去引用這個值,值的生命期就是作用域內;右值沒有名字只有值,生命周期就是用到值的那條語句而已。


瀉藥,題主鼓搗一下「寄存器分配」[llvm-dev] Register Allocation Graph Coloring algorithm and Others 就明白了 :)

我們理論上追求:

  • 更「貪婪」地使用「真實的物理」寄存器,因為從內存、CPU緩存LD很「昂貴」理論上10~100 時鐘周期
  • 無限的「虛擬的、偽的」寄存器,不可能100%分配到,有限的「真實的物理」寄存器,當寄存器實在不夠用,還是得「泄露」到內存 (不是題主以為的寄存器和內存的選擇)
  • 在寄存器分配和「指令級並行」之間保持平衡(註:這是超綱題,在清華《編譯原理》、龍一、龍二都沒有提及,在龍三10.2.3章節 Tradeoff between register usage and parallelism有粗略講解,今年會不會考?咳咳,嗯


……也許這是差別之一,但是根本差別在於

「左邊被賦值而右邊是值啊」

在內存還是寄存器不應該是底層細節么,沒法說一定的吧


推薦閱讀:

LOL盒子這類的輔助工具一般都是用什麼開發的?
一個典型的遊戲循環是怎樣的流程?
國內/外的編程圈子有什麼不一樣?
C語言 C++?
為什麼很多大牛在寫題的時候要加一堆宏?

TAG:彙編語言 | CC | C編程 | 操作系統原理 | 彙編語言王爽著書籍 |