1 内存和地址1 1 内存在讨论内存和地址之前,让我们从生活中的一个例子开始:假设你住在一个
1. 内存和地址
1.1 内存
在讨论内存和地址之前,让我们从生活中的一个例子开始:假设你住在一个有100个房间的宿舍楼里,但这些房间没有编号。如果你的朋友来找你玩,他必须逐个房间寻找,这样效率很低。然而,如果我们根据楼层和房间的位置为每个房间编号,那么朋友只要知道房间号就能快速找到你。
将这个例子应用到计算机中,情况又如何呢?我们知道,计算机的CPU(中央处理器)在处理数据时,需要从内存中读取数据,并将处理后的数据存回内存。我们购买电脑时,通常看到的内存容量是8GB、16GB或32GB等。那么,如何高效管理这些内存空间呢?
实际上,内存被划分为一个个内存单元,每个内存单元的大小为一个字节。
计算机中常见的单位(补充):
一个比特位可以存储一个二进制的位1或者0。
每个内存单元相当于一个学生宿舍,一个字节的空间内可以容纳8个比特位,就像宿舍里的八人间,每个人代表一个比特位。每个内存单元都有一个编号(相当于宿舍的门牌号),有了这个编号,CPU就可以快速找到特定的内存空间。
在生活中,我们把门牌号称为地址;在计算机中,内存单元的编号也被称为地址。在C语言中,地址被称为指针。
因此,我们可以理解为:内存单元的编号 == 地址 == 指针。
1.2 如何理解编址
CPU访问内存中的某个字节空间,必须知道这个字节空间在内存中的位置。由于内存中的字节数量庞大,因此需要对内存进行编址(就像给众多宿舍编号一样)。计算机中的编址并不是记录每个字节的地址,而是通过硬件设计完成的。
钢琴和吉他上并没有标注“剁、来、咪、发、唆、拉、西”这样的信息,但演奏者依然能准确找到每个琴弦的位置。这是为什么呢?因为制造商已经在乐器的硬件层面上设计好了,并且所有演奏者都知道这种约定。
首先,必须理解计算机内部有很多硬件单元,这些硬件单元需要协同工作。所谓的协同,至少需要能够进行数据传递。然而,硬件与硬件之间是独立的,那么如何通信呢?答案很简单,用“线”连接起来。
CPU和内存之间也有大量的数据交互,因此,两者也必须用线连接。不过,我们今天关注一组线,称为地址总线。硬件编址也是如此。我们可以简单理解,32位机器有32根地址总线,每根线只有两种状态,表示0或1(电脉冲的有无),一根线就能表示两种含义,两根线就能表示四种含义,依此类推。32根地址线就能表示2^32种含义,每种含义都代表一个地址。地址信息被下达给内存,内存就可以找到该地址对应的数据,并通过数据总线将数据传回CPU的寄存器。
指针变量和地址2.1 取地址操作符(&)
理解了内存和地址的关系后,我们再回到C语言。在C语言中,创建变量实际上是在内存中申请空间。例如:
如上所示的代码创建了一个整型变量a,内存中申请了4个字节用于存放整数10,每个字节都有地址,如图所示,4个字节的地址分别是:
那么我们如何得到a的地址呢?这里需要学习一个操作符(&)——取地址操作符。
虽然整型变量占用4个字节,但我们只要知道第一个字节的地址,就可以顺藤摸瓜访问到4个字节的数据。
2.2 指针变量和解引用操作符(*)
2.2.1 指针变量
通过取地址操作符(&)得到的地址是一个数值,例如:0x006FFD70。这个数值有时也需要存储起来,以便后期使用。我们将这样的地址值存放在指针变量中。例如:
指针变量也是一种变量,这种变量用于存储地址,存放在指针变量中的值都被理解为地址。
2.2.2 如何拆解指针类型
这里pa左边写的是int,是在说明pa是指针变量,而前面的int是在说明pa指向的是整型(int)类型的对象。
如果有一个char类型的变量ch,ch的地址应该放在什么类型的指针变量中呢?
2.2.3 解引用操作符
我们将地址保存起来,未来是要使用的,那么如何使用呢?
在现实生活中,我们使用地址找到一个房间,在房间里可以取走或存放物品。
C语言中也是如此,只要拿到了地址(指针),就可以通过地址(指针)找到指向的对象。这里必须学习一个操作符——解引用操作符(*)。
上面的代码中第7行就使用了解引用操作符,pa的意思是通过pa中存放的地址找到指向的空间,pa实际上就是a变量;所以*pa = 0,这个操作是将a改成了0。
如果目的是把a改成0,直接写成a = 0;不就行了,为什么还要使用指针呢?实际上,这里是将a的修改交给了pa来操作,这样对a的修改就多了一种途径,写代码会更加灵活,后期慢慢就能理解了。
2.3 指针变量的大小
前面的内容我们了解到,32位机器假设有32根地址总线,每根地址线出来的电信号转换成数字信号后是1或者0,我们把32根地址线产生的二进制序列视为一个地址,那么一个地址就是32个bit位,需要4个字节才能存储。
如果指针变量是用来存放地址的,那么指针变量的大小就必须是4个字节的空间。
同理,64位机器假设有64根地址线,一个地址就是64个二进制位组成的二进制序列,存储起来需要8个字节的空间,指针变量的大小就是8个字节。
结论:
在32位平台下,地址是32个bit位,指针变量大小是4个字节。在64位平台下,地址是64个bit位,指针变量大小是8个字节。注意,指针变量的大小与类型无关,只要是指针变量,在相同的平台下,大小都是相同的。指针变量类型的意义指针变量的大小与类型无关,只要是指针变量,在同一个平台下,大小都是一样的,为什么还要有各种各样的指针类型呢?
实际上,指针类型是有特殊意义的,我们接下来继续学习。
3.1 指针的解引用
对比以下两段代码,主要在调试时观察内存的变化。
调试时我们可以看到,代码1会将n的4个字节全部改为0,但代码2只是将n的第一个字节改为0。
结论:指针的类型决定了,对指针解引用时有多大的权限(一次能操作几个字节)。
例如:char的指针解引用只能访问一个字节,而int的指针解引用可以访问四个字节。
3.2 指针+-整数
我们可以看出,char类型的指针变量+1跳过1个字节,而int类型的指针变量+1跳过了4个字节。这就是指针变量的类型差异带来的变化。指针+1实际上是跳过一个指针指向的元素。指针可以+1,也可以-1。
结论:指针的类型决定了指针向前或向后移动一步的距离。
3.3 void* 指针
在指针类型中,有一种特殊的类型是void类型,可以理解为无具体类型的指针(或者称为泛型指针),这种类型的指针可以用来接受任意类型的地址。但是也有局限性,void类型的指针不能直接进行指针的+-整数和解引用的运算。
在上面的代码中,将一个int类型的变量的地址赋值给一个char类型的指针变量。编译器给出了一个警告(如下图),因为类型不兼容。而使用void类型就不会有这样的问题。
使用void*类型的指针接收地址:
VS编译代码的结果:
这里我们可以看到,void*类型的指针可以接收不同类型的地址,但无法直接进行指针运算。
那么void*类型的指针有什么用呢?
一般void*类型的指针用于函数参数部分,用来接收不同类型数据的地址,这样的设计可以实现泛型编程的效果。使得一个函数可以处理多种类型的数据,在《深入理解指针(4)》中我们会详细讲解。
菜鸟下载发布此文仅为传递信息,不代表菜鸟下载认同其观点或证实其描述。
版权投诉请发邮件到 cn486com#outlook.com (把#改成@),我们会尽快处理
Copyright © 2019-2020 菜鸟下载(www.cn486.com).All Reserved | 备案号:湘ICP备2022003375号-1
本站资源均收集整理于互联网,其著作权归原作者所有,如有侵犯你的版权,请来信告知,我们将及时下架删除相应资源