目录
前言
(一)内存与地址
从实际生活出发
地址
内存
内存与地址关系密切
(二)指针变量
(1)操作符
指针变量与取地址操作符
指针变量与解引用操作符
(2)指针的大小
(3)指针的运算
i,指针 +- 整数
ii,指针-指针
iii, 指针的关系运算
(4)指针的类型的意义
void* 指针
(5)const修饰指针
(三)野指针
(1)野指针成因
(2) 如何规避野指针
前言
C语言是一种直接操作内存的编程语言,我们可以直接访问和操作计算机内存中的地址空间。
而C语言中存在的指针类型,指针指向的就是内存中的地址。我们可以通过指针来访问和修改内存中存储的数据。
因此,深入理解指针,并且理解内存,对于编写高质量的程序和调试程序故障都非常有帮助。
在理解指针之前,我们先引入内存与地址的概念:
(一)内存与地址
从实际生活出发
地址
从实际生活中的问题出发——假如你要去上课,但是学校里教学楼那么多,如果想要上课,就得挨个教室去找,这样当你找到上课的教室,估计也下课了。
在实际生活中,我们是如何解决找东西效率低这一问题呢?
——我们可以将东西排好序,提前熟悉它们的位置,甚至编好号。
我们将每栋教学楼的每一个房间一一编号,这样,你要去上课,只要有了楼号和房间号,就可以快速找到房间,找到教室。
于是,C语言借鉴生活中的实例,从而有了地址的概念。
内存
我们知道计算上CPU(中央处理器)在处理数据的时候,需要的数据是在内存中读取的,处理后的数 据也会放回内存中。
我们买电脑的时候,电脑上内存是8GB/16GB/32GB等,那这些内存空间如何高效的管理呢?
其实也是把内存划分为⼀个个的内存单元,每个内存单元的⼤⼩取1个字节。
一个字节有多大呢?
常见内存单位
1byte(字节) = 8bit
1KB = 1024byte
1MB = 1024KB
1GB = 1024MB
1TB = 1024GB
1PB = 1024TB
(一个内存单元的大小取为1字节是比较合适的,如果取得过大,那么计算机的内存就会小很多,如果过大,那么内存无法被高效利用)
内存与地址关系密切
每个内存单元也都有⼀个编号(这个编号就相当于教学楼的门牌号),有了这个内存单元的编号,CPU就可以快速找到⼀个内存空间。
生活中我们把门牌号也叫地址,在计算机中我们 把内存单元的编号也称为地址。
C语⾔中给地址起了新的名字————指针
所以我们可以理解为:
内存单元的编号 == 地址 == 指针
(二)指针变量
(1)操作符
指针变量与取地址操作符
我们对内存和地址有了初步的了解,我们再回到C语言,在C语言中创建变量其实就是向内存申请空间:
#include
int main()
{
int a = 10;
return 0;
}
⽐如,上述的代码就是创建了整型变量a,内存中申请4个字节,用于存放整数10,其中每个字节都有地址,上图中4个字节的地址分别是:
0x007DF784 0x007DF785 0x007DF786 0x007DF787
上图中,出现了一个陌生的操作符—— &(取地址操作符)
作用:取出变量的地址
比如,我们可以将a的地址放在pa中,并用%p 打印出来:
但是,我们发现打印出的只有一个地址, 并且与第一次看到的a的地址不同!
原因在于:
1.每一次运行代码,编译器都会重新给变量分配内存空间,这也就解释了为什么第二次打印的地址与第一次不同。
2.同时虽然整型变量占用4个字节,我们只要知道了第⼀个字节地址,顺藤摸瓜访问到4个字节的数据也是可行的。
在上图中,又出现了一个陌生的操作符
指针变量与解引用操作符
我们通过取地址操作符(&)拿到的地址是⼀个数值,如:0x006FFD70,这个数值有时候也是需要 存储起来,⽅便后期再使用的,那我们把这样的地址值存放在哪里呢?
答案是:指针变量中。
我们可以通过创建一个指针变量,来存储变量的地址:
#include
int main()
{
int a = 100;
int* pa = &a;
*pa = 0;
}
*pa 的意思就是通过pa中存放的地址,找到指向的空间, *pa其实就是a.
于是,解引用操作符的作用就是通过地址找到对应的变量。
(2)指针的大小
我们知道,32位机器假设有32根地址总线,每根地址线出来的电信号转换成数字信号后 是1或者0,那我们把32根地址线产⽣的2进制序列当做⼀个地址,那么⼀个地址就是32个bit位,需要4 个字节才能存储。
如果是32位机器,那么指针的大小是4个字节。
如果是64位机器,指针的大小是8个字节。
int main()
{
printf("%zd\n", sizeof(char *));
printf("%zd\n", sizeof(short *));
printf("%zd\n", sizeof(int *));
printf("%zd\n", sizeof(double *));
return 0;
}
如图,演示:
注意指针变量的大小和类型是无关的,只要指针类型的变量,在相同的平台下,大小都是相同的。
既然指针的大小与类型无关,那么指针类型存在的意义是什么?
在讨论这个问题之前,我们先引入指针的运算的概念
(3)指针的运算
指针的基本运算有三种,分别是:
• 指针+- 整数
• 指针-指针
• 指针的关系运算
i,指针 +- 整数
#include
//指针+- 整数
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = &arr[0];
int i = 0;
int sz = sizeof(arr)/sizeof(arr[0]);
for(i=0; i { printf("%d ", *(p+i));//p+i 这⾥就是指针+整数 } return 0; } 1.数组在内存中是连续存放的,只要知道第⼀个元素的地址,顺藤摸瓜就能找到后面的所有元素。 2.指针加减整数,表示跳过当前指针类型的元素的个数。 ii,指针-指针 //指针-指针 #include int my_strlen(char *s) { char *p = s; while(*p != '\0' ) p++; return p-s; } int main() { printf("%d\n", my_strlen("abc")); return 0; } 1.图中实现的是strlen函数的模拟实现 2.指针-指针 表示两个指针变量之间的元素的个数 iii, 指针的关系运算 //指针的关系运算 #include int main() { int arr[10] = {1,2,3,4,5,6,7,8,9,10}; int *p = &arr[0]; int i = 0; int sz = sizeof(arr)/sizeof(arr[0]); while(p { printf("%d ", *p); p++; } return 0; } 1.指针的关系的比较,实际上就是地址大小的比较。 (4)指针的类型的意义 1.指针的类型决定了,对指针解引用的时候有多大的权限(⼀次能操作几个字节)。 //代码2 #include int main() { int n = 0x11223344; char* pc = (char*)&n; *pc = 0; return 0; 对于n的地址: *pc = 0;之前 之后: 2.指针的类型决定了指针向前或者向后一步有多大(距离)。 #include int main() { int n = 10; char *pc = (char*)&n; int *pi = &n; printf("%p\n", &n); printf("%p\n", pc); printf("%p\n", pc+1); printf("%p\n", pi); printf("%p\n", pi+1); return 0; } 我们可以看出, char* 类型的指针变量+1跳过1个字节, int* 类型的指针变量+1跳过了4个字节。 这就是指针变量的类型差异带来的变化。 void* 指针 在指针类型中有⼀种特殊的类型是 void* 类型的,可以理解为无具体类型的指针(或者叫泛型指 针),这种类型的指针可以用来接受任意类型地址。但是也有局限性, void* 类型的指针不能直接进行指针的+-整数和解引用的运算。 ``更具体来说,什么样的指针类型就决定了什么样的对数据的运算方法。`` 即使是一个float*类型的指针,赋值给void*指针,强转为int*或者double*再解引用仍然可以使用,但是数值的内容是未定义的! 示例: int a = 10; float b = 1; void* any = &a; cout< any = &b; cout< *(float*)any = 1.2; cout<<*(float*)any< //什么样的指针类型,决定了什么样的对数据的运算方法 cout<<*(int*)any< cout<<*(double*)any< 结果: (补充时间:2025年3月25日) (5)const修饰指针 见详解 const修饰指针https://mp.csdn.net/mp_blog/creation/editor/134341320 (三)野指针 概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的) (1)野指针成因 1. 指针未初始化 2. 指针越界访问 3. 指针指向的空间释放 ( 3的实例 ) #include int* test() { int n = 100; return &n; } int main() { int*p = test(); printf("%d\n", *p); return 0; } (2) 如何规避野指针 1.指针初始化为NULL; 2.指针进行数组运算时小心越界; 3.指针变量不再使用时,及时置NULL,指针使用之前检查有效性; 4.避免返回局部变量的地址; 5.assert断言; 完~ 未经作者同意禁止转载