目录
前言
(一)内存与地址
从实际生活出发
地址
内存
内存与地址关系密切
(二)指针变量
指针变量与取地址操作符
指针变量与解引用操作符
指针的大小
指针的运算
指针 +- 整数
指针-指针
指针的关系运算
指针的类型的意义
void* 指针
const修饰指针
野指针
野指针成因
如何规避野指针
C语言是一种直接操作内存的编程语言,我们可以直接访问和操作计算机内存中的地址空间。
而C语言中存在的指针类型,指针指向的就是内存中的地址。我们可以通过指针来访问和修改内存中存储的数据。
因此,深入理解指针,并且理解内存,对于编写高质量的程序和调试程序故障都非常有帮助。
在理解指针之前,我们先引入内存与地址的概念:
从实际生活中的问题出发——假如你要去上课,但是学校里教学楼那么多,如果想要上课,就得挨个教室去找,这样当你找到上课的教室,估计也下课了。
在实际生活中,我们是如何解决找东西效率低这一问题呢?
——我们可以将东西排好序,提前熟悉它们的位置,甚至编好号。
我们将每栋教学楼的每一个房间一一编号,这样,你要去上课,只要有了楼号和房间号,就可以快速找到房间,找到教室。
于是,C语言借鉴生活中的实例,从而有了地址的概念。
我们知道计算上CPU(中央处理器)在处理数据的时候,需要的数据是在内存中读取的,处理后的数 据也会放回内存中。
我们买电脑的时候,电脑上内存是8GB/16GB/32GB等,那这些内存空间如何高效的管理呢?
其实也是把内存划分为⼀个个的内存单元,每个内存单元的⼤⼩取1个字节。
一个字节有多大呢?
常见内存单位
1byte(字节) = 8bit
1KB = 1024byte
1MB = 1024KB
1GB = 1024MB
1TB = 1024GB
1PB = 1024TB
(一个内存单元的大小取为1字节是比较合适的,如果取得过大,那么计算机的内存就会小很多,如果过大,那么内存无法被高效利用)
每个内存单元也都有⼀个编号(这个编号就相当于教学楼的门牌号),有了这个内存单元的编号,CPU就可以快速找到⼀个内存空间。
生活中我们把门牌号也叫地址,在计算机中我们 把内存单元的编号也称为地址。
C语⾔中给地址起了新的名字————指针
所以我们可以理解为:
内存单元的编号 == 地址 == 指针
我们对内存和地址有了初步的了解,我们再回到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.
于是,解引用操作符的作用就是通过地址找到对应的变量。
我们知道,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;
}
如图,演示:
注意指针变量的大小和类型是无关的,只要指针类型的变量,在相同的平台下,大小都是相同的。
既然指针的大小与类型无关,那么指针类型存在的意义是什么?
在讨论这个问题之前,我们先引入指针的运算
指针的基本运算有三种,分别是:
• 指针+- 整数
• 指针-指针
• 指针的关系运算
#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
1.数组在内存中是连续存放的,只要知道第⼀个元素的地址,顺藤摸瓜就能找到后面的所有元素。
2.指针加减整数,表示跳过当前指针类型的元素的个数。
//指针-指针
#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.指针-指针 表示两个指针变量之间的元素的个数
//指针的关系运算
#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
1.指针的关系的比较,实际上就是地址大小的比较。
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* 类型的指针不能直接进行指针的+-整数和解引用的运算。
见详解
const修饰指针https://mp.csdn.net/mp_blog/creation/editor/134341320
概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
1. 指针未初始化
2. 指针越界访问
3. 指针指向的空间释放
3的实例
#include
int* test() { int n = 100; return &n; } int main() { int*p = test(); printf("%d\n", *p); return 0; }
1.指针初始
2.小心指针越界
3.指针变量不再使用时,及时置NULL,指针使用之前检查有效性
4.避免返回局部变量的地址
5.assert断言
完~
未经作者同意禁止转载