目录
1. 野指针的概念
在C语言中野指针是指那些指向的内存位置是不确定的、随机的、或者已经被释放的指针。这些指针可能指向了任意的内存地址,因此使用它们来访问或修改数据是极其危险的。
2. 野指针的产生原因
2.1 指针变量未初始化
任何指针变量在刚被创建时不进行初始化,其值是随机的,如果在使用前未对指针进行初始化,那么它可能会指向一个未知的内存地址。
2.2 指针越界访问
当我们使用指针访问数组中的元素时,超出数组范围,指针指向的就是一个未知的地址,此时的指针就是野指针。
2.3 指针指向的空间释放
根据下图:我们调用test函数,返回n的地址,但是函数调用结束后,函数内的n变量的内存空间被释放,所以此时主函数中p接收到的地址是一个未知的地址,即p此时就是一个野指针。
3. 如何规避野指针
我们已经知道野指针的危害和野指针是如何形成的,那么该如何规避野指针的产生呢?
3.1 指针初始化
当我们创建指针变量的时候,如果明确该指针指向的哪里就进行赋值,如果不知道指针应该指向哪⾥,可以给指针赋值NULL(空指针)在C++中Null表示0的意思,在C语言中Null会将0强转成((void *)0)。
例如:
3.2 ⼩⼼指针越界
⼀个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是越界访问。
例如在数组创建的空间范围内访问数组元素:
int main()
{
int arr[10] = { 0 };
int* p = &arr[0];
int sz = sizeof(arr) / sizeof(arr[0]);
for (int i = 0; i<sz ; i++)
{
*(p++) = i;
}
return 0;
}
3.3 指针变量不再使⽤时,及时置NULL
当指针变量指向⼀块区域的时候,我们可以通过指针访问该区域,后期不再使⽤这个指针访问空间的 时候,我们可以把该指针置为NULL。当重新使用指针时,可以判断一下是否是空指针
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = &arr[0];
int i = 0;
for (i = 0; i < 10; i++)
{
*(p++) = i;
}
//此时p已经越界了,可以把p置为NULL
p = NULL;
//下次使⽤的时候,判断p不为NULL的时候再使⽤
p = &arr[0];//重新让p获得地址
if (p != NULL) //判断
{
printf("重新使用指针");
}
else
{
printf("空指针不能使用");
}
return 0;
}
3.4 避免返回局部变量的地址
如之前调用test函数的例子,不要返回局部变量的地址,当函数调用结束后,局部变量的内存空间被释放,此时原局部变量的地址就会变成一个未知的地址,该地址指向的内容是未知的。
总结:野指针是编程中一个需要特别注意的问题。我们要养成良好的编程习惯,有效地减少野指针的产生,提高程序的稳定性和安全性。
4. assert 断⾔
assert 断⾔可以帮助我们检查程序运行中的一些错误,assert 断⾔的基本格式:assert (表达式),如果表达式为真,那么就不会发生报错,如果表达式为假,程序运行时就会报错停止运行。在使用assert 断⾔前,需要包含一个头文件:#include<assert.h>
例如我们要判断一个指针是否为空指针:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include<assert.h>
int main()
{
int* pa = NULL;
assert(pa != NULL);
return 0;
}
运行结果:
圈起来的报错信息的意思是:assert断言失败,错误位置在xx路径下的xx文件中的第x行。
当我们排查完错误后,不需要进行断言时,在#include<assert.h>上面加上#define NDEBUG即可 :
这样assert断言就不会起作用。
5. 传值调⽤和传址调⽤
之前在讲解函数的时候就提到过传值调用和传址调用,现在具体的讲一讲这2者的区别。
5.1 传值调用
传值调用顾名思义传的是数值,假设我们要写一个函数用来交换2个变量的值,传值调用是否能够完成呢?
我们发现交换前和交换后的数据,并没有发生改变这是为什么呢?
通过调试我们发现,在main函数内部,创建了a和b,a的地址是0x010ffbec,b的地址是0x010ffbe0,在调⽤change函数时,将a和b传递给了change函数,在change函数内部创建了形参x和y接收a和b的值,但是x的地址是0x010ffb08,y的地址是0x010ffb0c,x和y确实接收到了a和b的值,不过x的地址和a的地址不 ⼀样,y的地址和b的地址不⼀样,所以说x和y拥有自己独⽴的空间,那么在change函数内部交换x和y的值, 就不会影响a和b,当change函数调⽤结束后回到main函数,a和b的没法交换。所以交换前与交换后的数据是没有变化的。
传值调用的结论:实参传递给形参的时候,形参会单独创建⼀份临时空间来接收实参,对形参的修改不影响实参。
既然传值调用不能完成2个变量的值的交换,那我们来看看传址调用。
5.2 传址调用
传址调用传的是地址
我们发现通过传址调用可以实现2个变量的值的交换,这又是怎么回事呢?
通过调试,我们可以发现在调用change函数的时候,将a的地址传给了x,将b的地址传给了y。x和y通过地址找到了a的内存空间和b的内存空间,所以此时的 *x其实就是a,*y其实就是b,所以我们是通过指针(地址)间接访问a和b,对它们的值进行交换,这就是传址调用。
以上就是本章的全部内容,希望大家看完后能够有所收获,谢谢大家!!!