C 标准将 lvalue 定义为:
左值是一个可能指定对象的表达式(具有除 void 之外的对象类型);64) 如果左值在求值时未指定对象,则行为未定义。当一个对象被认为具有特定类型时,该类型由用于指定该对象的左值指定。可修改左值是不具有数组类型、不具有不完整类型、不具有 const 限定类型的左值,并且如果它是结构体或联合,则不具有任何成员(递归地包括任何成员)或所有包含的聚合或联合的元素)具有 const 限定类型。
我想准确理解该定义的哪一部分阻止“地址”表达式(例如
&ch
)成为左值。当我们获取一个对象的地址时,该地址不是“指定”该对象吗(因此,如果我们有 char ch
,那么 &ch = 10
将相当于 ch = 10
)?或者&ch
指定临时的值,而不是对象? (有没有临时物体之类的东西?)
我的困惑最初是在阅读C 上的指针时产生的,其中提供了以下解释:
优先级表显示
运算符产生 R 值作为结果,并且它们不能用作 L 值。但为什么?答案很简单。当计算表达式&
时,结果保存在计算机中的哪里?它一定在某个地方,但你无法知道在哪里。 该表达式不识别机器内存中的任何特定位置,因此不是左值。&ch
我对粗体部分感到困惑。
&ch
不知道ch
的位置吗?
显然&ch
的
contents确实代表了机器内存中的一个特定位置,即
ch
的地址。这本书的意思可能是,临时结果 &ch
本身可能存储在寄存器等中,并且仅根据 C 代码我们无法知道在哪里。它实际上也不是 ch
的地址,而是该地址的副本。
示例:
int x = 1;
int* ptr = &x;
printf("%p\n", ptr);
这会产生以下汇编程序(gcc 14 x86):
lea rsi, [rsp+12]
mov DWORD PTR [rsp+12], 1
call printf
即:将分配
x
的堆栈地址存储在寄存器rsi
内。将值 1 移至堆栈上的该位置。
现在,如果我们以某种方式在这里注入一些内联汇编程序并写入寄存器
rsi
,它就不会影响x
实际存储的位置。
一般来说,我们不希望程序员更改变量的地址,因为这会改变编译器和链接器的整个工作方式。如果需要的话,编译器会为每个变量提供一个地址(并且未放置在寄存器中),并且根据该地址,编译器可以推断出特定地址中存储的内容并相应地生成代码。如果我们想要更改变量的存储位置,我们通常通过将内容复制到新的地址位置来实现。
因此,变量的地址被视为只读,并且仅出于这个原因,
&
运算符就必须产生“右值”。我们通常不会在运行时更改存储地址,只是使用指针指向内存中的其他位置。
还值得注意的是
&
和 *
运算符的作用彼此相反。 *&x
相当于 x
,因为运算符相互抵消。 &
始终产生右值,但 *
始终产生左值。同样,&*ptr
等价于 ptr
,但它是一个右值。