• 理解C++ lvalue与rvalue


    一个众所周知的危险错误是,函数返回了一个局部变量的指针或引用。一旦函数栈被销毁,这个指针就成为了野指针,导致未定义行为。而左值(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++中的变量存储有更深的理解。

    https://www.cnblogs.com/kinsang/p/6855579.html

    堆栈概念一文,我小结了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

    lvalue和rvalue

    在计算机的远古时代,变量的lvalue和rvalue是指:
    lvalue:变量在内存中的位置。通过它能够找到内存中存放的变量(location value);
    rvalue:存放在lvalue对应的内存中的东西(register value);
    C++中的每个表达式要么是lvalue要么是rvalue。lvalue表示一个内存位置,而rvalue表示计算表达式的结果。
    rvalue引用是对有名称变量的引用,并允许变量表示的内存通过lvalue引用来访问。
    rvalue引用是对包含表达式结果的内存位置的引用。

    lvalue引用:

    使用lvalue引用形参,可以编写直接访问调用者实参的函数,避免了按值传递中的隐式复制。若不修改实参,只需要给lvalue引用类型使用const修饰符,以避免意外修改参数。无论是按值传递、按址传递参数或引用都是编译器的规则,我们需要熟悉参数在不同情况下的传递,好的理解方式就是输出地址来观察。

    lvalue引用:

    使用lvalue引用形参,可以编写直接访问调用者实参的函数,避免了按值传递中的隐式复制。若不修改实参,只需要给lvalue引用类型使用const修饰符,以避免意外修改参数。无论是按值传递、按址传递参数或引用都是编译器的规则,我们需要熟悉参数在不同情况下的传递,好的理解方式就是输出地址来观察。

    [cpp] view plain copy
     
    1. #include <iostream>      
    2. using namespace std;  
    3.   
    4. void add_1(int & num)  
    5. {  
    6.     num += 1;  
    7. }  
    8.   
    9. int main()  
    10. {  
    11.     int v = 6;  
    12.     add_1(v);  
    13.     cout <<"v="<< v << endl;  
    14.     return 0;  
    15. }  

    输出结果:v=7.

    rvalue引用:

    首先,举一个报错的例子:
    [cpp] view plain copy
     
    1. #include <iostream>      
    2. using namespace std;  
    3.   
    4. void add_1(int && num)  
    5. {  
    6.     num += 1;  
    7. }  
    8. int main()  
    9. {  
    10.     int v = 6;  
    11.     add_1(v);  
    12.     cout << "v="<<v << endl;  
    13.     return 0;  
    14. }  
    编译会报错: 无法将左值绑定到右值引用。
    因为:lvalue不能通过rvalue引用,有rvalue引用形参的函数只能通过rvalue实参来调用,后面列举正确编译的例子:
    [cpp] view plain copy
     
    1. #include <iostream>      
    2. using namespace std;  
    3.   
    4. void add_1(int && num)  
    5. {  
    6.     num += 1;  
    7.     cout << "num=" << num << endl;  
    8. }  
    9. int main()  
    10. {  
    11.     int v = 6;  
    12.     int s = 4;  
    13.     add_1(v+s);  
    14.     cout << "v="<<v << endl;  
    15.     return 0;  
    16. }  
    运行结果:num=11,v=6.

    参考:

    http://blog.chinaunix.net/uid-7471615-id-83794.html

    http://blog.csdn.net/rogerhe/article/details/6410993 

    http://www.cnblogs.com/yunqie/p/5892252.html 

    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 No sacrifice , no victory.
  • 相关阅读:
    数据库的......
    数据库
    XML
    网络编程
    I/O系统---流
    周结

    集合,框架
    Spring入门
    Java Wed
  • 原文地址:https://www.cnblogs.com/pityhero233/p/8601319.html
Copyright © 2020-2023  润新知