95、左值与右值:什么是左值?什么是右值?

说实话,左值和右值这个概念,我见过太多人栽跟头了。

刚入行那会儿,我自己也迷糊过。明明代码编译过了,跑起来却莫名其妙报错。后来一查,原来是赋值号左边放了个不能放的东西。嗯,这就是左值右值没搞明白。

一、先看个例子,感受一下

int a = 10;   // 这行没问题
10 = a;       // 这行编译报错

为什么第一行可以,第二行不行?

因为 a 是左值,10 是右值。赋值号左边必须放左值,右边可以放左值或右值。

核心定义:

  • 左值:能取地址、有内存位置、可以放在赋值号左边的表达式
  • 右值:不能取地址、没有固定内存位置、只能放在赋值号右边的表达式

二、左值到底是什么?

左值,说白了就是「有地址的东西」。

你想想看,变量 int x 在内存里占了一块空间,我能用 &x 拿到它的地址。那 x 就是左值。

int x = 5;
int *p = &x;   // 合法,x是左值
x = 10;        // 合法,x是左值

我在项目中遇到过一个问题:有人写了个函数返回结构体,然后直接给返回值赋值。编译过了,运行却崩溃。为什么?因为函数返回值是右值,不能放在赋值号左边。

判断左值的小技巧:

能不能对它取地址?能 → 左值。不能 → 右值。

三、右值又是什么?

右值就是「没有固定地址的临时值」。

比如 5a + b 的结果、函数返回值。这些东西用完就扔,没有长期住的地方。

int a = 3, b = 4;
int c = a + b;   // a+b的结果是右值,临时存在寄存器里
int *p = &(a+b); // 编译报错,不能对右值取地址

我曾经在调试一个嵌入式程序时,看到同事写了这样的代码:

void func(void) {
    return 100;
}

int main() {
    func() = 200;  // 编译报错!func()是右值
    return 0;
}

他一脸困惑:「函数明明返回了 int,为什么不能赋值?」

我告诉他:函数返回值是右值,没有内存地址,不能当左值用。

四、左值和右值的转换

有些情况下,左值可以变成右值。比如:

int x = 10;
int y = x;   // 这里的x作为右值使用,取的是x的值

但反过来,右值不能变成左值。除非你用指针或者引用去「接住」它。

注意:

C语言里,数组名是左值,但数组名不能放在赋值号左边。因为数组名是常量指针,不能修改。

int arr[5];
arr = NULL;  // 编译报错,arr是左值但不能赋值

五、常见陷阱汇总

表达式 左值/右值 能否赋值
int a 左值
a + 1 右值 不能
++a 左值
a++ 右值 不能
func() 右值 不能
*p 左值

这里有个细节:++a 返回的是 a 本身,所以是左值。而 a++ 返回的是 a 的副本,所以是右值。我刚开始学的时候也搞混过,后来记住一句话:「前置返回自己,后置返回副本」。

六、知识体系图

表达式 左值 右值 有内存地址 可取地址 & 可放赋值号左边 临时值 不能取地址 只能放赋值号右边 示例:int a = 10; → a是左值,10是右值 判断方法:能不能取地址?能就是左值,不能就是右值

七、避坑指南

我曾经在写一个通信协议栈时,遇到过一个隐蔽的bug。代码大概是这样的:

int get_status(void) {
    return 0;
}

void process(void) {
    int *p = &get_status();  // 编译报错
}

我当时想:「函数返回一个int,我取它的地址有什么问题?」

问题大了。函数返回值是右值,它可能存在寄存器里,也可能在栈上被立即销毁。你取它的地址,拿到的是一个已经失效的地址。这就是典型的「悬空指针」隐患。

我的建议:

写代码时养成一个习惯:看到赋值号左边的东西,先问自己一句「这东西能取地址吗?」。如果不能,就别往左边放。

还有一个常见坑:字符串字面量。

char *s = "hello";
s[0] = 'H';  // 运行时崩溃!

字符串字面量 "hello" 是右值,存在只读区。虽然 s 是左值,但 s[0] 试图修改只读内存,直接崩掉。嗯,这个坑我踩过不止一次。

八、总结

左值和右值,说白了就是「有家的」和「没家的」。

  • 左值有固定地址,能取地址,能放赋值号左边
  • 右值没有固定地址,不能取地址,只能放赋值号右边
  • 数组名是左值但不能赋值
  • 函数返回值是右值
  • 前置++是左值,后置++是右值

搞懂这个,很多编译错误和运行时崩溃你一眼就能看出来。别问我怎么知道的——都是血泪换来的经验。


公众号:蓝海资料掘金营,微信deep3321