程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> vc教程 >> 在VC6.0中如何讓new操作失敗後拋出異常?

在VC6.0中如何讓new操作失敗後拋出異常?

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

    VC6.0在<new>頭文件中聲明了這兩種Operator new操作符:

void *__cdecl Operator new(size_t) _THROW1(std::bad_alloc);
void *__cdecl Operator new(size_t, const std::nothrow_t&) _THROW0();

  並分別定義在newop.cpp和newop2.cpp中。而_THROW0和_THROW1則是兩個宏,在Include目錄的xstddef文件中定義:

#define _THROW0()   throw ()
#define _THROW1(x)  throw (x)

  newop.cpp和newop2.cpp對應的目標模塊被打包進標准C++庫中。標准C++庫有若干個版本: libcp.lib(單線程靜態版)、libcpd.lib(單線程靜態調試版)、libcpmt.lib(多線程靜態版)、libcpmtd.lib(多線程靜態調試版)、msvcprt.lib(多線程動態版的導入庫),msvcprtd.lib(多線程動態調試版的導入庫),這些庫與相應版本的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 <new>
#include <iOStream>
     
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(都在VC98crtsrc目錄下)拷貝到test.cpp所在的目錄,並將newop.cpp中的

#include <dbgint.h>

改成

#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中。

    後記: 作者在2001年便碰到過這個問題,百思不得其解,於是在CSDN論壇上發問,也不見答復。從此便擱置一旁,直到最近因探究LNK2005鏈接錯誤而徹底弄清楚VC鏈接器解析符號的規則後,才意識到二者或有聯系。於是重拾舊疑,順籐而上,果然問題就迎刃而解。此題雖小,功夫卻做足,最後總算水落石出,解除了4年的積惑。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved