1.野指针
定义的指针变量应该初始化为NULL,用完指针后也应设置为NULL,否则该指针变量指向的是一块未知的内存,发生未知错误。
例如:
int *p = NULL;
...
...
free(p);
p = NULL;
2.栈、堆和静态区
对于程序员,我们简单的将内存理解为三部分:堆、栈、静态区。
栈:保存的是局部变量,栈的内容只存在函数范围内,函数运行结束,这些内容自动销毁,特点:效率高,但空间有限。
堆:通过malloc系列函数或者new操作符分配的内存,生命周期由free和delete决定,手动申请,手动释放。若未释放,直到程序结束释放。特点:使用灵活,空间较大,但容易出错,未释放内存。
静态区:保存自动全局变量和static变量。静态区的内存在整个程序的生命周期都存在,在编译阶段分配。
3.常见的内存错误
3.1指针未能指向一个合法内存区域
① 结构体成员指针未初始化
struct person
{
char *name;
int age;
}per ,*per_s;
int main()
{
strcpy(per.name,"lll");
return 0;
}
此处定义了结构体变量per,只是给name指针变量分配了4个字节,但指针name没有指向一个合法的地址,在调用strcpy会将字符串"lll"向指向一个未知的地址复制,而这个未知地址,该指针没有访问权限,导致出错。
解决方案:给成员指针变量name malloc一块空间。
相似错误,例如:
struct person
{
char *name;
int age;
}per ,*per_s;
int main()
{
per_s = (struct person *)malloc(sizeof(struct person));
strcpy(per_s->name,"lll");
free(per_s);
per_s = NULL;
return 0;
}
为指针变量per_s分配了内存,但是依旧没有给name指针分配内存,此处容易产生错觉,以为给per_s分配了内存,也给name分配了内存。其解决方案同上。
例如:
int main()
{
per_s = (struct person *)malloc(sizeof(struct person *));
strcpy(per_s->name,"lll");
...
return 0;
}
为per_s分配内存,分配的大小不合适,此处将sizeof(struct person)误写为sizeof(struct person *)。不过name指针依旧未能分配内存,解决方案同上。
③ 函数入口校验
可以通过assert宏对参数校验,例如assert(NULL != per_s);仅作为定位错误的一种方式。
3.2 分配的内存不够
为指针分配了内存,但是不够,后续操作出现越界错误。
char *p1 = "123456789";
char *p2 = (char *)malloc(sizeof(char) * strlen(p1));
strcpy(p2,p1);
p1是字符串常量,长度为9,但是占用内存大小是10字节。字符串常量的结束标志"\0",若按照上述操作,则p1的结束标志未能复制到p2。
只有字符串常量有结束标志符,下面这种写法没有:
char arr[3] = {'a','b','c'};
此外不要因为char的大小为1就省略sizeof(char)的写法,会导致代码的可移植性降低。
3.3内存越界
内存分配成功,且已经初始化,但是操作越过了内存的边界。这种错误经常是由于操作数组或指针时出现“多1”或“少1”而出现的。
3.4内存泄漏
会产生泄漏的内存就是堆上的内存(这里不讨论资源、句柄等泄漏情况),即由malloc系列函数或new操作符分配的内存。如果用完之后没有及时free或delete,这块内存就无法释放,直到整个程序终止。
因此在malloc空间,使用完成后,切记需要free释放内存,若为指针,则需要对指针初始化为NULL;若未将指针指向NULL;则成为野指针。