内存和指针
内存和指针
苏丙榅1. 内存
关于内存我们都耳熟能详,对于程序员而言,可以从两个维度去理解这个概念 — 物理存储器和存储地址空间
:
内存是计算机系统中用于存储数据和指令的地方。它是计算机的关键组件之一,用于临时存储和处理正在运行的程序所需的数据。
- 主板上装插的内存条
- 显示卡上的显示RAM芯片
- 各种适配卡上的RAM芯片和ROM芯片
计算机的内存通常被划分为不同的存储单元,每个存储单元都有一个唯一的地址,用于标识其在内存中的位置。每个存储单元都可以存储一定数量的数据。
编码
:对每个物理存储单元(一个字节)分配一个号码寻址
:可以根据分配的号码找到相应的存储单元,完成数据的读写
我们可以将内存抽象成一个很大的一维字符数组,编码就是对内存的每一个字节分配一个唯一的32位或64位的编号(与32位或者64位处理器相关),这个内存编号我们称之为内存地址,例如:一个内存地址可能是0x00007FF6D9A5C000。
内存中的每一个数据根据类型的不同,分配的内存大小也不尽相同:
char:占1个字节分配1个地址
int、float:占4个字节分配4个地址
double、long long:占8个字节分配8个地址
2. 指针
指针是计算机编程中一个重要的概念,它是一种特殊的数据类型,用于存储变量的内存地址。简单来说,指针指向了一个变量在计算机内存中的存储位置
。
每个变量在内存中都有一个地址,在编程中,通过定义指针变量,我们可以存储一个变量的地址,这就使得我们可以通过间接的方式操作和修改变量,而不需要访问原始的变量名。
2.1 指针变量的定义和使用
指针也是一种数据类型,指针变量也是一种变量,指针变量指向谁,就把谁的地址赋值给指针变量。
首先,要声明一个指针变量,需要使用星号"*"
来表示该变量是一个指针。例如,以下示例声明了一个指向整数的指针变量 ptr:
1 | // 关于 * 的位置没有要求,根据个人喜好书写即可 |
可以将指针初始化为某个特定变量的地址,也可以在后续的代码中将指针指向某个变量的地址。要将指针指向一个变量的内存地址,需要使用取地址符号"&"
,例如:
1 | int num = 10; |
要访问指针指向的变量的值,需要使用星号"*"
来解引用指针。解引用操作符告诉编译器去访问指针所指向的内存地址,并获取那个地址处存储的值
。例如:
1 | printf("%d", *ptr); // 输出指针所指向的变量的值 |
还可以通过指针来修改变量的值。通过解引用指针并使用赋值操作符,可以将新的值存储到指针指向的内存地址处。例如:
1 | *ptr = 20; // 将指针所指向的变量的值修改为20 |
下面是关于指针定义和使用的一段完整示例代码:
1 |
|
程序输出的结果如下:
1 | 0000008E46D9F914, 0000008E46D9F934 |
注意事项:&可以取得一个变量在内存中的地址。但是,不能取寄存器变量地址,因为寄存器变量不在内存里,而在CPU里面,所以是没有地址的。
2.2 指针大小
指针是一个变量,它存储了内存地址的值。在C语言中,使用运算符sizeof()可以计算指针的大小,指针的大小指的是指针变量指向的存储地址的大小
,得到的值为 4 或 8:
- 在32位平台,所有的指针(地址)都是32位(4字节)
- 在64位平台,所有的指针(地址)都是64位(8字节)
1 |
|
在32为平台测试的结果如下:
1 | sizeof(p1) = 4 |
在64为平台测试的结果如下:
1 | sizeof(p1) = 8 |
2.3 野指针和空指针
指针变量也是变量,是变量就可以任意赋值,不要越界即可(32位为4字节,64位为8字节),但是,任意数值赋值给指针变量没有意义,因为这样的指针就成了野指针
,此指针指向的区域是未知(操作系统不允许操作此指针指向的内存区域)。所以,野指针不会直接引发错误,操作野指针指向的内存区域才会出问题
。
1 |
|
在上面的代码中演示的野指针有两种存在形式:
第6行
定义了指针变量但是没有初始化,其指向一块随机的内存地址第8行和第10行
都是让指针指向了一块内存地址,这块地址属于哪个进程(程序)以及现在有没有被使用都是未知的
野指针和有效指针变量保存的都是数值,为了标志此指针变量没有指向任何变量(空闲可用),C语言中,可以把NULL赋值给此指针,这样就标志此指针为空指针,没有任何指针。
1 | int* ptr = NULL; |
NULL是一个值为0的宏常量:
1 |
2.4 万能指针 void*
void*
是一个特殊的指针类型,用来表示一个指向未知类型的指针。它可以存储任何类型的地址,但无法直接解引用或操作其指向的数据。
它可以用于在没有明确类型信息的情况下表示指针。例如,当你需要在函数中传递一个指针,但不确定指针所指向的数据类型时,可以使用 void*
作为参数类型。
使用 void*
类型时,需要注意的是,在使用 void* 指针进行操作之前,必须将其转换为适当的指针类型,以便进行正确的解引用和操作。这是因为 void* 指针在不确定指向的具体类型时无法进行类型推断。
1 |
|
总而言之,void*
是一种特殊的指针类型,可以用于表示指向未知类型的指针。它在某些情况下非常有用,但需要小心使用,以避免类型不匹配或错误的操作。
2.5 const 与指针
在定义指针的时候可以添加const
关键字, 根据const
关键字的位置可以用其修饰指针本身也可以用来修饰指针指向的值:
常量指针:const 关键字在 * 左边,常量指针的本质是指针,表示指针所指向的地址可变,但是地址中的数据不能被修改。例如:
1
2const int* ptr; // ptr 是一个指向 int 类型常量的指针
int const *ptr; // 等价于 const int* ptr;在这个例子中,
ptr
是一个指向int
类型常量的指针。这意味着不能通过ptr
修改它所指向的整数值,但该指针指向的地址是可以改变的
。指针常量: const 关键字在 * 右边,表示指针指向的地址不能被修改,但是地址中的值可以被修改。例如:
1
2
3int value = 10;
// ptr 是一个常量指针,指向 int 类型的变量 value, 不能被重新赋值
int* const ptr = &value;在这个例子中,
ptr
是一个指针常量,指向了变量value
。这意味着不能通过ptr
修改指针的值,即不能将ptr
指向其它地址,但可以通过*ptr
来修改所指向的变量的值。注意:指针常量在定义时要赋初值。
下面有几句口诀,方便大家记忆常量指针和指针常量:
const (*号)左边放,我是指针变量指向常量 - 常量指针
const (*号)右边放,我是指针常量指向变量 - 指针常量
const (*号)两边放,我是指针常量指向常量 - 常量指针常量
指针变量能改指向,指针常量不能转向,要是全部变成常量,锁死了,我不能转向,你也甭想变样!
1 |
|
const
修饰的指针变量可以帮助确保数据的不可变性和程序的安全性,特别是在函数参数传递、返回值和常量数据的处理方面有广泛的应用。在使用 const
修饰的指针时,需要注意遵守 const
修饰符的规则,以便正确使用和理解指针的行为。