一個眾所周知的危險錯誤是,函數返回了一個局部變量的指針或引用。一旦函數棧被銷毀,這個指針就成為了野指針,導致未定義行為。而左值(lvalue)和右值(rvalue)的概念,本質上,是理解“程序員可以放心使用的變量”。
空泛的討論先到這里,先看一段會報錯的代碼:
#include <iostream> using std::cout; using std::endl; int foo(int &a) { return a; } int main() { int a = 1; cout << &a << endl; int *p = &foo(a); }
這里,對foo(a)取地址會引起錯誤: "lvalue required as left operand of assignment".字面理解是,&取地址運算符只能獲取左值的地址。
毫無疑問的是,foo(a)的值是存在的,是數值1。之所以報錯,是因為編譯器認為它不是左值,不允許程序員獲取它存放的地址。
我對這點標准的理解是:
獲取一個臨時空間的地址通常意味着要對這塊內存賦值。在C++程序中,臨時空間的銷毀時機是不確定(undefined)的,它隨時被用於其他臨時空間的存儲。於是程序員不允許使用這塊空間。聯系之前總結的堆棧概念,可以對C++中的變量存儲有更深的理解。
在堆棧概念一文,我小結了C++程序中數據可以存在三個地方:
1. 函數棧,在函數體內的定義的變量
2. 堆,特別指使用new,malloc獲取的內存空間
3. 靜態數據區,即.data 和.bss
對於lvalue的通俗描述,是“具有確定地址的非臨時對象”,而不滿足lvalue定義的值均被認為是rvalue。換句話說,C++程序里面出現的值,非左即右。下面我們分析一下這三個存放數據的區域里面可以被使用的值的情況:
堆
堆的空間上的變量完全由程序員申請和管理的,所以它們都有明確的地址,是可以放心使用的左值。
靜態數據區
對於靜態數據區,盡管存放的位置是固定的,但里面的數據並不能認為都是左值。主要是因為里面有“字面值”,包括const所實現的常量,即靜態存儲而不能被修改的值。
函數棧
當函數調用發生的時候,系統會創建函數棧,保留上下文,函數調用結束的時候,函數棧內的變量會被銷毀。函數體里面定義的變量是左值,而臨時變量是右值。
拓展
C++ 11標准,為了更好地利用臨時變量,提出Rvalue Reference,對應的的實現是move semantics (轉移語意)和Perfect Forwarding(完美轉發)。對這些新特性還不了解,暫時不寫。
參考:
http://www.cnblogs.com/dejavu/archive/2012/09/02/2667640.html
http://stackoverflow.com/questions/230584/where-are-variables-in-c-stored
http://eli.thegreenplace.net/2011/12/15/understanding-lvalues-and-rvalues-in-c-and-c