使用动态内存可能出现的问题总结(笔试题解析)
2018年08月02日 15:56:19 Jeanne_ou 标签: C语言 内存访问 动态内存开辟 指针 函数调用
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Erica_ou/article/details/81361017
在使用动态内存分配程序中,常常会产生不易发现的错误,这其中就包括对NULL指针进行解引用,访问未知内存区域(越界访问&访问未初始化指针指向区域),内存泄漏,释放非动态内存开辟空间(向free函数传递一个并非由malloc函数返回的指针),释放动态内存开辟的部分空间,空间释放后利用指针被继续使用,不检查空间分配结果。
问题一:访问未知内存区域
问题二:对NULL指针进行解引用访问
问题三:内存泄漏
问题四:内存分配检查
问题一:访问未知内存区域
例1:以下代码有什么问题,运行会有何结果(题目来自《C语言深度解剖》)
struct student
{
char *name;
int score;
}*pstu;
int main()
{
pstu = (struct student *)malloc(sizeof(struct student));
strcpy(pstu->name, “Jimy”);
pstu->score = 99;
free(pstu);
return 0;
}
答案:运行错误:
error7.exe 中的 0x5b33f689 (msvcr90d.dll) 处未处理的异常: 0xC0000005: 写入位置 0xcdcdcdcd 时发生访问冲突。
在定义结构体变量 stu 时,对于结构体内部 char *name 成员只是给 name 这个指针变量本身分配了 4 个字节。name 指针并没有指向一个合法的地址,这时候其内部存的只是一些乱码。所以在调用 strcpy 函数时,会将字符串”Jimy”往乱码所指的内存上拷贝,而这块内存 name 指针根本就无权访问,导致出错。解决的办法是为 name 指针 malloc 一块空间。
在为结构体指针分配内存时是从外向里,即先分配结构体的指针再分配成员指针.释放时从里向外,先释放成员指针再释放结构体指针。
图示:
这里写图片描述
修改:
int main()
{
pstu = (struct student *)malloc(sizeof(struct student));
pstu->name = (char *)malloc(20);
strcpy(pstu->name,"Jimy");
pstu->score = 99;
free(pstu->name);
//free()函数并不能将指针指向空,在free掉之后应该手动将开辟的空间指针指向空。
pstu->name = NULL;
free(pstu);
pstu = NULL;
return 0;
}
例二:以下代码有什么问题,运行会有何结果(题目来自《高质量的c c++编程》)
char *GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}
结果:可能是乱码。
在调用函数的时候在栈空间内定义一个字符数组并返回其首地址(数组名)。但是函数调用结束后在栈上定义的字符数组内容被销毁,只返回了原首地址。也就是说函数返回后再根据此地址查找到的内容并非是定义的内容(随机值)。
问题二:对NULL指针进行解引用访问
例三:以下代码有什么问题,运行会有何结果(题目来自《高质量的c c++编程》)
void GetMemory(char *p)
{
p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
结果:程序崩溃
在这里首先要注意的一点是函数参数传递时具有两种方式:值传递和值传递。值传递是传给函数一份参数在栈上的临时拷贝,函数在这个临时拷贝上进行操作。函数调用结束后该临时拷贝会被销毁,除非有返回值否则不会对实参造成影响。址传递则是将变量的地址传递给形参,调用函数时通过这个形参找到实参直接对实参进行更改。
需要注意的是,如果实参是一个指针变量那么传址传的是指针的地址(&指针变量名,相当于一个二级指针)而非指针的内容(虽然都是地址,但意义完全不同)。
所以本题的调用语句GetMemory(str);实质上是一次传值调用,而且函数返回值为空,这就意味无论在函数内部进行了什么操作一旦调用结束返回str指针变量的值还是NULL并未发生任何改变。而且NULL指向的空间是被禁止访问的。
问题三:内存泄漏
会产生泄漏的内存就是堆上的内存,也就是说由malloc 系列函数或 new 操作符分配的内存。如果用完之后没有及时 free 或 delete,这块内存就无法释放,直到整个程序终止。
例四:以下代码有什么问题,运行会有何结果(题目来自《高质量的c c++编程》)
void GetMemory2(char **p, int num)
{
*p = (char *)malloc(num);
}
void Test(void)
{
char *str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
}
结果:能够输出 hello但会发生内存泄漏
这里的函数调用以及内存开辟都是正确的(是例3的正确形式),但唯一少了将开辟的空间free掉。既然有分配,那就必须有释放。不然的话,有限的内存总会用光,而没有释放的内存却在空闲。与 malloc 对应的就是 free 函数了。free 函数只有一个参数,就是所要释放的内存块的首地址。而且free函数只做了一件事:斩断指针变量与这块内存的关系,但却并没有能力将指针指向空。所以一般为了安全起见,在释放掉空间后都会手动将指针指向NULL。
注意:
指针指向的开辟空间只能释放一次,若多次free可能引发不可预测的问题
释放一块非动态内存开辟的空间可能会导致程序立即终止或延迟终止
试图释放一块动态内存开辟空间的部分内容也有可能造成相同情况但是realloc函数可以缩小一块动态分配内存,有效释放尾部部分内容。
问题四:内存分配检查
例五:以下代码有什么问题,运行会有何结果(题目来自《高质量的c c++编程》)
void Test(void)
{
char *str = (char *) malloc(100);
strcpy(str, “hello”);
free(str);
if(str != NULL)
{
strcpy(str, “world”);
printf(str);
}
}
结果:篡改动态内存区的内容,后果难以预料
在这里可以看到在执行完strcpy后程序已经将指针str释放,但此时的指针变量 p 本身保存的地址并没有改变也就是!=NULL,此时再通过str访问那片内存就是非法访问。所以一定要在指针释放后将指针指向NULL。
需要注意的是,在这里若进行改动并不能将free放到最后再执行。因为当空间已经被开辟且被使用后再判断其指向是否为空在逻辑上行不通。而且不管什么时候,使用指针之前首先应该确定指针是有效的。**
一般在函数入口处使用assert(p!=NULL)检验。在非参数的地方使用if(p!=NULL)。
本文主要用于总结学习之用,参考数目《高质量的c c++编程》,《C语言深度解剖》,《C和指针》。文中若有不足或错误欢迎批评指针,谢谢。