C++内存管理

来源:互联网 发布:2007版excel数据有效性 编辑:程序博客网 时间:2024/06/10 01:27

引言

  • 这部分主要简单介绍内存分配的三种方式,函数传递参数进行内存分配与函数返回值问题。内存管理一直是C/C++坑最多的地方,修为不够,初步做个笔记。

1、内存分配的三种方式

  • 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static 变量;
  • 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限;
  • 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc 或new 申请任意多少的内存,程序员自己负责在何时用free 或delete 释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。

2、通过指针参数传递内存

void getMemory1(char *str, int num){    str = new char[num];}void test1(){    char *str = NULL;    int strLength = 6;    getMemory1(str, strLength);    strcpy(str, "hello");    cout << str << endl;    delete[] str;}

函数进行值传递的时候, “编译器总是要为函数的每个参数制作临时副本,指针参数 p 的副本是 _p,编译器使 _p = p。如果函数体内的程序修改了_p 的内容,就导致参数 p 的内容作相应的修改。这就是指针可以用作输出参数的原因。在本例中, _p 申请了新的内存,只是把_p 所指的内存地址改变了,但是 p 丝毫未变。”即_p指向新的内存区域,_p的修改不再影响p中内容。事实上,每执行一次 GetMemory1 就会泄露一块内存,因为没有用free 释放内存,这里可用工具VLD进行内存泄漏检测。

3、通过指针引用参数传递内存

这里可以把好程序改一下,传递参数改为传递指针引用。指针的引用,相当于传递的是:指针的指针,这样指针本身是可以被改变的。

void getMemory2(char *&str, int num){    str = new char[num];}void test2(){    char *str = NULL;    int strLength = 6;    getMemory2(str, strLength);    strcpy(str, "hello");    cout << str << endl;    delete[] str;}

4、通过指针的指针分配内存

指针的引用可行,那么指针的指针自然也可以分配内存,这两种情况都不能忘了在使用完之后释放内存空间。

#include <iostream>  #include <stdlib.h> #include "vld.h"using namespace std;void getMemory3(char **str, int num){    *str = new char[num];//new之后分配的内存赋值给指针*str,而不是指针的指针str}void test3(){    char *str = NULL;    int strLength = 6;    getMemory3(&str, strLength);//此处传递指针地址    strcpy(str, "hello");    cout << str << endl;    delete[] str;}int main(){    test3();    return 0;}

5、以函数返回类型分配内存

除了用参数进行内存空间的分配,还可以用函数返回值进行内存分配,但是这种方式一定要注意不要返回“栈内存”的指针,“栈内存”在函数结束之后会被回收。那么函数返回值是怎样将局部变量返回给调用的变量?这里粗分为以下两种方式:

  • 返回值在8字节以内(具体数值跟操作系统有关),系统将数据先移到寄存器,栈中的局部空间被回收,再由寄存器将数据返回给调用函数的变量。
  • 当返回值大于这个数值,进入子函数之前,系统会预先分配一段栈内存,寄存器将数据移到该内存,释放子函数中的数据,再由该空间将值返回给调用函数的变量。

可以看出第二种方式占用了太多资源,比较低效,所以用指针进行间接访问更高效。

char *getMemory4(int num){    char *str = new char[num];    return str;}void test4(){    char *str = NULL;    int strLength = 60;    str = getMemory4(strLength);    strcpy(str, "hello");    cout << str << endl;    delete[] str;}int main(){    Test4();    return 0;}

6、函数返回字符串

以上的方式可通过函数分配空间再进行字符串的赋值,如果直接用函数传递字符串又有哪些坑需要注意的?
看下面这个例子,函数返回指向“hello World”的数组首地址,这是一个局部变量,在函数结束时会释放内存。所以函数返回的是一个已被释放的内存地址,所以有可能打印出来的是乱码

char *GetString1(void){    char p[] = "hello world";    return p; }void test5(){    char *str = NULL;    str = GetString1();    cout << str << endl;}int main(){    Test5();    return 0;}

但是用以下这种方式是可以正常返回”hello world!”,因为str指向的内容是是一个“只读”字符串常量,存放在只读数据段,位于静态存储区,它在程序生命期内恒定不变,函数返回的正是只读数据段的首地址。

char *GetString2(){    char *str = "hello world!";    return str;}

7、 free和delete之后的指针

free和delete之后的指针只是释放指针指向的内存空间,但是指针本身依旧存在,所指的内存地址不会改变,该地址的内存内容为垃圾,不是NULL。

8、总结

  • 谨防内存泄漏,动态分配的内存空间即使赋值给局部指针变量也不会自动回收(指针变量所占的空间会被回收),需要调用free/delete释放;
  • 指针消亡了,并不表示它所指的内存会被自动释放;
  • 内存被释放了,并不表示指针会消亡或者成了 NULL 指针;

参考:
1、 《高质量 C++/C 编程指南》林锐
2、 http://blog.csdn.net/learning_zhang/article/details/52389035
3、 http://blog.csdn.net/haiwil/article/details/6691854/

0 0