首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【面试高频】C语言指针十大易错点详解:野指针、越界访问、空指针陷阱全解析

【面试高频】C语言指针十大易错点详解:野指针、越界访问、空指针陷阱全解析

作者头像
码途随笔
发布2026-01-12 20:02:39
发布2026-01-12 20:02:39
2070
举报

一、内存

1.1 内存的管理

计算机的CPU读取数据时是从内存中读取,处理完数据,也是放到内存中

内存分为一个个小的内存单元,一个内存单元的大小取一个字节,类比学校的宿舍,给房间编号,给每个内存单元编上号,CPU才能快速的找到一个内存空间

1.2 数据传输

CPU是如何拿数据又把数据拿回来的?

计算机中的编址是通过硬件设计来完成的,不需要把地址给存起来 地址信息被下达给内存,在内存上,就可以找到该地址对应的数据,将数据在通过数据总线传入CPU内寄存器。

控制总线传输的是做什么操作(’是‘读还是’写‘),地址总线,把地址传过去,找到是读哪里的数据或写到哪里的数据(找到地方),数据通过数据总线传入CPU寄存器中

二、指针变量

2.1 取地址操作符

创建变量就是申请空间,每一个字节都有自己的空间

怎么访问变量a的地址

&a 虽然有四个地址,但是&a取出的是a所占4个字节中地址较小的字节的地址。 我们知道了第一个地址,顺藤摸瓜可以访问其余的地址。

&a也是值,该用什么存储

因为编号== 地址 ==指针,p=&a中的p就叫指针变量,它的类型int * 怎么理解指针变量

只要存放到指针变量里面去,都会当成地址处理

2.2 解引用操作符

为了有朝一日能再次访问指针变量操作符里面的地址,*p可以找到地址指向的变量*p==a

2.3 指针的意义

提供了更多的手段来改变变量a的值

  • 直接改变a
  • 通过指针——*p

2.4 指针变量的大小

指针变量是用来存放地址的,其大小跟地址有关系

传地址是由地址线传过去的,32根地址总线产生的是32个0/1,32个0/1当成内存单元编号了 32位机器上,有32个地址线,需要32个比特位,4个字节 同理,64位机器上,有64个地址线,需要64个比特位,8个字节 无论是那种类型,地址都是32位的,长度不变

地址和里面的数据 打印为了方便,不打印2进制(太长了),打印16进制

三、指针变量类型的意义

既然指针变量的大小一样,为啥要有这么多指针类型(char、int、float等)

3.1 解引用操作符的权限

指针变量类型让他觉得自己是个指向什么类型的指针,只访问该类型大小的字节 对比图:

指针类型决定了指针进行解引用操作符的时候访问几个字节,也就是决定指针的权限! 选择适当的指针来访问不同的字节长度,如果想访问1个字节,就用char类型的指针,想访问2个字节可以用short类型,想访问4个字节的整形可以用int类型,想访问4个字节的浮点型的可以用float类型

3.2 指针加减

指针类型决定了指针加1或减1时,能走多远的距离

3.3 void*

void*是无类型指针,不能解引用操作和指针加减

应用场景:

  • 可以把void*当作一个垃圾桶,什么类型的地址都可以传(不同类型的地址相互传给会报错),当你在这个地方传地址不确定传什么类型的地址就可以用void
  • 一般用于函数参数部分,用来接收不同类型的地址,可以实现泛型编程的效果

四、const修饰

4.1 const修饰变量

但是在C++中编译没有报错,因为在C++中const修饰的变量就是常量 应用场景:有一个变量不想让别人直接去修改就可以用,但是这种情况可以通过指针来修改

有没有方法限制指针无法修改呢?

4.1 const修饰指针变量

补充知识:

  • 放在*的右边 限制的是p变量本身,变成常变量了

const限制的是指针变量本身,指针变量不能再指向其他变量了,但是可以通过指针变量,修改指针变量指向的内容

  • 放在*的左边 限制的是指针指向的内容不能通过指针来修改,但是可以修改指针变量本身的值
  • 两个都加 都被限制
  • 结论

五、指针运算

5.1 指针±整数

指针的作用就是访问内存,如何访问内存访问的更好?

同理,减号就是向回跳

作用:访问数组 (前提条件:数组在内存中连续存放的)

  • p在移动
代码语言:javascript
复制
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int* p = &arr[0];
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", *p);
		p++;
	}
	return 0;
}

