Chinaunix首页 | 论坛 | 博客
  • 博客访问: 875563
  • 博文数量: 366
  • 博客积分: 10267
  • 博客等级: 上将
  • 技术积分: 4290
  • 用 户 组: 普通用户
  • 注册时间: 2012-02-24 14:04
文章分类

全部博文(366)

文章存档

2012年(366)

分类: 系统运维

2012-03-10 19:39:54

 使用C/C++的苦逼娃们经常深陷内存越界错误,资源泄漏错误等等问题,而且这样的惨剧每时每刻都在这个世界上重复的发生着。其实,我也是苦逼娃哭泣的脸!也为了个神马越界的东西debug了整个下午过,为此也想砸电脑过。最后想想,其实不是C/C++苦逼,是我们的不小心而造成如此的苦逼。罪过,罪过。。。尴尬

在C语言库中,提供了这么两个函数,malloc和free,分别用于执行动态内存分配和释放。两个函数的声明如下:

void* malloc(size_t size);void free(void *pointer);

从外观上看,malloc函数的参数是接受需要分配的内存字节数,如果内存能够满足请求量,那么将会返回:太阳指向被分配的内存块起始位置的指针(引用自《C和指针》中的表述)。而且,这块内存是一块连续的内存。够爽吧!如果分配不成功的话,那么就返回null指针灯泡。例如:

struct Node{ data_type value; struct Node *pre_node; struct Node *next_node;}; typedef struct Node *List; List newnode = NULL; newnode = (List)malloc(sizeof(struct Node)); // 分配了Node的空间,返回了相应的指针 if (!newnode) // 为了防止空指针的存在,要检查是否分配成功{ printf("内存分配不成功!\n"); exit(1);}

在这里,我们又一次看到了void指针的功效。对于不同类型的指针转换,void指针都能应付自如,更重要的是,我们不用为每种类型都特化一个内存分配版本。这个会累死的恶魔。还有,对于分配出来的指针,我们要检查它是否为空,这是为什吗?因为,对于空指针的操作将会带来未知的错误,所以错误摁在萌芽阶段是我们的义务和责任。

内存的东西,向来都是要有借有还。有malloc就会有free。太阳free函数的作用就是负责释放申请来的内存的。这个指向释放的内存指针是有讲究的,你不能释放申请来的一小部分的区域。例如:

int *pi; pi = (int *)malloc(sizeof(int)*10); free(pi + 5); // 这里就苦逼了!

其次,较为常见失误在于,灯泡我们对申请的内存指针进行操作,操作完之后,对其进行释放。此时,释放不成功的原理和上面这个例子一样,释放了部分的内存,而不是释放全部内存。一般的做法是设一个临时变量保存malloc来的指针,在释放的时候,就释放这个指针所以的内存区域,这样就不会出错了。

关于内存越界,这是一个很普遍的问题。这个只能你去亲自把握,记清楚你申请了多少,其边界在哪里,不要出错就埋怨电脑。

说了malloc和free,那就进入今天的主题new和delete了。

想必我们都写过这样的代码吧灯泡

string *str = new string("memory alloc!");

我们都知道new在堆上创建了一个string类型对象,这个看似一个动作的事情却暗地包含了三件事恶魔:获得一块内存空间、调用构造函数、返回正确的指针。相当于下面:

void *pstr = operator new(sizeof(string));string::string("memory alloc!");string *str = static_cast(pstr);

你应该会心里犯嘀咕,卧槽,怎么出现了这么多的环节书呆子。其实呢,new可分为这么三种情形:new operator(我们平常用的new), operator new(用来分配内存的),placement new(构造对象的)。当然,你要用placement new的时候,你要加入的头函数。

⑴ new operator

new operator的第一步分配内存实际上是通过调用灯泡operator new来完成的,这里的new是可以重载的。operator new默认情况下首先调用分配内存的代码,尝试得到一段堆上的空间,如果成功就返回,如果失败,则转而去调用一个new_hander,然后继续重复前面过程。

#include using namespace std; class MyClass {private: public: MyClass() { }; void* operator new(size_t size) { cout << "调用了operator new" << endl; return ::operator new(size); }}; int main(){ MyClass *a = new MyClass(); return 0;}

运行的结果如下:

OW~C{Q48Q)V5ND[M$62RN@2

相应的,delete也有delete operator和operator delete之分,后者也是可以重载的。并且,如果重载了operator new,就应该也相应的重载operator delete。

⑵ placement new

placement new的作用是用来实现定位构造对象,可以说相当于new operator三步操作中的第二步,也就是在取得了一块可以容纳指定类型对象的内存后,在这块内存上构造一个对象。其用法太阳

void construct(pointer __p, const _Tp& __val) { new(__p) _Tp(__val); }

其格式是这样的大笑:new(指针指向的内存地址) 类型(值)。对照格式,可以这么说是在__p上构造一个_Tp类型的,值为__val。

如果是像上面那样在栈上使用了placement new,则必须手工调用析构函数:

void destroy(pointer __p) { __p->~_Tp(); }

当觉得默认的new operator对内存的管理不能满足我们的需求,我们可以自己手工打造内存管理,此时的placement new就相当有用了。以上两段代码源于sgi stl的alloc中,就说明问题了灯泡

⑶ new operator 和 operator new 的取舍

两个关键词很容易让人迷糊,两者又都是骨肉相连的状态,取舍有点不易。对于取舍的评判,我挪用了《more effective C++》中的观点太阳:“如果你想产生对象在heap上,那么就用new operator,它不但分配内存,而且为该对象调用了构造函数;如果你只打算分配内存,那么就用operator new ,只分配内存,其余的事情都没做。当然,使用operator new 后续的对象构造也要跟上,不然但分配内存也就没有意义了。”

总结,在这篇文章中,介绍了malloc,free,new,delete的使用和一些心得体会。当然,还有许多的内容没有做相应的介绍,比如new_handle等内容。抱歉,抱歉!

关于内存分配的问题,你还可以参考:内存分配机制。这篇文章介绍了C/C++内存分配的知识。

参考文献

1. 《C语言程序设计》

2. 《C和指针》

3. 《C专家编程》

4. 《more effective C++》

5. 《STL源码解析》

阅读(321) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~