引言
- 这部分主要简单介绍内存分配的三种方式,函数传递参数进行内存分配与函数返回值问题。内存管理一直是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/