上篇博客,我们介绍了指针是什么还有分解的指针变量,这篇博客,我们将介绍指针的运算和野指针。
指针的运算类型
指针也是可以进行运算的,有以下三种运算类型:
·指针+/-整数
·指针-指针
·指针的关系运算
指针+/-整数
我们可以先通过一个代码来观察指针+/-整数的变化
int main()
{
int n = 10;
char* pc = (char*)&n;
int* pi = &n;
printf("&n = %p\n",&n );
printf("pc = %p\n",pc );
printf("pc+1 = %p\n",pc+1 );
printf("pi = %p\n",pi );
printf("pi+1 = %p\n",pi+1 );
return 0;
}
在这串代码中,我们定义了两种类型的指针变量,在指针解引用的过程中,指针类型决定了解引用时一次能操作多少字节,因此,当int*类型的指针变量加一时,应跳过四个字节,同理,char*类型加一时跳过一个字节,那么,程序运行的结果应该是这样的
我们可以得出结论,指针的类型决定了指针向前或者向后走一步的距离有多大
指针-指针
指针-指针的类型,我们可以通过模拟一个库函数来认识。
strlen函数,这个函数是用来计算字符串长度的,使用方法就像下面这串代码
int main()
{
char str[] = "abcdef";
int count = strlen(str);
printf("%d", count);
return 0;
}
字符串的长度是6,那么程序打印的结果也应该是6,我们来看看运行的结果
通过strlen函数我们很容易就能得到字符串长度(在使用strlen函数前得包含头文件string.h),现在我们就需要模拟strlen函数
int my_strlen(char* s)
{
char *p = s;
while(*p != '\0')
p++;
return p - s;
}
int main()
{
printf("%d", my_strlen("abc"));
return 0;
}
这串代码中,函数实参传过去的是字符串abc的起始地址,那么形参就用了char*类型来定义s,后又定义了一个char*类型的指针变量p,是为了通过p来改变指针,直到遇到\0停止,这样s保存的地址没有变化,而p的地址已经是字符串\0之前所处的地址了,因此,这时候用指针变量p减去指针变量s,就能得到两个指针之间的距离,以此来计算出字符串的长度。
指针的关系运算
先看一串代码
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 < arr + sz)//指针大小的比较
{
printf("%d ", *p);
p++;
}
return 0;
}
这段代码里,我们进行了一段指针大小的比较,while循环中,p<arr+sz,这是两个指针的比较,(注意:arr在这里指向的是数组第一项的地址,是一个指针),这样,我们就能打印出数组的所有项了。
野指针的成因与规避方法
在程序中,有很多情况会出现野指针,野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
想要规避野指针,我们就得先认识有可能生成野指针的情况
野指针的成因
指针未初始化
如:
int main()
{
int* p;
*p = 20;
return 0;
}
从中可以看出,指针变量p未进行初始化,系统就会默认给个随机值,形成野指针。
指针越界访问
int main()
{
int arr[10] = { 0 };
int* p = &arr[0];
int i = 0;
for (i = 0; i <= 11; i++)
{
*(p++) = i;
}
return 0;
}
当指针指向的范围超出arr的范围时,就会形成野指针。
指针指向的空间释放
int* test()
{
int n = 100;
return &n;
}
int main()
{
int* p = test();
printf("%d\n", *p);
return 0;
}
如何规避野指针
指针初始化
当我们已经明确指针指向哪里时,就需要直接赋地址,否则会出现野指针的情况,若不明确目标,则可以赋值空指针,即NULL。无论如何,一定要进行初始化,情况如下
int main()
{
int n = 10;
int *p1 = &n;
int* p2 = NULL;
return 0;
}
其中,p1指针目标是存放n的地址,因此初始化取地址n,p2没有目标地址,则初始化空指针
小心指针越界
注意指针不能超出访问范围,超出了就是越界访问,容易出现野指针
及时置NULL
当我们不需要再用到一个指针时,及时置于空指针,以此来防止野指针的出现。
还需注意的是,每次使用指针之前,我们需要检查的有效性。
这时候,就可以使用到C语言中的一个关键字:assert
assert断言
assert.h头文件定义了宏assert(),用于在运行时确保程序符合指定条件,如果不符合,就报错终止运行,常常被称为“断言”。
如:
assert(p != NULL);
assert()宏接受一个表达式作为参数。如果该表达式为真(返回值非零),assert()不会产生任何坐拥,程序继续运行。如果该表达式为假(返回值为零),assert()就会报错。在标准错误流stderr中写入一条错误信息,显示没有通过的表达式,以及包含这个表达式的文件名和行号。
int main()
{
int n = 20;
int* p = &n;
p = NULL;
assert(p != NULL);
printf("%d", *p);
return 0;
}
运行一下上面这条代码,得到这样的结果
可以看到,程序返回了assert所在的行数和文件,很方便得反映出程序的问题所在。
The End