JM233333's Blog
  • Programming Languages

    • C
    • UNIX-C
    • Python
  • Algorithms and Data Structures

    • Data Structure
    • Fundamental Algorithms
    • Graph Theory
  • GNU Toolchain

    • Bash
    • gdb
  • Development Environment

    • Ubuntu
    • QEMU
  • Development Tools

    • Git
    • VSCode
  • Operating Systems

    • Principles of Operating Systems
    • Xv6
    • Linux Kernel
  • Software Testing and Analysis

    • Software Testing
    • Software Analysis
    • Program Verification
  • LeetCode
  • XJTUOJ
  • Programming

    • Is Parallel Programming Hard
  • System

    • System Performance
  • Others

    • ...
  • Paper Reading

    • Model Checking
    • Fuzzing
    • Symbolic Execution
  • 3D Game Programming

    • 3D Mathematics
  • Miscellaneous

JM233333

弱小可怜又无助的学术废物
  • Programming Languages

    • C
    • UNIX-C
    • Python
  • Algorithms and Data Structures

    • Data Structure
    • Fundamental Algorithms
    • Graph Theory
  • GNU Toolchain

    • Bash
    • gdb
  • Development Environment

    • Ubuntu
    • QEMU
  • Development Tools

    • Git
    • VSCode
  • Operating Systems

    • Principles of Operating Systems
    • Xv6
    • Linux Kernel
  • Software Testing and Analysis

    • Software Testing
    • Software Analysis
    • Program Verification
  • LeetCode
  • XJTUOJ
  • Programming

    • Is Parallel Programming Hard
  • System

    • System Performance
  • Others

    • ...
  • Paper Reading

    • Model Checking
    • Fuzzing
    • Symbolic Execution
  • 3D Game Programming

    • 3D Mathematics
  • Miscellaneous
  • c

    • C 00 - Introduction
    • C 01 - Summary of Basic Syntax (unfinished)
    • C 01 - Summary of Basic Debugging Skills
    • C 01 - Develop Good Coding Style and Programming Habits
    • C 01 - Convention of Naming Identifier
    • C 01 - The Right Way to Ask Others for Help
    • C 01 - Summary of Common Mistakes for Beginners
    • C 02 - Pointer
    • C 03 - Function
    • C 03 - Types
    • C 03 - Scope and Lifetime
    • C 03 - Input and Output
    • C 03 - Undefined Behavior and Unspecified Behavior
    • C 04 - Dynamic Memory Allocation
    • C 04 - Type Conversion
    • C 04 - Declaration and Definition
    • C 04 - Lvalue and Rvalue
      • 前置知识
      • 前言
      • 左值
        • 基本概念
        • 详细定义
      • 右值
      • 实践作用
    • C 04 - Organizing Multiple Source Files (unfinished)
    • C 04 - Summary of Piecemeal Knowledge (unfinished)
    • C 05 - Memory Layout and Rules
    • C 05 - Improve Your Code with [typedef] (unfinished)
    • C 05 - Array Name is NOT a Pointer
    • C 05 - More about Input and Output (unfinished)
    • C 06 - More about the Structure Type
    • C 06 - Macro (unfinished)
    • C 06 - Do not Abuse Side Effects in Subexpressions
    • C 06 - Overview of C Standards
  • unix-c

  • cpp

  • java

  • python

  • programming-languages
  • c
JM233333
2020-07-01
2316
20

C 04 - Lvalue and Rvalue

Creative Commons

# 前置知识

在阅读本文档前,请确保你已经掌握类型的基础知识,详见 C 语言的 03 - Types 文档。

  • C 03 - Types

# 前言

左值和右值的概念是 C 语言标准中有明确规定的,它们为语法中的一些常见的、基础的、易于解释的简单现象或结论提供了理论上的规范和解释。不知道是什么原因,国内的课程或教材极少会介绍左值和右值的相关概念,它们的概念并不复杂,却有着较为重要的意义,完全没有将其省略的必要。

对于 C 语言开发者而言,左值和右值的概念其实不是必要的,哪怕你对此一无所知,也并不影响平时的编程,但是对于帮助理解编译信息和深入学习 C 语言来说,了解这些概念是很有意义的。

(不过,在 C++ 语言中,左值和右值的概念对于学习 C++11 及以上的一部分新特性(例如右值引用)来说是必要的前置知识。另外,左值和右值的概念在 C 语言和 C++ 语言中有微妙的差异,还请注意。)


# 左值

# 基本概念

左值 (l-value, left value) 是指标识了对象的内存位置的表达式,它有时也被称为 locator value 。左值可以出现在赋值运算符 = 的左侧或右侧,通常表示为标识符。

左值所表示的内容一定是在内存中具有确切可识别的地址的,例如你通常定义的各类变量的名称几乎都是左值,而例如一些字面量整数值就不是左值。

如果左值所标识的内存位置是可修改的,则称其为 可修改的左值 (modifiable l-value) 。例如数组类型和被 const 修饰的类型就不是可修改的左值,因为你不能直接修改一个数组,也不能直接修改一个 const 变量的值。

