高质量C/C++编程之内存管理
2014-04-09 17:37
113 查看
内存管理
1.内存分配方式
1)从静态存储区分配
内存在程序编译时就已经分配好,且在程序的整个运行期都存在。如全局变量,static变量存放在这里。
2)在栈上创建
在执行函数时,函数内局部变量的存储单元都可以在栈上创建。函数执行结束时,这些存储单元自动被释放。
栈内存分配运算内置于处理器的指令集,效率很高,但是分配的内存容量有限。
3)从堆上分配,亦称动态内存分配
在程序运行时用malloc和new申请任意的内存,程序员自己负责在何时用free和delete释放内存。
2.常见的内存错误及其对策
1)内存分配未成功,却使用了它
对策:在使用内存前检查指针是否等于NULL。
如果指针p为参数,在函数入口处使用断言assert(NULL == p)进行检查。
如果使用malloc和new来申请内存,应该用if(NULL == p)进行防错。
2)内存分配虽然成功,但尚未初始化就引用它:没有初始化或者误以为内存缺省初值为0
内存的缺省初值是没有统一的标准的
对策:创建后赋初值
3)分配成功,且创建后赋初值,但是操作越界
4)忘记了释放内存,造成了内存泄露
动态内存的申请和释放必须配对,程序中malloc和free的次数一定要相等
5)释放了内存,却继续使用它
# 程序中对象调用关系过于复杂
# return返回指向栈内存的指针或引用
# free或delete释放内存之后,没有将指针设置为NULL,导致产生“野指针”
规则1: 使用malloc和new申请内存后,应该立即检查指针值是否为NULL,防止使用值为NULL的指针
规则2: 不要忘记对数组和动态内存赋初值,防止未初始化的内存当右值
规则3: 避免数组或指针的下标越界,特别是当心发生“多1”“少1”的操作
建议1: 动态内存的申请和释放必须配对,防止内存泄露。
建议2: 用free和delete释放内存后,立即将指针置为NULL,防止产生野指针
3.指针和数组的对比
数组要么是在静态内存区创建(全局数组),或是在栈上创建。数组名对应着一块内存。
指针可以指向任意类型的内存块,它的特征是可变,所以常用指针来操作动态内存。
1)修改内容
数组中内容可以改变,但是如果指针指向一个常量字符串(存放在静态存储区),不可通过指针修改内容。
char a[] = "hello"; a[0] = x; //ok
char *p = "world"; p[0] = x; //error
2) 内容复制和比较
不能对数组名进行复制和比较
不能使用p = a将a的内容复制给p,只能将a的地址赋值给p。
3)计算内存容量
用运算符sizeof可以计算数组的容量(字节数)。
C/C++无法知道指针所指的内存容量,除非在申请内存的时候记住它。
当数组为函数的参数进行传递时,该数组自动退化为同类型的指针。
char a[] = "hello world";
char *p = a;
cout<<sizeof(a)<<endl; //12 bytes
cout<<sizeof(p)<<endl; //4 bytes
void func(char a[100]){
sizeof(a); //a退化为同类型的指针,等于sizeof(char*),4 bytes
}
4.指针参数如何传递内存
如果函数的参数是一个指针,不要指望用该内存去申请动态内存。
void getMemory(char *p,int num){
p = (char*)malloc(num * sizeof(char));
}
void test(void){
char *p = NULL;
gerMemory(p,100); //p依然为NULL
strcpy(p,"hello"); //error,访问空指针
}
编译器总是为函数的每一个参数制作临时副本,指针参数的副本_p,编译器是_p = p,
本例中,_p申请了新的内存,只是把_p的内存地址改变了,但p丝毫没变,getMemory不能输出任何东西。
事实上,调用一次getMemory,就会泄露一块内存。
如果非要用指针参数去申请内存,需要使用指向指针的指针。
void getMemory2(char **p,int num){
*p = (char*)malloc(num * sizeof(char));
}
更直观的办法是使用函数返回值来传递动态内存:
char* getMemory3(int num){
return (char*)malloc(num * sizeof(char)); //注意此处传递的是不是栈中的指针,而是动态内存指针
}
例1:
char* getString(void){
char p[] = "hello world"; //error,传递栈中指针
return p;
}
例2:
char* getString2(void){
char *p = "hello world"; //由于“hello world”是常量字符串,存放在静态存储区,在程序生命周期恒定不变
return p; //无论何时调用,p返回同一个只读的内存块
}
7.free和delete把指针怎么啦
free和delete只是把指针所指的内存释放掉,并没有把指针本身干掉。
free(p)之后,p的值不变,依然是以前的地址,p成了野指针,所以此时需要立刻将指针置为NULL。
6.动态内存会自己释放吗
函数体内的局部变量才会在函数结束时自动消亡。
void func(void){
char *p = (char*)malloc(100 * sizeof(char));
}
临时变量p在函数结束时自动消亡,但是申请到的内存没有被释放,造成内存泄露
1)指针消亡了,不代表它所指向的内存自动释放
2)内存释放了,不代表指针消亡了或称为了NULL指针
7.杜绝野指针
野指针不是NULL指针,而是指向垃圾内存的地址。
野指针的成因:
1)指针变量没有被初始化
2)指针被free或delete之后,没有被置为NULL
3)指针操作超过了变量的操作返回,
8.malloc/free vs new/delete
malloc/free是C/C++标准库函数,new/delete是C++的运算符。
C/C++语言需要一个能完成动态分配初始化工作的操作符new,和一个完成清理和释放工作的运算符delete。
9.内存耗尽
如果没有足够内存,那么malloc和new将返回一个NULL指针,宣告内存申请失败。
处理内存耗尽问题:
1)判断指针是否为NULL,如果是马上用return终止本函数
2)判断指针是否为NULL,如果是,调用exit(1)
3) 为new和malloc设置异常处理函数
10.malloc/free使用要点
函数malloc原型:
void* malloc(size_t size);
1) malloc返回值为void*,需要在调用malloc时进行显示地类型转换
2)malloc并不识别要申请的内存是什么类型,它只关心内存的总字节数
函数free原型:
void free(void*);
11.new和delete使用要点
使用new比使用malloc简单多了,这是因为new内置了sizeof、类型转换和类型安全检查功能。
对于非内部数据类型的对象,new在创建动态对象的同时完成了初始化工作。
如果对象有多个构造函数,那么new的语句可以有多种类型。
1.内存分配方式
1)从静态存储区分配
内存在程序编译时就已经分配好,且在程序的整个运行期都存在。如全局变量,static变量存放在这里。
2)在栈上创建
在执行函数时,函数内局部变量的存储单元都可以在栈上创建。函数执行结束时,这些存储单元自动被释放。
栈内存分配运算内置于处理器的指令集,效率很高,但是分配的内存容量有限。
3)从堆上分配,亦称动态内存分配
在程序运行时用malloc和new申请任意的内存,程序员自己负责在何时用free和delete释放内存。
2.常见的内存错误及其对策
1)内存分配未成功,却使用了它
对策:在使用内存前检查指针是否等于NULL。
如果指针p为参数,在函数入口处使用断言assert(NULL == p)进行检查。
如果使用malloc和new来申请内存,应该用if(NULL == p)进行防错。
2)内存分配虽然成功,但尚未初始化就引用它:没有初始化或者误以为内存缺省初值为0
内存的缺省初值是没有统一的标准的
对策:创建后赋初值
3)分配成功,且创建后赋初值,但是操作越界
4)忘记了释放内存,造成了内存泄露
动态内存的申请和释放必须配对,程序中malloc和free的次数一定要相等
5)释放了内存,却继续使用它
# 程序中对象调用关系过于复杂
# return返回指向栈内存的指针或引用
# free或delete释放内存之后,没有将指针设置为NULL,导致产生“野指针”
规则1: 使用malloc和new申请内存后,应该立即检查指针值是否为NULL,防止使用值为NULL的指针
规则2: 不要忘记对数组和动态内存赋初值,防止未初始化的内存当右值
规则3: 避免数组或指针的下标越界,特别是当心发生“多1”“少1”的操作
建议1: 动态内存的申请和释放必须配对,防止内存泄露。
建议2: 用free和delete释放内存后,立即将指针置为NULL,防止产生野指针
3.指针和数组的对比
数组要么是在静态内存区创建(全局数组),或是在栈上创建。数组名对应着一块内存。
指针可以指向任意类型的内存块,它的特征是可变,所以常用指针来操作动态内存。
1)修改内容
数组中内容可以改变,但是如果指针指向一个常量字符串(存放在静态存储区),不可通过指针修改内容。
char a[] = "hello"; a[0] = x; //ok
char *p = "world"; p[0] = x; //error
2) 内容复制和比较
不能对数组名进行复制和比较
不能使用p = a将a的内容复制给p,只能将a的地址赋值给p。
3)计算内存容量
用运算符sizeof可以计算数组的容量(字节数)。
C/C++无法知道指针所指的内存容量,除非在申请内存的时候记住它。
当数组为函数的参数进行传递时,该数组自动退化为同类型的指针。
char a[] = "hello world";
char *p = a;
cout<<sizeof(a)<<endl; //12 bytes
cout<<sizeof(p)<<endl; //4 bytes
void func(char a[100]){
sizeof(a); //a退化为同类型的指针,等于sizeof(char*),4 bytes
}
4.指针参数如何传递内存
如果函数的参数是一个指针,不要指望用该内存去申请动态内存。
void getMemory(char *p,int num){
p = (char*)malloc(num * sizeof(char));
}
void test(void){
char *p = NULL;
gerMemory(p,100); //p依然为NULL
strcpy(p,"hello"); //error,访问空指针
}
编译器总是为函数的每一个参数制作临时副本,指针参数的副本_p,编译器是_p = p,
本例中,_p申请了新的内存,只是把_p的内存地址改变了,但p丝毫没变,getMemory不能输出任何东西。
事实上,调用一次getMemory,就会泄露一块内存。
如果非要用指针参数去申请内存,需要使用指向指针的指针。
void getMemory2(char **p,int num){
*p = (char*)malloc(num * sizeof(char));
}
更直观的办法是使用函数返回值来传递动态内存:
char* getMemory3(int num){
return (char*)malloc(num * sizeof(char)); //注意此处传递的是不是栈中的指针,而是动态内存指针
}
例1:
char* getString(void){
char p[] = "hello world"; //error,传递栈中指针
return p;
}
例2:
char* getString2(void){
char *p = "hello world"; //由于“hello world”是常量字符串,存放在静态存储区,在程序生命周期恒定不变
return p; //无论何时调用,p返回同一个只读的内存块
}
7.free和delete把指针怎么啦
free和delete只是把指针所指的内存释放掉,并没有把指针本身干掉。
free(p)之后,p的值不变,依然是以前的地址,p成了野指针,所以此时需要立刻将指针置为NULL。
6.动态内存会自己释放吗
函数体内的局部变量才会在函数结束时自动消亡。
void func(void){
char *p = (char*)malloc(100 * sizeof(char));
}
临时变量p在函数结束时自动消亡,但是申请到的内存没有被释放,造成内存泄露
1)指针消亡了,不代表它所指向的内存自动释放
2)内存释放了,不代表指针消亡了或称为了NULL指针
7.杜绝野指针
野指针不是NULL指针,而是指向垃圾内存的地址。
野指针的成因:
1)指针变量没有被初始化
2)指针被free或delete之后,没有被置为NULL
3)指针操作超过了变量的操作返回,
8.malloc/free vs new/delete
malloc/free是C/C++标准库函数,new/delete是C++的运算符。
C/C++语言需要一个能完成动态分配初始化工作的操作符new,和一个完成清理和释放工作的运算符delete。
9.内存耗尽
如果没有足够内存,那么malloc和new将返回一个NULL指针,宣告内存申请失败。
处理内存耗尽问题:
1)判断指针是否为NULL,如果是马上用return终止本函数
2)判断指针是否为NULL,如果是,调用exit(1)
3) 为new和malloc设置异常处理函数
10.malloc/free使用要点
函数malloc原型:
void* malloc(size_t size);
1) malloc返回值为void*,需要在调用malloc时进行显示地类型转换
2)malloc并不识别要申请的内存是什么类型,它只关心内存的总字节数
函数free原型:
void free(void*);
11.new和delete使用要点
使用new比使用malloc简单多了,这是因为new内置了sizeof、类型转换和类型安全检查功能。
对于非内部数据类型的对象,new在创建动态对象的同时完成了初始化工作。
如果对象有多个构造函数,那么new的语句可以有多种类型。
相关文章推荐
- 高质量C++/C编程指南 -- 第7章 内存管理 P1
- C内存管理相关内容--取自高质量C++&C编程指南
- C/C++高质量编程之内存管理
- 高质量C++/C编程指南 -- 第7章 内存管理 P3
- 高质量C++/C编程指南 -- 内存管理之一
- 高质量C++/C编程指南 -- 第7章 内存管理 (1)
- 高质量C++/C编程指南 -- 第7章 内存管理 P4
- 高质量C++/C编程指南 -- 内存管理之二
- 高质量C++/C编程指南 -- 第7章 内存管理 P5
- 高质量C++/C编程指南 - 第7章 内存管理 (2)
- 第十六章 内存管理 ===高质量C/C++编程指南
- 高质量C++/C编程指南 - 第7章 内存管理 (1)
- 高质量C++/C编程指南 -- 第7章 内存管理
- 高质量C++/C编程指南 -- 第7章 内存管理 P6
- 高质量C++/C编程指南 -- 第7章 内存管理 (1)
- 高质量C++/C编程指南 -- 第7章 内存管理 (2)
- 高质量C++/C编程指南 -- 第7章 内存管理
- 高质量C++/C编程指南 -- 第7章 内存管理
- 高质量C++/C编程指南-第7章-内存管理(3)
- C++内存管理详解 -高质量编程(林锐)