内存管理

内存管理


题外话

最近在知乎看到叫markdown的编辑器

据说它很神奇,并且它比html还简单,想知道如何在博客园用markdown写博客,请点击

想看关于markdown的中文语法说明,请点击

想知道markdown的入门指导,请点击;也可以下载一个markdown编辑器,查看软件的帮助

好,言归正传

本文是笔者学习《高质量C++C 编程指南》 (林锐) 的一些总结。

内存分配方式

内存分配方式有三种:

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

常见的内存错误及对策

发生内存错误很麻烦,编辑器不能自动发现这些错误,可能在程序运行的时候被调试人员发现。下面列举一些常见的内存错误以及对策:

  • 内存分配未成功,却使用了它

在使用之前检验指针是否为NULL。如果使用malloc或new来申请内存。应该使用如下的方法进行防错检验

if(p==NULL)||if(p!=NULL)
  • 内存分配成功,但没有初始化就引用了它

这种情况下就要加强初始化的观念,不要误认为内存的缺省初值全为零,导致引用初值错误。

  • 内存分配成果并且已经初始化,但操作越过了内存的边界

通常会出现在for循环中,笔者曾经在用迭代器的时候因为操作不当而导致指针越界

  • 忘记了释放内存却继续使用它

有三种情况:

  1. 杂乱的数据结构,致使不能从根本上解决对象管理的混乱局面。
  2. 函数的return语句写错了,不要返回指向“栈内存”的“指针”或者“引用”,因为该内存会在函数题结束时被自动销毁。下文也会有这种类型错误的很典型的函数,具体请见下文。
  3. 使用free或者delete释放了内存后,没有将指针设置为NULL。导致产生野指针

指针与数组的对比

在C++/C的程序中,指针和数组在不少地方可以相互替换着用,让人产生了一种错觉,以为两者是等价的。

首先,数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。数组名对应着(和不是指向)一块内存,其地址与容量在生命期内保持不变,只有数组的内容可以改变。

指针可以随时指向任意类型的内存块,它的特征是"可变",所以我们常用指针来操作动态内存。指针远比数组灵活,但也很危险,使用的时候要考虑上文中所提到的内容。

下面以字符串为例比较指针和数组的特性

修改内容

char a[] = "hello";
a[0] = 'X';
cout << a << endl;
char *p = "world";	//注意p指向常量字符串
p[0] = 'X';			//编译器并不能发现该错误
cout << p << endl;

上述代码中,字符数组a的容量是6个字符,分别为hello\0,a的内容可以改变,如a[0] = 'X' 。 指针p指向常量字符串“world”(位于静态存储区,内容为world\0),常量字符串的内容是不可以被修改的。从语法上看,编译器不会发现语句p[0] = 'X' 有什么不妥,但是该语句企图修改常量字符串的内容而导致运行错误。

内容复制与比较

不能对数组名进行直接的复制与比较,例如下面的代码:

char a[] = "hello";
char b[10];
strcpy(b,a);
if(strcmp(b,a)==0){
//……指针操作
}
int len = strlen(a);
char *p = (char*)malloc(sizeof(char)*(len+1);
strcpy(p,a);

如果想把数组a的内容复制给数组b,不能用语句b = a,否则会出现编译错误。应该用标准库函数strcpy进行复制。同理,语句if(p==a)比较的不是内容而是地址,应该用库函数strcmp来比较。

计算内存容量

用运算符sizeof可以计算出数组的容量(字节数)。见下面的程序:

char a[] = "hello wolrd";
char *p = a;
cout << sizeof(a) << endl;  //12字节
cout << sizeof(p) << endl;  //4字节
void func(char a[100]){
	cout << sizeof(a) << endl; //4字节 而不是100字节
}

可以看出来,sizeof(a)的值是12(包括'\0')。指针p指向a,但是sizeof(p)的值却是4。这是因为sizeof(p)得到的是一个指针变量的字节数,=>sizeof(char*),而不是p所指的内存容量。

C++/C语言没有办法知道指针所指的内存容量,除非特地在申请内存时去记住它。

还有一点值得注意的是,当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针,所以说无论在上面的func(char a[SIZE])中,SIZE值为多少,sizeof(a)始终等于sizeof(char*)。这就是数组退化为指针的一种特例。

指针参数是如何传递内存的?

如果一个函数的参数是一个指针,不要指望用该指针去申请动态内存。请看如下的这个函数:

void getMemory(char *p, int num)
{
	p = (char *)malloc(sizeof(char)* num);
}

void Test(void)
{
	char *str = NULL;
	getMemory(str, 100);	//str仍然为NULL
	strcpy(str,"hello");	//运行错误
}

上面想用getMemory(str,100)并没有使得str获得期望的内存,str依旧是NULL。

毛病出在getMemory中。编译器总是要为每个参数制作临时副本,指针p的副本是_p,编译器使_p=p。如果函数体内的程序修改了_p的内容,就导致参数p的内容作相应的修改。这就是指针可以用作输出参数的原因。在上例中,_p申请了新的内存,只是把_p所指的内存地址改变了,但是p丝毫没变。所以函数getMemory并不能输出任何东西。事实上,每执行一次getMemory就会泄漏一块内存,因为没有使用free释放内存。

函数改进

如果非得要用指针参数去申请内存,那么应该改用“指向指针的指针”,见下述函数:

void getMemory2(chaar **p, int num)
{
	*p = (char *)malloc(sizeof(char) * num);
}

void Test2(void)
{
	char *str = NULL;
	getMemory2(&str,100);	//注意参数是&str,而不是str
	strcpy(str,"hello");
	cout << str << endl;
	free(str);
}


如果不能很好的理解“指向指针的指针”,我们也可以用函数的返回值来传递动态参数。这个方法更加简单。

char * getMemory3(int num)
{
	char *p = (char *)malloc(sizeof(char) * num);
	return p;
}

void Test3(void)
{
	char *str = NULL;
	str = getMemory3(100);
	strcpy(str,"hello");
	cout << str << endl;
	free(str);
}

用函数返回值来传递动态内存这种方法虽然好用,但是有人会把return语句用错了。这里强调不要用return语句返回指向"栈内存"的指针,因为该内存在函数结束时自动消亡。见下述代码:

char * getString(void)
{
	char p[] = "hello world";
	return p;		//编译器将提出警告
}

void Test4(void)
{
	char *str = NULL;
	str = getString();		//str的内容是垃圾
	cout << str << endl;
}

用Debug逐步跟踪Test4,发现执行str = getString语句后str不再是NULL指针,但是str的内容不是"hello world"而是垃圾。

杜绝“野指针”

在C++/C编程中,我们会发现有一些"似是而非"的特征:

  • 指针消亡了,并不代表它所指的内存会被自动释放。
  • 内存被释放了,并不代表着指针消亡或成了NULL指针。

如果程序终止了运行,一切指针都会消亡,动态内存会被操作系统回收。所以,如果想让程序长期地运作,我们就要及时手动释放内存,并且将指针设置为NULL。

下面我们来说一说“野指针”,它不是NULL指针,是指向“垃圾”内存的指针,“野指针”很危险。if语句对它不起作用。
“野指针”的成因主要有两种:

  • 指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一起。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。例如:
char *p = NULL;
or
char *str = (char *)malloc(100);
  • 指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针。

以上内容为笔者根据林锐博士的《高质量C++C 编程指南》的一些摘录以及一些自己的拓展。

欢迎转载,转载请注明出处

update by 2017/3/27 13:58

已完结

by一枝猪

posted @ 2017-03-26 00:29  一棵球和一枝猪  阅读(445)  评论(1编辑  收藏  举报