1.野指针
各位读者们,你们想一想,野狗是不是居无定所,到处游走?从人的身边经过搞不好还会来上一口?野指针就好比是一条野狗,也是非常危险的。
野指针的概念:
指针指向的位置是不可知的,随机的,不确定的
野指针的成因
- 指针未初始化
- 指针越界访问
- 指针指向的空间被释放
指针未初始化:
#include<stdio.h>
int main()
{
int *p;
*p=20;
return 0;
}
此时,指针p未初始化,值是随机的。
指针的越绝访问:
#include<stdio.h>
int main()
{
int arr[5]={0};
int *p=arr;
int i=0;
for(i=0;i<=5;i++)
{
printf("%d ",*(p+i));
}
return 0;
}
数组下标是从0~4,但是指针越界访问到了下标为5的地方,这就形成了野指针。
指针指向的空间被释放:
#include<stdio.h>
int* test()
{
int n=100;
return &n;
}
int main()
{
int *p=test();
return 0;
}
n是创建在test函数下的局部变量,它的生命周期只存在于test函数的范围内,当test函数调用结束后,n的空间就会被释放。在vs下运行这段代码,可以正常运行,并输出n的值,但是会报warning
将这段代码放到sublime text下运行:
也会报warning,但是程序无法正常运行,不会输出n的值
指针指向一个被释放的空间,就好比“你某天在123宾馆开了间房,住了一晚,第二天就把房退了,但是你和你朋友说:我在123宾馆开了间房,你可以过来住。你朋友在你退完房后来到了这间房内”这放到现实生活中是不是就属于非法访问?是违法的。
既然野指针这么危险,那我们因该怎么规避呢?
2.如何规避野指针
野指针
是非常危险的,为了规避野指针
,我们需要注意以下几点:
- 指针初始化
- 避免野指针的越界访问
- 不使用指针时及时赋值NULL
- 避免返回局部变量的地址
当我们暂时使用不到某个指针时,要及时的赋值NULL:
#include<stdio.h>
int main()
{
int a[5]={0};
int *p=a;
for(int i=0;i<5;i++)
{
printf("%d ",*(p+i));
}
//此时p已经访问完数组a的空间,要及时置NULL
p=NULL;
//再使用p时,要重新指向一个地址
int arr[1]={1};
p=arr;
return 0;
}
3.assert断言
assert头文件定义了宏assert(),用于在运行时确保程序符合指定条件,如果不符合就报错,终止运行,这个宏常常被称为“断言”
#include<stdio.h>
#include<assert.h>
int main()
{
int* p = NULL;
assert(p);
return 0;
}
当p为真时,程序正常运行,当p为假时(NULL),报错,显示没有通过的表达式,以及包含这个表达式的文件名与行号
放到vs下运行试一试:
assert对程序员是非常友好的,使用assert有几个好处:
它不仅能⾃动标识⽂件和出问题的⾏号,还有⼀种⽆需更改代码就能开启或关闭assert()
的机制。果已经确认程序没有问题,不需要再做断⾔,就在#include <assert.h> 语句的前⾯,定义⼀个宏“NDEBUG”
假设,我们就是需要p为NULL,此时我们不需要再assert(),就定义一个宏"NDEBUG",运行和后发现assert()不再起效果了。
assert()也是优缺点的:
引入了额外的检查,增加了程序的运行时间
一般我们是在debug版本下使用,在release版本下assert会被直接优化掉。
34指针的使用和传址调用
strlen的模拟实现
库函数strlen的功能是求字符串长度,计算在’\0’前的字符个数
函数原型: size_t strlen ( const char * str );
我们用自己的代码模拟实现:
#include<stdio.h>
size_t my_strlen(const char*s1)
{
char*start=s1;//用start记录s1的首元素地址
while(*s1++)
;
return s1-start-1;//两个指针相减,求指针之间的元素个数
//因为在while循环的条件判断下,当*s1为'\0'时不会进入循环,但是有++的存在,s1还是会向后移动一位,所以在返回时需要多减一个1
}
int main()
{
char s1[]={"abcdef"};
size_t ret=my_strlen(s1);//将字符数组的首元素地址传入
printf("%zd",ret);
return 0;
}
传值调用和传址调用
有的读者想问”为什么要学习指针?是有什么问题非指针不可的吗?“
不急,我们接下去看
我们定义两个变量x与y,通过调用函数swap实现两个值的交换。
#include<stdio.h>
void swap(int x,int y)
{
int tmp=x;
x=y;
y=tmp;
}
int main()
{
int x=10;
int y=20;
swap(x,y);
printf("x=%d y=%d",x,y);
return 0;
}
当我们放到ide下运行时,会发现值根本没有交换:
其中的原因就在于你向函数传参时,传的只是一个值,在函数内部,由形参接收这个值,但是形参是另外开辟一个空间用于存放传入的值的
我们通过调试,来观察他们的内存地址,我们便于观察,我们把形参改成x1与y1
虽然实参x、y的值与形参x1、y1的值是相等的,但是他们的地址是不一样的,或者说,我们可以把形参理解为:
形参是实参的一份临时拷贝
因为形参是创建在函数swap内部的,所以它们的生命周期只存在于swap函数内部,值的交换也只对形参有用,当函数调用结束后,形参就会销毁,实参x、y的值不受到影响。
这便是传值调用
那我们有没有什么办法,可以通过swap函数交换实参的值?有,用指针。
我们将代码修改一下,将形参改为int*类型的指针,传入的参数改为传入实参的地址。我们运行后便会发现,两个实参的值被成功交换了。
#include<stdio.h>
void swap(int *x,int *y)
{
int tmp=*x;
*x=*y;
*y=tmp;
}
int main()
{
int x=10;
int y=20;
swap(&x,&y);
printf("x=%d y=%d",x,y);
return 0;
}
这便是传址调用
所以,想要让函数与主调函数建立真正的联系,就要用传址调用;如果函数只是需要主调函数的值来实现计算,那么就用传值调用。