您的位置:首页 > 编程语言 > C语言/C++

高质量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的语句可以有多种类型。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: