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