深入理解指针 (2)
一、如何规避野指针
1、指针的初始化
如果明确知道指针指向哪里,则直接赋值。如果不知道指针指向哪里,则可以给指针赋值NULL。
NULL:C语言中定义的一个标识符常量,值是0,0也是地址,此地址是无法使用的,读写该地址会报错。
int main()
{
int a = 10;
int* p = &a;//初始化
int* ptr = NULL;//空指针
*ptr = 100;//nullptr,报错
return 0;
}
注:初始化为NULL指针就不要去访问
2、小心指针越界
一个程序向内存申请空间,则通过指针只能访问所申请的空间,不能超出范围访问,超出则越界访问。(详细讲解在主页)
1、指针不再使用时,及时设置为NULL
用于指针使用前检查有效性。在使用前判断指针是否安全,可避免错误。
例
int main()
{
int a = 10;
int* p = &a;
int* ptr = NULL;
if (p != NULL)
{
//使用p
}
if (ptr != NULL)
{
*ptr = 100;
}
return 0;
}
例
变量未初始化时使用,会造成野指针
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
for (i = 0; i < sz; i++)
{
printf("%d ", *p);
p++;
}
//在这里继续使用p的话,p就是野指针了
p = NULL;
p = arr;
if (p != NULL)
{
//使用
}
return 0;
}
例
再例如 返回栈空间地址的问题,走出函数后,函数则被销毁,很容易造成野指针问题,可以使用以上方法规避
int* test()
{
//局部变量
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//...
return arr;
}
int main()
{
int* p = test();
//此时p就是野指针
return 0;
}
二、assert断言
assert.h头文件定义宏assert( ),用于在允许时确保程序符合指定条件,如果不符合,就报错种植运行。这个宏常常被称为:“断言”。
assert(p != NULl);
或
assert(p);
在代码运行到这一行语句,验证变量p是否等于NULL,若不等于NULL,程序继续运行,否则终止运行,并给出报错信息。
assert( ) 宏接受一个表达式作为参数。如果表达式为真(返回值非零),assert( )不会产生任何作用,程序正常运行。如果该表达式为假(返回值为零),**assert( )**就会报错。
#include <assert.h>
int main() {
int a = 10;
int* p = NULL;
assert(p != NULL);
return 0;
}
在屏幕上会出现一条错误信息,显示没有通过的表达式,以及包含整个表达式的文件名和行号
若不需要断言,可以在 #include <assert.h> 输入 #define NDEBUG
#define NDEBUG
#include <assert.h>
int main() {
int a = 10;
int* p = NULL;
assert(p != NULL);
return 0;
}
assert( ) 的优点是,保证了指针的有效性。
assert( ) 的缺点是,因为引入了额外的检查,会稍微增加程序的运行实践。
一般我们在debug下使用,在release版本下,例如VS这类开发环境,在release下,直接优化掉了。这样有利于程序员排查问题(debug),而不影响用户使用效率(release)
三、指针的使用和传址
1、传值调用
将变量传递给函数,这种函数调用方式叫做:传值调用。
int Max(int x, int y)
{
if (x > y)
return x;
else
return y;
}
int main()
{
int a = 100;
int b = 20;
//传值调用
int m = Max(a, b);
printf("%d\n", m);
return 0;
}
但是
一些传值调用需要分情况而定
例如,写一个函数,用于交换两个整形变量的值
void Swap(int x,int y)
{
int z= 0;//临时变量
z = x;
x = y;
y = z;
}
int main()
{
int a = 10;
int b = 20;
printf("交换前:a=%d b=%d\n", a, b);
Swap(a, b);
printf("交换后:a=%d b=%d\n", a, b);
return 0;
}
交换前:a=10 b=20
交换后:a=10 b=20
传值调用的时候,函数实参传递给形参后,
形参是实参的一份临时拷贝,
形参有自己独立的空间,
所以对形参的修改不能影响实参。
所以
在函数内部想要改变函数外部的变量,就需要传址调用。
2、传址调用
将变量的地址传递给函数,这种函数调用方式叫做:传址调用。
void Swap(int* px, int* py)
{
int z = 0;//临时变量
z = *px;//z = a
*px = *py;//a=b
*py = z;//b = a
}
int main()
{
int a = 10;
int b = 20;
printf("交换前:a=%d b=%d\n", a, b);
Swap(&a, &b);
printf("交换后:a=%d b=%d\n", a, b);
return 0;
}
交换前:a=10 b=20
交换后:a=20 b=10
————————
完
有问题请指出…