|
阅读:2282回复:11
恐怖C++之四
New, Delete. "Just this !?".
上次讨论了一下 malloc、free 和 heap 的操作,这次翻翻箱底,把 new 和 delete 也来个小节。 “new 和 delete 有什么好总结的? new 完了记得 delete 就好了!” 首先看最简单的操作,在堆中初始化一个内建类型(primary type)数据:
猜猜输出是多少,我就没猜对,在 VC6 上得到的结果是:“*p is -842150451”,看来编译器内部生成的 default constructor 并没有初始化数据呀!(细节见恐怖C++之二) 当然你也可以给 int() 传递一个参数,类似 new int(517)。 然后稍复杂一点,在堆中初始化一个自定义类型数据:
结果是:“Point is (-842150451, -842150451)”,两个数据成员都没有被初始化,不过这都是构造函数的问题,我只是想说:“别以为 new 调用了构造函数,数据就一定会被初始化!” new 和 malloc 的最大不同就是 new 在从堆中分配完内存后,还会在指定的内存上调用构造函数,编译器为每个new表达式提供类似如下伪码:
new 首先调用 ::operator new(int size) 函数从堆中分配内存,然后调用类的某个构造函数,将所得到的内存地址作为第一个参数传入(实际上的 this 指针),然后调用 operator &() 得到对象的有效地址。 如果用户自定义了某个类的 operator new() 操作符,编译器就会用自定义的操作符来替代上面的全局操作符 ::operator new(),在本例中为: void *p = Point::operator new(sizeof(Point))。另外,编译器默认提供的 ::operator new() 就相当于 malloc,给定尺寸、分配内存,而 ::operator delete() 也相当于 free。 我们还可以通过显示的调用特定的 new 操作来选择 operator new()。如:“Point *pj = ::new Point()”,这时即使用户定义了 Point::operator new(int size) 也不会被调用,因为我们显示的选择了全局的 ::operator new(int size)。 对于每一个类,编译器都会自动为其生成一个 operator &(),用来进行取地址运算。 对于 delete 也有类似的伪码:
除了 new 和 delete 之外还有一套 new[] 和 delete[] 用来对数组进行操作,只要不将两种东东混用就可以了。其实我们根本不可能将 new 用于数组的分配(我又在断言了,呵呵),然而 new[] 用于单个对象的情况还是有的,很容易写出下面这样的代码:
对于这样的代码,即便在所有现有的编译器上测试都能够通过,我也宁可认为这是一个因编译器而异的、未定义行为的代码。 new 和 new[] 调用的操作符函数也不相同,new[] 调用的是 operator new[](int size)。 出错的总是 delete 与 new 的配合问题,在写每一个 delete 时我们都应该仔细的查看相应的 new 操作,delete 与 delete[] 的区别并不在于其释放的内存大小(还记得恐怖C++之三中 free 的运行方法么),而在于他们调用的析构函数的数量。delete 只会调用一个析构函数,而 delete[] 会对有效地址空间(待释放地址空间)内的所有对象调用析构函数。 试想如果析构函数中带有 File Close、Memory Release 或者 Object Counting 等代码,那么错误的将 delete 用作 delete[] 将导致的后果。恐怖! 回头看看上面写过的所有的代码,有没有发现什么问题,哈哈。 new 操作并不总是会成功的,至少在超过 4G 地址空间的时候会出错,所以必须要进行错误检测,像这样:
可是,这样的代码也不正确,虽然这是我们最常见的。这样的代码在VC6中运行良好,可是却不是标准的。作为标准的拥护者,我们都希望自己的所有代码都符合标准,而不是随编译器的不同而有不同表现。 在标准 C++ 中 new 操作失败时会抛出异常 std::bad_alloc,而不保证返回 0。所以上面的检测代码在一些编译器上可能不起任何作用,为了支持这种返回 0 的检测方式标准 C++ 还提供了一个非异常方式的 new 操作:
nothrow 并不是关键字,事实上是一个 std::nothrow_t 类型的全局对象,new 表达式会调用 operator new(int size, ¬hrow_t) 操作符函数,此函数不抛出任何异常,而是以返回 0 作为出错标识。 所以正确的检测方法应该是:
除此之外,C++ 中还有一个 new_handle 机制来对内存分配的错误进行处理,用户可以通过 set_new_handle() 函数向系统注册一个 new_handle 处理程序,当内存分配发生异常时这个函数就会被调用。准确说是循环调用,直到获得有效内存。这时上面这些异常、返回 0 之类都不会起作用! new_handle 可以用在自定义的内存池上,用来处理类似 GC 机制。 对 new 和 delete 的小节基本到此,但还差一些东西,如:编写自定义的 operator new,以及 placement new 操作,只能下次再说了。 |
|
|
1C#
发布于:2004-07-16 18:04
|
|
|
|
2C#
发布于:2004-07-16 22:06
Re:恐怖C++之四
[em050]
看来楼主对 c++很熟息 我做你的徒弟吧! 一直想认真的学习C++但是每回都是半途而废[em050][em061]因为有时候程序编译的时候很多错误,自己根本无法解决掉 当然,这是我为自己想出来的借口。 希望能得到你的帮助 |
|
|
3C#
发布于:2004-07-17 09:06
Re:恐怖C++之四
我能看懂的地方差不多就到这里了,有点不明白的是:构造函数根本就没有初始化数据,难道构造函数会把变量初始化为变量的数据类型的默认值吗?不是很清楚 -------------------- [em074][em074][em074][em074][em074][em074][em074][em074][em074][em074][em074][em074][em074] [em074]趴在楼下的背上睡得呼呼的Zzzzzz............[em074] [em074][em074][em074][em074][em074][em074][em074][em074][em074][em074][em074][em074][em074] [a=http://home.itdrp.com/go2west/]感情欠费中 爱情停机中 工作寻找中[/a] |
|
|
|
4C#
发布于:2004-07-22 23:40
Re:恐怖C++之四
只有VC在Debug时会将其初始化为0xcdcdcdcd,谁听过我的课还记得的来补充一下,MS为什么选这个值. |
|
|
5C#
发布于:2004-07-23 09:19
Re:恐怖C++之四
0xcdcdcdcd 永远不是一个可用的内存地址。
_nh_malloc_dbg这样做是为了更方便地捕捉错误。 |
|
|
|
6C#
发布于:2004-07-23 09:23
Re:恐怖C++之四
_heap_alloc_dbg initializes each byte of the allocated block to 0xCD. It also initializes the "no-man's land"—extra bytes that belong to the internal block allocated, but not the block you requested—with 0xFD. (The internal block can be larger than what you requested.) Similarly, the debug version of free called from ::delete initializes freed bytes with yet another special value, 0xDD. Figure 1 is a snippet from the C runtime source file dbgheap.c that explains why Microsoft chose these particular values.
Figure 1 dbgheap.c Fragment /* * The following values are non-zero, constant, odd, large, and atypical * Non-zero values help find bugs assuming zero filled data. * Constant values are good so that memory filling is deterministic * (to help make bugs reproducible). Of course it is bad if * the constant filling of weird values masks a bug. * Mathematically odd numbers are good for finding bugs assuming a cleared * lower bit, as well as useful for trapping on the Mac. * Large numbers (byte values at least) are less typical, and are good * at finding bad addresses. * Atypical values (i.e. not too often) are good since they typically * cause early detection in code. * For the case of no-man's land and free blocks, if you store to any * of these locations, the memory integrity checker will detect it. */ static unsigned char _bNoMansLandFill = 0xFD; /* fill no-man's land w/this */ static unsigned char _bDeadLandFill = 0xDD; /* fill free objects w/this */ static unsigned char _bCleanLandFill = 0xCD; /* fill new objects w/this */ |
|
|
|
7C#
发布于:2004-07-23 16:52
Re:恐怖C++之四
c++
不只是恐怖,一听到简直就是恐惧阿。非常PF学c的,高深莫测的样子。 还是java简单阿,构造函数会自动初始化。 -------------------- 好好吃饭,天天睡觉 努力赚钱,娶个老婆 |
|
|
|
8C#
发布于:2007-03-18 13:51
Re:恐怖C++之四
0xcdcdcdcd 永远不是一个可用的内存地址。 为什么不是 0x0000 .... ---- 好像还差几个 0x00000000 [ 2007-03-18 13:52:21 0000 修改 ] |
|
|
|
9C#
发布于:2007-04-24 20:10
Re:恐怖C++之四
0xcd == { int 3; }
并且是个负数 是不是这个原因,老牛? |
|
|
|
10C#
发布于:2007-04-24 21:03
Re:恐怖C++之四
欢迎来扫盲!!!
|
|
|
|
11C#
发布于:2007-05-24 09:29
Re:恐怖C++之四
引用:
class Point { public: int x, y; }; Point *pj = new Point(); cout << "Point is (" << x << ", " << y << ")" << endl; delete pj; 结果是:“Point is (-842150451, -842150451)”,两个数据成员都没有被初始化,不过这都是构造函数的问题,我只是想说:“别以为 new 调用了构造函数,数据就一定会被初始化!” 这是很正常的,new的调用,会引发构造函数,但如果构造函数为空,类成员肯定不会被初始化。只是被声明。 |
|
|