VC6.0如何讓new失敗後拋出異常【轉】

http://www.enet.com.cn/article/2005/1013/A20051013461516.shtml

【簡 介】
  標準C++規定new一個對象時如果分配內存失敗就應拋出一個std::bad_alloc異常,如果不希望拋出異常而僅僅傳回一個NULL指針,可以用new的無異常版本:new(nothrow)。

C標準庫一起使用,比如libcp.lib與libc.lib搭配。另外,VC6.0在new.cpp還定義了一個operator new,原型如下 :

void * operator new( unsigned int cb )

  而new.cpp對應的目標模塊卻是被打包進C標準庫中的(是不是有點奇怪?)。

  一般來說,程序員不會顯式指定鏈接C++標準庫,可是當程序中確實使用了標準C++庫時鏈接器卻能聰明地把相應的C++標準庫文件加進輸入文件列表, 這是爲什麼?其實任何一個C++標準頭文件都會直接或間接地包含use_ansi.h文件,打開它一看便什麼都清楚了(源碼之前,了無祕密) :

/***
*use_ansi.h - pragmas for ANSI Standard C++ libraries
*
* Copyright (c) 1996-1997, Microsoft Corporation. All rights reserved.
*
*Purpose:
* This header is intended to force the use of the appropriate ANSI
* Standard C++ libraries whenever it is included.
*
* [Public]
*
****/


#if _MSC_VER > 1000
#pragma once
#endif

#ifndef _USE_ANSI_CPP
#define _USE_ANSI_CPP

#ifdef _MT
#ifdef _DLL
#ifdef _DEBUG
#pragma comment(lib,"msvcprtd")
#else // _DEBUG
#pragma comment(lib,"msvcprt")
#endif // _DEBUG

#else // _DLL
#ifdef _DEBUG
#pragma comment(lib,"libcpmtd")
#else // _DEBUG
#pragma comment(lib,"libcpmt")
#endif // _DEBUG
#endif // _DLL

#else // _MT
#ifdef _DEBUG
#pragma comment(lib,"libcpd")
#else // _DEBUG
#pragma comment(lib,"libcp")
#endif // _DEBUG
#endif

#endif // _USE_ANSI_CPP

  現在我們用實際代碼來測試一下new會不會拋出異常,建一個test.cpp源文件:

// test.cpp
#include
#include

using namespace std;

class BigClass
{
 public:
  BigClass() {}
  ~BigClass(){}
  char BigArray[0x7FFFFFFF];
};

int main()
{
 try
 {
  BigClass *p = new BigClass;
 }
 catch( bad_alloc &a)
 {
  cout << "new BigClass, threw a bad_alloc exception" << endl;
 }

 BigClass *q = new(nothrow) BigClass;
 if ( q == NULL )
  cout << "new(nothrow) BigClass, returned a NULL pointer" << endl;
  try
  {
   BigClass *r = new BigClass[1];
  }
  catch( bad_alloc &a)
  {
   cout << "new BigClass[1], threw a bad_alloc exception" << endl;
  }
 return 0;
}

 

根 據VC6.0編譯器與鏈接器的做法(請參考《爲什麼會出現LNK2005"符號已定義"的鏈接錯誤?》),鏈接器會首先在C++標準庫中解析符號,然後才 是C標準庫,所以如果開發者沒有自定義operator new的話最後程序鏈接的應該是C++標準庫中newop.obj和newop2.obj模塊裏的代碼。可是程序運行的結果卻是:

new(nothrow) BigClass, returned a NULL pointer

  顯然程序始終未拋出bad_alloc異常。單步跟蹤觀察,發現第1個和第3個new實際上調用了new.cpp裏的operator new,而第二個new(nothrow)則正確地調用了newop2.cpp定義的版本。很難理解是吧?但是當你用

dumpbin /SYMBOLS libcp.lib

  dump出libcp.lib所有的符號信息時,你會發現其中的newop.obj模塊沒有定義任何符號(其它版本也一樣)。不可思 議!newop.cpp的實現代碼明明寫在那兒,怎麼會....?讓我們再仔細看看newop.cpp,咦,operator new的定義被包裹在一個#if...#endif塊中:

#if !defined(_MSC_EXTENSIONS)

...
...

void *__cdecl operator new(size_t size) _THROW1(_STD bad_alloc)
{
...
...
}

#endif

  原來需要_MSC_EXTENSIONS宏未定義,實現代碼纔是有效的啊。那麼這個宏是什麼意思?其實Visual C++在語言層面上對ANSI C標準做了一些特殊的擴展,定義_MSC_EXTENSIONS意味着編譯器支持這樣的擴展,沒有定義它編譯器就會嚴格按照ANSI C標準來編譯程序。實際上如果指定了編譯選項/Ze編譯器就會自動定義這個宏,指定/Za則不會,而且/Ze是缺省選項。作者猜想Visual Studio的開發人員在build標準C++庫時很可能沒有指定/Za,導致newop.cpp中的operator new定義被無情拋棄。是他們的疏漏嗎?我看未必,大家可以試試用/Za選項去編譯那些標準庫文件,看看有多少編譯不通過。VC標準庫的實現用了很多微軟 擴展的語言特性,不指定/Za是情有可原的,我不明白的是newop.cpp的作者(好象是P.J. Plauger老人家)爲什麼會加上一個如此愚蠢的"#if !defined(_MSC_EXTENSIONS)",因爲實在看不出這個operator new定義與_MSC_EXTENSIONS有什麼衝突的地方。

  既然標準C++庫裏的newop.obj是個空殼,那我們就只好自己動手豐衣足食了。把newop.cpp和dbgint.h(都在VC98/crt/src目錄下)拷貝到test.cpp所在的目錄,並將newop.cpp中的

#include

  改成

#include "dbgint.h"

  然後用

cl /c /Za /D_CRTBLD newop.cpp

  編譯它。/D_CRTBLD定義了_CRTBLD宏,爲什麼這麼做呢?因爲dbgint.h屬於內部頭文件,VC不希望應用程序用到它,便在文件中埋伏了這麼一段:

#ifndef _CRTBLD
/*
* This is an internal C runtime header file. It is used when building
* the C runtimes only. It is not to be used as a public header file.
*/
#error ERROR: Use of C runtime library internal header file.
#endif /* _CRTBLD */

  可我們確確實實是想build標準庫(的一部分),所以只好強行突破這個限制了。然後編譯test.cpp:

cl /c /GX test.cpp

  最後進行鏈接:

link test.obj newop.obj

  這時再運行test.exe輸出的結果就是

new BigClass, threw a bad_alloc exception
new(nothrow) BigClass, returned a NULL pointer
new BigClass[1], threw a bad_alloc exception

  值得慶幸的是雖然VC6.0如此弱智,但VC7.1卻表現良好,原因是VC7.1的newop.cpp和newaop.cpp(數組版)取消了那個愚 的"#if !defined(_MSC_EXTENSIONS)",於是標準C++庫中的newop.obj和newaop.obj模塊都實實在在地有了相應代碼。 另外,nothrow版的定義也分別轉移到了newopnt.cpp和newaopnt.cpp中。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章