文章目录
- 野指针、空指针、空悬指针
- 野指针
- 空指针
- 空悬指针
- void与void*的区别
- 内存操作
- 常用内存操作函数
- 内存填充
- 内存拷贝
- 内存比较
- 内存查找
野指针、空指针、空悬指针
野指针
定义:指向一块未知区域(已经销毁或者访问受限的内存区域外的已存在或不存在的内存区域),被称作野指针。野指针是危险的
危害:
①引用野指针相当于访问了非法的内存,常常会导致段错误(segmentation fault),也有可能编译运行不报错
②引用野指针,可能会破坏系统的关键数据,导致系统崩溃等严重后果
野指针产生的场景:
1.变量未初始化,通过指针访问该变量
int a;
int *p = &a;//p是野指针
printf("%d\n",*p);//访问野指针,数据不安全
2.指针变量未初始化
int *p;//p是野指针
printf("%d\n",*p);
int a = get();
p = &a;
3.指针指向的内存空间被(free)回收了
int *p = malloc(4);
*p = 12;//此时的p不是野指针
free(p);
printf("%d\n",*p);
4.指针函数中直接返回了局部变量的地址
int* get_num()
{
int a = 15;
int *p = &a;//此时p对应的数据是一个局部作用域的数据
return p;
}
main()
{
int *p = get_num();//此时p是野指针
}
如何避免野指针
1.指针变量要及时初始化,如果暂时没有对应的值,建议赋初值NULL
2.数组操作(遍历和指针运算)时,注意数组的长度,避免越界
3.指针指向的内存空间被回收,建议给这个指针变量赋值为NULL
int *p = (int *)malloc(10);
free(p);
p = NULL;
4.指针变量使用之前要检查它的有效性(非空校验)
int* p = NULL;
//if(p == NULL)
if(!p)//!p等价于p == NULL
{
return -1;
}
空指针
很多情况下,我们不可避免的会遇到野指针,比如刚定义的指针无法立即为其分配一块恰当的内存,又或者指针指向的内存已经被释放了等等。一般的做法是将这些危险的野指针指向一块确定的内存,比如零地址内存(NULL)
定义:空指针即保存了零地址的指针(赋值为NULL的指针),也就是指向零地址的指针(NULL是空常量,它的值是0,这个NULL一般存放在内存0x0000 0000的位置,这个地址只能存NULL,不能被其他程序修改)
示例:
//1.刚定义的指针,让其指向零地址以确保安全
char *p1 = NULL;
int* p2 = NULL;
//2.被释放了内存的指针,让其指向零地址以确保安全
char *p3 = malloc(100);
free(p3);
p3 = NULL;
int sum = 0;
空悬指针
在C语言中,悬空指针指的是指向已删除(或释放)的内存地址位置的指针。如果一个指针指向的内存已经被释放,但指针本身并未重新指向其他有效的内存地址,那么这个指针就变成了悬空指针。悬空指针会引发不可预知的错误,并且如果一旦发生,就很难定位,因此在编程中尽量避免使用悬空指针
char *p3 = malloc(100);
free(p3);
printf("%p,%c\n",p3,*p3);
void与void*的区别
定义:
- void:是空类型,是数据类型的一种
- void* :是指针类型,是指针类型的一种,可以匹配任意类型的指针,类似于通配符,又被叫做万能指针
void:
- 说明:void作为返回值类型使用,表示没有返回值;作为形参,表示形参列表为空,在调用的时候不能给实参
- 举例:
//函数定义
void fun(void){..}//等效于 void fun(){..}
//函数调用
fun();
void*:
- 说明:
- void*是一个指针类型,但该指针的数据类型不明确,无法通过解引用获取内存中的数据,因为
void*
不知道访问几个内存单元 - void*是一种数据类型,可以作为函数
返回值类型
,也可以作为形参类型
- void* 类型的变量在使用之前必须强制类型转换,明确它能够访问几个字节的内存空间
- void*是一个指针类型,但该指针的数据类型不明确,无法通过解引用获取内存中的数据,因为
int *p = (int*)malloc(4);
- 说明:
- void*作为返回值类型,这个函数可以返回任意类型的指针
- void*作为形参类型,这个函数在调用时,可以给任意类型的指针
- 总结:
- void*类似于通配符,不能对
void*
类型的变量解引用(因为不明确数据类型,所以无法确定内存单元的大小) - void*在间接访问(解引用)前要强制类型转换,但不能太随意,否则存和取的数据类型不一致
- void*类似于通配符,不能对
内存操作
我们对于内存的操作需要依赖string库(对应的头文件string.h
)完成内存的操作
常用内存操作函数
内存填充
-
头文件:
#include <string.h>
-
函数原型:
void* memeset(void* s,int c,size_t n)
-
函数功能:填充s开始的堆内存空间前n个字节,使得每个字节值为c
-
函数参数:
- void* s:代操作内存首地址
- int c:填充的字节数据
- size_t n:填充的字节数
-
返回值:返回s
-
注意:c常常设置为0,用于动态内存初始化
内存拷贝
-
头文件:
#include <string.h>
-
函数原型:
void* memcpy(void* dest,const void* src,size_t n)
适合目标地址与源地址内存无重叠的情况void* memove(void* dest,const void* src,size_t n)
-
函数功能:拷贝src开始的堆内存空间前n个字节,到dest对应的内存中
-
函数参数:
- void* dest:目标内存首地址
- void* src:源内存首地址
- size_t n:拷贝的字节数
-
返回值:返回dest
-
注意:内存申请了几个内存空间,就访问几个内存空间,否则数据不安全
-
案例:
#include <stdio.h>
#include <string.h>
int main(int argc,char *argv[])
{
//创建源空间和目标空间
int src[4] = {11,22,33,44};
int dest[6] = {111,222,333,444,555,666};
//将src中的数据拷贝到dest
memmove(dest,src,2*sizeof(int));
printf("%d\n",dest[0]);
return 0;
}
内存比较
- 头文件:
#include <string.h>
- 函数原型:
int memcmp(void *dest,const *src,size_t n)
- 函数功能:比较src和dest所代表的内存前n个字节的数据
- 函数参数:
- void* dest:目标内存首地址
- const void* src:源内存首地址
- size_t n:比较的字节数
- 返回值:
0
:数据相同>0
:dest中的数据大于src<0
:dest中的数据小于src
- 注意:n建议和src,dest的总容量一致;如果不一致,内存比较的结果就不确定了
案例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc,char *argv[])
{
//申请内存
int* src = (int*)malloc(3*sizeof(int));
int* dest = (int*)calloc(4,sizeof(int));
if(!src || !dest)
{
perror("内存申请失败!");
return -1;
}
//对使用malloc申请的空间清零
bzero(src,3*sizeof(int));
*src = 65;
*(src+1) = 66;
*dest = 70;
*(dest+1) = 55;
int result = memcmp(dest,src,2*sizeof(int));
char *a = (char*)src;
char *b = (char*)dest;
int result2 = memcmp(b,a,1*sizeof(char));
printf("%d,%d\n",result,result2);
free(src);
free(dest);
src = NULL;
dest = NULL;
return 0;
}
内存查找
- 头文件:
#include <string.h>
- 函数原型:`int *memchr| *memrchr(const void *s,int c,size_t n)
- 函数功能:在s开始的堆内存空间前n个字节中查找字节数据c
- 函数参数:
- const void *s: 代操作内存首地址
- int c:待查找的字节数据
- size_t n:查找的字节数
- 返回值:返回查找到的字节数据地址
- 注意:如果内存中没有重复数据,memchr和memrchr