只要理解了指针,就有多种写法

  • i在移动
代码语言:javascript
复制
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int* p = &arr[0];
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));
	}
	return 0;
}

5.2 指针减指针

类比日期

并且,指针加指针没有任何的意义 注意:指针减指针的前提条件是两个指针指向同一块空间 否则:(如图所示)

应用:模拟strlen()函数的实现 方法一(传统方法):

代码语言:javascript
复制
int my_strlen(char* str)
{
	int count = 0;
	while (*str != '\0')
	{
		str++;
		count++;
	}
	return count;
}
int main()
{
	char arr[] = "abcdef";
	int len = my_strlen(arr);
	printf("%d", len);
}

方法二:指针减指针

代码语言:javascript
复制
int my_strlen(char* str)
{
	char* start = str;
	while (*str != '\0')
	{
		str++;
	}
	return str - start;
}
int main()
{
	char arr[] = "abcdef";
	int len = my_strlen(arr);
	printf("%d", len);
}

5.3 指针与指针关系运算

也就是地址与地址比较大小

代码语言:javascript
复制
int main()
{
	int arr[] = { 1,2,3,4,5 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int* p = arr;
	while (p < arr + sz)
	{
		printf("%d ", *p);
		p++;
	}
	return 0;
}

六、野指针

野指针是什么? 野指针是指针指向的位置不可知的(随机、不正确的、没有明确限制的)

那些是野指针?

6.1 野指针的成因

  • 指针未初始化
  • 指针越界访问
  • 指针指向内存空间释放

这么多的野指针,该这么规避它们?

6.2 野指针的规避

  • 指针初始化 NULL是标识符常量,值是0,0也是地址,但是该地址无法使用,读写该内存会报错
  • 小心指针越界访问 一个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是越界访问。
  • 指针变量不再使用时,应先把它及时置为空指针 ,使用之前检查有效性
代码语言:javascript
复制
if (p != NULL)
{
	……
}
  • 避免返回局部变量的地址

七、assert断言

assert.h头文件定义了宏assert,用于在程序运行时符合特定的条件,否则会报错终止运行,这个宏成为”断言“,为了避免野指针,可以用assert(p!=NULL),如果括号为假,会报错

注意:在Release版本中assert自动优化掉了(禁用),样在debug版本写有利于程序员排查问题,在Release版本不影响用户 使用时程序的效率。

八、指针的使用和传址调用

8.1 strlen模拟实现

更标准:

代码语言:javascript
复制
size_t my_strlen(const char * p)//防止函数内通过p来修改字符
{
	size_t count = 0;
	assert(p != NULL);//更安全
	while (*p != '\0')
	{
		count++;
		p++;
	}
	return count;
}
int main()
{
	char arr[] = "abcdef";
	size_t len = my_strlen(arr);
	printf("%zd", len);
	return 0;
}

8.2 传值调用和传址调用

有什么问题非指针不可

写一个函数交换a和b的值

  • 用常规传值调用不行 当实参传递给形参的时候,形参是实参一份临时拷贝,对形参的修改不会影响实参的
  • 传址调用(主调函数和被调函数可以建立一定的联系),可以解决问题
代码语言:javascript
复制
void Fuc(int *p1,int *p2)
{
	int tmp = 0;
	tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
int main()
{
	int a = 10;
	int b = 20;
	printf("a=%d,b=%d\n", a, b);
	Fuc(&a, &b);
	printf("a=%d,b=%d\n", a, b);
	return 0;
}
  • 总结
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-10-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、内存
    • 1.1 内存的管理
    • 1.2 数据传输
  • 二、指针变量
    • 2.1 取地址操作符
    • 2.2 解引用操作符
    • 2.3 指针的意义
    • 2.4 指针变量的大小
  • 三、指针变量类型的意义
    • 3.1 解引用操作符的权限
    • 3.2 指针加减
    • 3.3 void*
  • 四、const修饰
    • 4.1 const修饰变量
    • 4.1 const修饰指针变量
  • 五、指针运算
    • 5.1 指针±整数
    • 5.2 指针减指针
    • 5.3 指针与指针关系运算
  • 六、野指针
    • 6.1 野指针的成因
    • 6.2 野指针的规避
  • 七、assert断言
  • 八、指针的使用和传址调用
    • 8.1 strlen模拟实现
    • 8.2 传值调用和传址调用
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档