http://sunxiunan.com/?p=1628
lvalue算是C語言裡面不怎麼太容易說清楚的概念,我們上學的時候多半稱之為left-value左值,對應的還有在C++標準中的rvalue,也就是右值。
在wiki百科上http://en.wikipedia.org/wiki/Value_%28computer_science%29 解釋了一些。
首先什麼是value?value也好object也好在電腦內部的表示都是0和1,沒有什麼區別,某一塊記憶體位址的資料,按照整數解釋是一個值,按照class CObject解釋又是另外一個值,浮點數也好字串也好,如果光看這個記憶體位址裡面的資料是沒法看出什麼究竟的。所以需要編譯器做一些處理在某個地方記錄下來類型資訊。
上面文字翻譯成中文就是lvalue是程式運行時可以通過地址(address)資訊編程存取的值(比如通過&操作符),意味著他們是變數或者可以提領(dereferenced,通常用*操作符)某一塊特定記憶體位置。在C++標準中說明:不是lvalue的值就是rvalue。我們不能把rvalue作為lvalue使用,但是反過來可以,比如int a = b; 其中變數b可以為lvalue。
在偉大的K&R第二版中197頁寫道:一個對象是被命名的儲存地區或者被指向一Block Storage地區(an object is a named or pointed to region of storage);一個lvalue是一個指向對象的運算式(an lvalue is an expression referring to an object),(!!注意lvalue是一個運算式,這在後面例子中會體現出來)。。。lvalue這個名字來自賦值運算式E1 = E2,其中左邊的運算元E1必須是一個lvalue。
在Dan Saks的文章中提到,一個rvalue之所以不引用一個object,不是因為不能,而是因為不需要(Conceptually, an rvalue is just a value; it doesn’t refer to an object. In practice, it’s not that an rvalue can’t refer to an object. It’s just that an rvalue doesn’t necessarily refer to an object. Therefore, both C and C++ insist that you program as if rvalues don’t refer to objects.)
我們拿int n = 1; 來做說明,如果1是一個lvalue,那麼產生的彙編代碼類似下面這樣:
one: .word 1
…
mov (one), n
實際上對於大多數機器而言,其實是對n直接操作,類似:
mov #1, n
在這裡1沒有像我們想象的那樣指向一個object(如one),而是直接就操作了。甚至還有的機器產生彙編代碼如:
clr n
inc n
先把n清零,然後增加n的值,這裡面根本就沒有出現1。
MSDN部落格上http://blogs.msdn.com/vcblog/archive/2009/02/03/rvalue-references-c-0x-features-in-vc10-part-2.aspx
有一篇非常詳細的文字解釋c++中的lvalue,rvalue,有興趣的可以去看看。其中最為重要的一句話就是“another way to determine whether an expression is an lvalue is to ask "can I take its address?". If you can, it’s an lvalue. If you can’t, it’s an rvalue. ”翻譯為中文就是“決定一個運算式是否是lvalue的方法可以是:我能不能擷取它的地址?如果可以,那就是lvalue,否則就是rvalue。”另外需要著重說明的是,lvalue一定可以擷取地址,但不要求是可以修改的(如const,如後面提到的字串字面值)。
加法操作一直返回一個rvalue,比如m+ 1 = n; 這就沒法編譯通過,因為 m + 1 這個運算式是一個rvalue。
針對lvalue中的l,我們也可以認為是location,其實也就是能不能擷取地址的另一種意思,這也就是為何數字型字面值以及非引用型函數不是lvalue的原因。
char(*pchar)[] = &("aabbccddee");
有些文章說字串字面值(string literals)算是lvalue,但是不能改變這個值,上面的代碼就是一個示範。
*&("aabbccddee") = ‘a’;
這個指派陳述式在VC6下編譯通過(這是錯誤的!),在Xcode中出錯提示“對read-only location賦值”,在VC2008下提示"left operand must be l-value"。所以還是建議大家用新版本的編譯器來學習工作,否則很容易造成誤解。
下面我舉幾個例子,大家看看能否判斷是lvalue還是rvalue。
obj , *ptr , ptr[index] , ++x, 1729 , x + y , std::string("meow") , x++
在VC6中編寫一個最簡單的操作台程式,輸入下面代碼:
int main(int argc, char* argv[])
{
int obj = 0;
int* ptr = &obj;
int x = 1;
int* ptr2 = &(++x);
int* ptr3 = &(x++);
return 0;
}
編譯結果是testconsole1.cpp(12) : error C2102: ‘&’ requires l-value,第12行就是int* ptr3 = &(x++);
稍微修改一下這段代碼讓它做一下deference操作:
int main(int argc, char* argv[])
{
int x = 1;
*&(++x) = 9;
*&(x++) = 10;
return 0;
}
同樣也是在x++這一行編譯不通過。儘管x++或者++x並沒有修改x的記憶體位置,但x++這種形式就是rvalue,沒法對這個運算式進行提領(*)操作。很有意思吧,這裡面應該還有更說得通的理由,希望有高人指點一下。(在msdn blog上的解釋是x++這種形式會產生臨時對象,但那是針對C++而言,不知道對於C來說是不是也是同一種解釋)
既然是運算式,函數也是運算式一種形式,是不是也可以作為lvalue出現呢?stackoverflow上有一個問題裡面舉了一個很有意思的例子http://stackoverflow.com/questions/579421/often-used-seldom-defined-terms-lvalue
#include "stdafx.h"
static int aa = 0;
int* func() {
aa++;
return &aa;
}
int main(int argc, char* argv[])
{
*func() = 42;
printf("%d", aa);
return 0;
}
在VC6下運行結果為43(?!),而VC2008以及Mac XCode3.1運行結果為42,在這裡VC6好像是有一些問題啊。當然我們期望aa結果應該是42.
相關參考文檔:ISO C99標準
《The New C Standard》http://www.coding-guidelines.com/cbook/cbook1_2.pdf
《C Programming Language》K&R第二版
http://publications.gbdirect.co.uk/c_book/
http://www.cs.dartmouth.edu/~chris/cs23/summit-intro/
http://www.embedded.com/story/OEG20010518S0071