“左值”这一名称最初来自赋值表达式,例如对于 a = b ,其中 a 必须是一个(可修改的)左值。

基于左值的概念,我们可以规范地解释大量的基础语法规则。例如:

int a, b;
const int c = 0;
a = 1;
a = b;
3 = a;
a + 1 = b;
c = b;

第 3,43,\ 43, 4 行的代码是合法的;第 5,65,\ 65, 6 行的代码会编译错误,因为 3 和 a + 1 都不是一个左值;第 777 行的代码也会编译错误,因为 c 不是一个可修改的左值。

如果一个标识符引用一个内存位置并且其类型是算术、结构、联合或指针,则它是可修改的左值。例如对于指向了某个内存区域的指针 p 而言, *p 是可修改的左值,用于指定 p 所指向的内存。例如:

int a[5];
int * p = &a[0];
int * q;
*p = 1;
q = p + 2;
*(p + 1) = 3;
p + 1 = q;

类似地,第 777 行的代码会编译错误,因为 p + 1 都不是一个左值;而第 666 行则是正确的,因为 *(p + 1) 是合法的左值。

# 详细定义

在 C 语言中,左值必须是下列表达式之一:

  • 任何类型的变量的名称,例如整型、指针类型、结构体类型的标识符。

  • 下标表达式 [] 的结果,如果结果不是数组类型。

  • 对指针解引用的结果,如果结果不引用数组类型或函数类型。

  • 括号 () 中的左值。

  • 由 const 修饰的表达式。

  • 通过 . 或 -> 访问结构体或共用体类型的成员的结果。

除了括号的规则以外应该都很好理解,就不再列举代码示例了。

现在轻松一下——下面这段看起来很奇怪的代码其实是合法的,因为括号不会改变左值的性质:

int a;
(a) = 1;

下面是 C99 标准中对左值的原文定义:

6.3.2.1 Lvalues, arrays, and function designators 1 An lvalue is an expression with an object type or an incomplete type other than void; if an lvalue does not designate an object when it is evaluated, the behavior is undefined. When an object is said to have a particular type, the type is specified by the lvalue used to designate the object. A modifiable lvalue is an lvalue that does not have array type, does not have an incomplete type, does not have a const-qualified type, and if it is a structure or union, does not have any member (including, recursively, any member or element of all contained aggregates or unions) with a const-qualified type.


# 右值

右值 (r-value, right value) 通常是指存储在内存中某个地址的数据值,是无法为其分配值的表达式,它有时也被称为 value of the expression 。右值只能出现在赋值运算符 = 的右侧。

从概念上讲,右值只是一个数据值,在 C 语言中右值不像左值那样引用某个对象(但在 C++ 中右值并非一定不能引用对象)。

右值的描述看起来和左值很像,但实际上完全不同。左值强调的是标识出指定对象的内存位置,而右值所标识的内存地址通常是匿名的或临时的,所指的通常是表达式计算的中间结果,例如:

int a = 1;
int b = a + 1;

在 C 程序被转换为汇编语言后,第 222 行的代码会被分解为若干个步骤,其中 a + 1 会在过程中被存储在临时的位置(例如可能在寄存器中),这也就是右值的定义中所述的“存储在内存中某个地址”。但这个地址通常是无法由上层程序直接访问的,你不能去修改 a + 1 的值,例如令 a + 1 = 2 。

在 C 语言中右值的概念不太重要,事实上 C 标准甚至没有给出“右值”这一概念的明确定义,只将右值简单地称为 value of the expression 。所以在 C 语言中我们一般只需重点关注左值的相关问题。


# 实践作用

许多 C/C++ 编译器的编译信息中都引用了左值和右值的概念,了解左值和右值有利于我们阅读理解这些信息。例如:

int a = 1, b = 2;
a + 1 = b;

上述代码会报告如下编译错误:

|2| error: lvalue required as left operand of assignment

其字面意思为“需要左值作为赋值的左操作数”。如果你不理解左值是什么,看到这个报错很可能会一头雾水。而现在事情就简单多了,显然编译错误是因为你在赋值运算符的左侧误用了一个右值表达式 a + 1 。

当然,这种简单的问题不需要看懂报错也能解决,但在复杂的程序中,如果你理解左值和右值的概念,就可以非常高效地判明错误的来源,否则看到 lvalue 啥的你可能就要陷入迷惑了。

此外,因为 C/C++ 标准中事实上大量引用了左值和右值的概念,所以了解左值和右值有利于我们阅读相关文献和资料。一些复杂的问题可能很难用通俗的语言去描述,但是用左值和右值的理论就很容易描述清楚。


#基础教程#编程语言#C

← C 04 - Declaration and Definition C 04 - Organizing Multiple Source Files (unfinished)→

最近更新
01
Reading Papers - Kernel Concurrency
06-01
02
Linux Kernel - Source Code Overview
05-01
03
Linux Kernel - Per-CPU Storage
05-01
更多文章>
Theme by Vdoing | Copyright © 2019-2023 JM233333 | CC BY-NC-SA 4.0
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式