• [C]左值


    一、概述

    左值是一个很让人困惑的概念,通常一条赋值表达式,例如x = y; 左边的操作数一定要是一个左值才能够被赋值,否则编译器就会报错:

    error: lvalue required as left operand of assignment

    要搞清楚左值的含义,首先要理解C语言的“对象”这一概念:

    在C语言中,对象(object)指的是在内存中的一个位置,其内容可以用来表示某个值。

    左值,指的就是内存中有具体位置的对象。

    对象能出现在赋值表达式的左边进行赋值操作,所以它是一个左值。

    有些表达式,它只产生一个值,却没有指示一个对象,这种表达式就是右值。

    左值可以出现在赋值表达式的任意一边,而右值就只能出现在右边。

    左值一定可以被解析出对应对象的地址,除非此对象是位字段,或者被类型限定符定义为const了。

    左值的运算符包括下标运算符[]和间接运算符*。

    C语言规定函数的返回值始终不是左值(C++会有例外情况)。

    二、示例

    1.比方说声明一个变量int x = 6;

    x就是左值,它在内存中的地址是:&x,指针类型是int*。它是一个有位置的对象。

    (x+1)则不是一个左值,这个表达式是x中保存的一个int类型数据(即6)加上1的结果,它代表一个值,它并不是内存中有具体位置的对象。

    这意味着你不能这样为它赋值:(x+1) = 8;

     

    2.上面是一个很简单的示例,但通常事情会显得相对复杂一点:

    例如数组int arr[3] = {1, 2, 3};

    arr+1得出的是一个新的指针,按照惯性思维,你可能会觉得它是一个左值,毕竟指针代表着内存地址(请参考指针运算)。

    实际上它不是一个左值,因为地址值也只是一个数字罢了,0xff和127没有区别。

    但是把这个地址值加上间接运算符*后,它的含义就变了,变成了“以int类型访问这个内存空间",这样它就变成了有空间的对象,现在它是一个左值了:*(arr+1)。

    #include <stdio.h>
    
    int arr[3] = {1,2,3};
    
    void main(void)
    {
        printf("%d
    ", *(arr+1));//输出2
        *(arr+1) = 20;
        printf("%d
    ", arr[1]);//输出20
    }

    3. 再看看这个例子:

    #include <stdio.h>
    
    int arr[3] = {5,9,12};
    
    void main(void)
    {
        printf("%d
    ", *arr+1);
    }

    由于运算符优先级的问题(间接运算符比算术运算符优先级高),所以这里的表达式*arr+1也只是产生一个值而已(*arr的值5+1=6)。

    4.再来看一个相对更加复杂一点的例子:

    #include <stdio.h>
    
    int arr[3] = {6, 7, 8};
    
    int main(int argc, char const *argv[])
    {
        printf("%d
    ", *++arr);
        return 0;
    }

    根据运算符优先级的特性(请参考运算符优先级一文),表达式(*++arr)的运行顺序是先执行对arr的递增,然后再进行解参考运算;

    理论上如果arr是一个指针类型的变量,那么这个表达式是没有任何问题的,arr执行的是对指针的偏移操作(参考指针运算);

    但是,这里的arr只是一个指针类型的值,而不是一个变量!换而言之,它不是一个左值,而递增递减操作符要求操作数一定要是一个左值,

    于是编译器会报错:

    1.c:7:18: error: lvalue required as increment operand
      printf("%d
    ", *++arr);
                      ^

     

     倘若需要进行类似操作,你必须确保操作数是一个左值,像这样是理想的:

    printf("%d
    ", *(arr+1));

    因为表达式(arr + 1)运算只是产生了一个类型为int指针的值,并不需要给任何对象赋值,接着用*为该指针类型的值解参考;

    又或者可以这样:

    int* ptr = arr;
    printf("%d
    ", *++ptr);

     ptr是可以运行递增操作的,因为ptr是一个对象,这个对象保存的是一个int类型指针,递增操作改变了ptr,使它编程了指向下一个元素的指针;

    两者结果都是正常的输出元素7;

    5.通常地,函数的返回值都不是一个左值,无论返回值是什么类型。

    例如,返回值是一个指针,那么它仅仅是一个代表内存地址的数字罢了,要访问它指向的对象,必须加上间接运算符*;

    又例如,返回值是一个整型,在赋值给一个空间之前,这个整型并不具备任何可操作空间,想象一下你如何运用地址操作符&拿到函数返回结果的地址值?答案是不能的;

    所以函数返回的结果,都是数据,不是左值。

    struct Article getArticle(int id);
    printf("%s
    ", getArticle(3).content);

    以上代码函数getArticle()返回一个Article的结构(假设该结构包含成员content),所以点运算符在这里是合法的,但是getArticle()的返回结果不是一个左值,

    你无法对它进行类似这样的赋值操作:

    getArticle(3).content = "some text";//illegal

     

    6.结合表达式和运算符优先级的概念,再来看看一个有趣的例子:

    int main(int argc, char const *argv[])
    {
        int x = 1;
        ++x++;
        return 0;
    }

    这段代码会抛出操作数不是左值的错误信息:

    1.c: In function ‘main’:
    1.c:7:2: error: lvalue required as increment operand
      ++x++;
      ^

     

    原因是由于运算符优先级的关系,x++比++x具有更高的优先级,所以x++先运行了。

    其实无论哪个表达式先运行,它运行的结果都是产生一个值,而接下来运行的表达式将会基于这个值进行运算。

    x++优先运行,它产生了一个值,这个值等于x的本身,其实关注点不在这个值是多少,而是,x++运行后,后面的表达式只是基于它运行后的值接着运算。

    而这个时候它已经不是一个左值,但是前序++运算符需要一个左值作为操作数,所以它报错了。

    即使把表达式改为:(++x)++,也无济于事,报错依旧,只不过这次轮到了后序++运算符报错:

    1.c:7:7: error: lvalue required as increment operand
      (++x)++;
           ^

     那么,

    x+++x++

    可不可以运行呢,答案是可以的,因为运算符优先级的原型,以上式子实际上是按照这个顺序运行的:

    (x++) + (x++)

    虽然这个表达式可以运行,但是是不推荐的,尽量不要在两个序列点直接改变同一个变量超1次;

     

  • 相关阅读:
    docker 安装 mysql5.7
    docker 安装 redis
    docker 安装 gitlab
    docker 升级到新版本
    logstash 采集springboot 错误日志配置
    图片左下角添加水印
    frida动态修改
    反反frida调试
    IDA插件KeyPatch直接在IDA中修改arm指令
    frida调用制作dex(用于有些对象读取不了)
  • 原文地址:https://www.cnblogs.com/yiyide266/p/11753261.html
Copyright © 2020-2023  润新知