学编程的闹钟 发表于 2024-3-8 14:48

C语言指针和左值

本帖最后由 学编程的闹钟 于 2024-3-8 14:49 编辑

首先我们要知道内存是如何存放变量的:通过变量名对变量进行访问和存储是为了方便程序员而设计的,其实在内存中完全没有存储变量名的必要。因为编译器知道具体每一个变量名对应的存放地址,所以当你读取某个变量的时候,编译器就会找到变量名所在的地址,并根据变量的类型读取相应范围的数据。通俗的来说就是变量名就是个地址,存这个地址存放的是一个不同类型的值。

我们先通过变量名来访问一个整型变量的地址,代码如下:
#include<stdio.h>
int main(void)
{
    int a=77;//当然这里不被赋值也可以。
    printf("%d",&a);//这里在a前面加了个&取地址符,得到的是a的地址。
    printf("%p",&a);//这里%p是通过十六进制输出地址,上面一行代码则是十进制。
    return 0;
}


上面这段代码十进制的地址是:6356764。十六进制地址是:0060FF1C。有些怀疑的朋友可以通过计算器转换试试哈。每次地址都不会一样,所以最好先通过查找一个int变量的地址,在用那个地址-4得到我们要存放值的地址,这样能最少与当前地址之前的存放的东西冲突,就会报错。

好,接下来我们就用十进制的地址的方法给大家演示。上面我们知道了一个十进制地址:6356764。因为int类型占4字节,所以我们就6356764-4=6356760。我们就用这个地址来做演示。

我们想把一个值存放到一个地址里面去都先想的是这样:
#include<stdio.h>
int main(void)
{
    6356760=77;//我们想把6356760想成是一个地址,而编译器只会认为这是一个常量,所以会报错。
    return 0;
}


当然上面这段代码肯定是错误的,因为编译器根本不知道6356764是一个地址,它只会认为这是一个常量。

上面报错信息为:lvalue required as left operand of assignment。翻译过来就是:左值必须作为赋值的左操作数。

那我们就强制转换为地址,也就是指针类型,代码如下:

#include<stdio.h>
int main(void)
{
    (int*)6356760=77;//这里强制转换成了整型指针类型。
    printf("%d\n",(int*)6356760);//我们想要输出这个地址的值,也就是77。
    return 0;
}


有些朋友可能会认为这样就可以运行了,但其实不然。

它还是会报错:lvalue required as left operand of assignment。翻译过来就是:左值必须作为赋值的左操作数。

是不是觉得还差了啥,没错差个解引用,没有这个符号怎么能给内存赋值呢。代码如下:

#include<stdio.h>
int main(void)
{
    *(int*)6356760=77;//这里强制转换成了整型指针类型,再用取地址符分配内存。
    printf("%d\n",*(int*)6356760);//我们想要输出这个地址的值,也就是77。
    return 0;
}


这样代码即没报错,也没失败,成功的打印出了77。

所以这里没有变量名来分配地址也能成功分配地址。所以不要死脑筋。不过有时那个地址被占用了,要多多换换地址就行。

上面有两个错误的代码中的报错都是:lvalue required as left operand of assignment。翻译过来就是:左值必须作为赋值的左操作数。

那么到底什么是左值(lvalue)和右值(rvalue)呢?

你会经常看到 lvalue 这个词。一般出现在各种书籍中,更频繁遇到的是在你的错误提示中。

一般你会看到这个错误是因为你的代码类似这么写:

#include<stdio.h>
int main(void)
{
    int i;
    5=i;
    return 0;
}

一些朋友可能想当然的就觉得左值(lvalue)指的就是赋值运算符左边的那个值,而右值(rvalue)当然就是右边那个值啦。所以我们叫它们为“左值”和“右值”。

事实上这样理解并不全面,并且常常容易犯思想上的错误。

首先,我们找到 C 语言的作者问问究竟,他是这么说的:

《The C Programming Language》翻译:《C程序设计语言》

"An object is a manipulatable region of storage; an lvalue is an expression referring to an object.

The name 'lvalue' comes from the assignment expression E1 = E2 in which the left operand E1 must be an lvalue expression."

上面书中内容翻译为:

对象是可操纵的存储区域;左值是指对象的表达式。

名称“左值”来自赋值表达式E1=E2,其中左操作数E1必须是左值表达式

因为C语言是在不断发展的,毕竟老头的这本书说的是C语言的原型,自1988年第二版之后就没有再出新版了。而C的标准则经历了 K&RC,C89,C90,C99,C11 的迭代。
那我们找来了目前最广泛使用的 C99 标准:

The name "lvalue" comes originally from the assignment expression E1 = E2, in which the left operand E1 is required to be a (modi?able) lvalue. It is perhaps better considered as representing an object "locator value". What is sometimes called "rvalue" is in this International Standard described as the "value of an expression".

上面书中内容翻译为:

“左值”的名称最初来自赋值表达式E1=E2,其中左操作数E1必须是(可调整的、可修改的)左值。它可能更被认为是表示对象“特定位置的值”。有时所谓的“左值”在这个国际标准中被称为“表达式的值”。

所以,单纯用左边(left-value)和右边(right-value)来理解是不全面的。请尝试执行下边代码:

#include<stdio.h>
int main(void)
{
    int a=5;
    ++(a++);
    return 0;
}

你会得到下边错误提示:lvalue required as increment operand。翻译为:递增操作数需要左值。

在这里如果你认为lvalue只是赋值运算符左边那个值,那这个错误提示就无法理解了。但是如果你知道lvalue是用于识别或定位存储位置的标识符,那么就好解释了:

(a++)是先将变量a的值(5)做为整个表达式的值返回,再将a自增(类似于a=a+1)。
所以这里++(a++);,相当于++(5),a=a+1;
这个当然要报错,5是一个常量,当然不能执行5=5+1。

C 语言的术语lvalue指用于识别或定位一个存储位置的标识符。(注意:左值同时还必须是可改变的)。

除了左值其实还有右值吧,没错,其实rvalue的发明完全是为了搭配lvalue,rvalue你可以理解为readable value,即任何可读取的值都被认为是右值(非左值)。

书香 发表于 2024-3-8 17:19

这篇文章是介绍指针和左值的吗

学编程的闹钟 发表于 2024-3-8 17:29

书香 发表于 2024-3-8 17:19
这篇文章是介绍指针和左值的吗

是的呀.

书香 发表于 2024-3-8 18:24

学编程的闹钟 发表于 2024-3-8 17:29
是的呀.

可以出一个系列的图文课程

学编程的闹钟 发表于 2024-3-9 09:15

书香 发表于 2024-3-8 18:24
可以出一个系列的图文课程

嗯,以后会出的
页: [1]
查看完整版本: C语言指针和左值