常见的动态内存错误
- 1.动态内存错误
- 2.经典案例分析
- 2.1案例一
- 2.1.1**问题分析**
- 2.1.2**修改错误**
- 2.2案例二
- 2.2.1 原因分析
- 2.2.2 解决问题
- c/c++内存分布
- 1.2 内存分区简介
- 1.2.1 栈区(stack)
- 1.2.2 堆区(heap)
- 1.2.3 全局(静态)区
- 1.2.4 常量区
- 1.2.5 代码区
1.动态内存错误
(1)对NULL指针的解引用操作
(2)对动态开辟空间的越界访问
(3)对非动态开辟内存使用free释放
(4)使用free释放一块动态开辟内存的一部分
(5)对同一块动态内存多次释放
(6)动态开辟内存忘记释放(内存泄漏)
2.经典案例分析
2.1案例一
#include<stdio.h>
#include<string.h>
void getmemory(char* p)
{
p = (char*)malloc(100);
}
void test(void)
{
char* str = NULL;
getmemory(str);
strcpy(str, "hello");
printf("%s", str);
}
int main()
{
test();
return 0;
}
2.1.1问题分析
上述代码运行失败。
首先回顾一下知识;
值传递:将实参的值复制到形参相应的存储单元中,即形参和实参分别占用不同的存储单元。
地址传递:形参并不存在存储空间,编译系统不为形参数组分配内存。因此在数组名或指针作函数参数时所进行的传送只是地址传送,形参在取得该地址之后,与实参共同拥有一段内存空间,形参的变化也就是实参的变化。
原因是:1.因为这里是值传递,p和str分别占用不同的存储单元,malloc只是给p开辟了空间,而str仍然指向空指针。所以strcpy赋值时失败。(对NULL指针的解引用操作)
2.没有free(p);造成内存泄漏。(动态开辟内存忘记释放)
注意:printf(“%s”, str);与printf(str);是一样的。
2.1.2修改错误
#include<stdio.h>
#include<string.h>
void getmemory(char** p)
{
*p = (char*)malloc(100);
}
void test(void)
{
char* str = NULL;
getmemory(&str);
strcpy(str, "hello");
printf("%s", str);
free(str);
str = NULL;
}
int main()
{
test();
return 0;
}
运行成功
- 修改为地址传参
下图是一个指向关系图:
由图可见:str是一级指针,p是二级指针。
2.增加了free释放
free(str);
str = NULL;
2.2案例二
#include<stdio.h>
#include<string.h>
char* getmemory(void)
{
char p[] = "hello word";
return p;
}
void test(void)
{
char* str = NULL;
str = getmemory();
printf("%s", str);
free(str);
str = NULL;
}
int main()
{
test();
return 0;
}
2.2.1 原因分析
原因就出在下面代码上;
char* getmemory(void)
{
char p[] = "hello word";
return p;
}
数组p临时申请的那块空间,在退出这个函数的时候,就被释放掉了。
虽然我们的str可以获得p的地址,但是p指向的东西未知,p就相当于野指针
。访问时,就会造成非法访问
。
属于:返回栈空间地址问题。
2.2.2 解决问题
#include<stdio.h>
#include<string.h>
char* getmemory(void)
{
static char p[] = "hello word";
return p;
}
void test(void)
{
char* str = NULL;
str = getmemory();
printf("%s", str);
free(str);
str = NULL;
}
int main()
{
test();
return 0;
}
运行成功
我只是增加了static静态修饰符。
static char p[] = "hello word";
为什么这样就可以呢?首先我们来回顾static的作用
实际上普通的局部变量是在栈区分配空间的,栈区的特点是在上面创建的变量出了作用域就销毁。但是被static修饰的变量存放在数据段(静态区
),数据段的特点是在上面创建的变量,直到程序结束才销毁,
所以生命周期变长。
所以,我们使用static修饰过后,当退出这个函数时,这个变量就会一直保存,我们再次调用时,仍是保存上一次的调用结果。此时,str可以获得p的地址。并且,p不再是野指针。
c/c++内存分布
内核空间,内存映射段。
1.2 内存分区简介
1.2.1 栈区(stack)
栈区
由编译器自动分配释放,由操作系统自动管理,无须手动管理。
栈区上的内容只在函数范围内存在,当函数运行结束,这些内容也会自动被销毁。
1.栈区按内存地址由高到低方向生长,其最大大小由编译时确定,速度快,但自由性差,最大空间不大。
2.栈区是先进后出原则,即先进去的被堵在屋里的最里面,后进去的在门口,释放的时候门口的先出去。
栈区存放内容
临时创建的局部变量和const定义的局部变量存放在栈区。
函数调用和返回时,其入口参数和返回值存放在栈区。
1.2.2 堆区(heap)
堆区由
程序员
分配内存和释放。堆区按内存地址由
低到高
方向生长,其大小由系统内存/虚拟内存上限决定,速度较慢,但自由性大,可用空间大。
堆区动态申请与释放内存用malloc(),free()等函数实现动态分布内存。
1.2.3 全局(静态)区
通常是用于那些在编译期间就能确定存储大小的变量的存储区,但它用于的是在整个程序运行期间都可见的全局变量和静态变量。
全局区有 .bss段 和 .data段组成,可读可写。
1 bss段
未初始化的全局变量和未初始化的静态变量存放在.bss段。
初始化为0的全局变量和初始化为0的静态变量存放在.bss段。
.bss段不占用可执行文件空间,其内容由操作系统初始化。
2data段
已初始化的全局变量存放在.data段。
已初始化的静态变量存放在.data段。
.data段占用可执行文件空间,其内容有程序初始化。
1.2.4 常量区
字符串、数字等常量存放在常量区。 const修饰的全局变量存放在常量区。 程序运行期间,常量区的内容不可以被修改。
1.2.5 代码区
程序执行代码存放在代码区,其值不能修改(若修改则会出现错误)。 字符串常量和define定义的常量也有可能存放在代码区。