| 導購 | 订阅 | 在线投稿
分享
 
 
 

GDI+編程中的一條錯誤信息及其原因分析

來源:互聯網網民  2006-12-17 07:32:47  評論

GDI+編程中的一條錯誤信息及其原因分析

GDI+編程中的一條錯誤信息及其原因分析 公司不讓用盜版,遂准備逐一將各軟件要麽換成開源的,要麽就自己寫,看了看,就數Acdsee最簡單了(有些高級功能根本用不著),行,從這個入手吧。

需求分析:基本的圖片查看功能,圖片格式轉換功能,基本的圖形變換功能。

技術可行性分析:MS提供的GDI+已經提供了比較專業的圖形顯示、格式轉換功能,而且簡單易用。

....

OK,就緒,開始幹吧。

但是在程序編寫的過程中,有條錯誤信息讓我很不解。程序中有如下語句:

bmPhoto = new Bitmap( THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT, PixelFormat24bppRGB );

每次DEBUG編譯的時候總是報告如下的錯誤:

error C2660: 'new' : function does not take 3 parameters

開始以爲是Bitmap的構造函數的問題,但是查了一下,Bitmap明明有個構造函數:

Bitmap(IN INT width,

IN INT height,

IN PixelFormat format = PixelFormat32bppARGB);

那會是什麽問題呢?上網討論了一下,最終將問題鎖定在MFC程序中的這樣一個宏定義上:

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

這幾行從來都不會引起我們注意的代碼有什麽問題呢?爲什麽會使得我們的代碼報告如上所述的編譯錯誤呢?

讓我們來看看DEBUG_NEW的定義(在afx.h中):

#if defined(_DEBUG) && !defined(_AFX_NO_DEBUG_CRT)

// Memory tracking allocation

void* AFX_CDECL operator new(size_t nSize, LPCSTR lpszFileName, int nLine);

#define DEBUG_NEW new(THIS_FILE, __LINE__)

#if _MSC_VER >= 1200

void AFX_CDECL operator delete(void* p, LPCSTR lpszFileName, int nLine);

#endif

看到這裏你可能會想,new被define成了DEBUG_NEW,而後者又被define成了new(...),這不是成了個循環?非也。由于afx.h早于任何其它頭文件被包含(stdafx.h包含afxwin.h,afxwin.h又包含了afx.h,而MFC要求我們在任何有效代碼之前包含stdafx.h,當然,這不是必須的),所以DEBUG_NEW的定義早于後面的#define new DEBUG_NEW,也就是說這個define只對後面的代碼有效,對前面已經include了的afx.h中的代碼是無效的。

上面只是題外話,現在回到正題。

MFC重載operator new,是爲了方便定位內存泄漏,重載後的operator new會記錄下所分配的每塊內存對應的__FILE__和__LINE__信息。一般來講,標准的operator new的聲明如下:

void *__cdecl operator new(size_t);

即它只有一個參數,只接收一個size信息。我們的如下代碼

int* pi = new int; // the same as int* pi = new int(); or int* pi = new int[1];

等價于

int* tpi = (int*)operator new(sizeof(int)); // attention: this line cannot pass compilation if you have define DEBUG_NEW

int* pi = tpi;

同理,定義DEBUG_NEW前,文章開頭報錯的這條語句:

Bitmap* bmPhoto = new Bitmap( THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT, PixelFormat24bppRGB );

等價于

Bitmap* tbmPhoto = (Bitmap*)operator new(sizeof(Bitmap));

tbmPhoto->Bitmap( THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT, PixelFormat24bppRGB ); // initialize variable

Bitmap* bmPhoto = tbmPhoto;

但是現在,由于DEBUG_NEW使用的是被重載的operator new:

void* AFX_CDECL operator new(size_t nSize, LPCSTR lpszFileName, int nLine);

上述代碼等價于:

Bitmap* tbmPhoto = (Bitmap*)operator new(sizeof(Bitmap), __FILE__, __LINE__);

tbmPhoto->BitmapBitmap( THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT, PixelFormat24bppRGB ); // initialize variable

Bitmap* bmPhoto = tbmPhoto;

回過頭來看gdiplus.h中的operator new的聲明(在GdiplusBase.h中):

class GdiplusBase

{

public:

void (operator delete)(void* in_pVoid)

{

DllExports::GdipFree(in_pVoid);

}

void* (operator new)(size_t in_size)

{

return DllExports::GdipAlloc(in_size);

}

void (operator delete[])(void* in_pVoid)

{

DllExports::GdipFree(in_pVoid);

}

void* (operator new[])(size_t in_size)

{

return DllExports::GdipAlloc(in_size);

}

};

它重載了operator new,並且沒有提供一個可以容納3個參數的operator new,同時基于這樣一個事實:

不同命名域(指全局命名空間與有名命名空間之間,父類與子類,全局與類內部)內進行重載時,下一級的命名空間會覆蓋掉上一級的定義,除非顯示調用上一級的定義。

因此,全局的重新定義的operator new並不能用于Bitmap類。也正因爲這一原因,編譯器會報告:

Bitmap* tbmPhoto = (Bitmap*)Bitmap::operator new(sizeof(Bitmap), __FILE__, __LINE__);

error C2660: 'new' : function does not take 3 parameters

知道了這一點,要修正這一問題,只需給class GdiplusBase多重載幾個operator new即可。修正後的class GdiplusBase如下:

#ifdef _DEBUG

namespace Gdiplus

{

namespace DllExports

{

#include <GdiplusMem.h>

};

#ifndef _GDIPLUSBASE_H

#define _GDIPLUSBASE_H

class GdiplusBase

{

public:

void (operator delete)(void* in_pVoid)

{

DllExports::GdipFree(in_pVoid);

}

void* (operator new)(size_t in_size)

{

return DllExports::GdipAlloc(in_size);

}

void (operator delete[])(void* in_pVoid)

{

DllExports::GdipFree(in_pVoid);

}

void* (operator new[])(size_t in_size)

{

return DllExports::GdipAlloc(in_size);

}

void * (operator new)(size_t nSize, LPCSTR lpszFileName, int nLine)

{

return DllExports::GdipAlloc(nSize);

}

void operator delete(void* p, LPCSTR lpszFileName, int nLine)

{

DllExports::GdipFree(p);

}

};

#endif // #ifndef _GDIPLUSBASE_H

}

#endif // #ifdef _DEBUG

OK,問題已解決,其實這只是個重載operator new的問題,但這個問題由于DEBUG_NEW這個不起眼的宏,倒還真變得有點複雜。

最後總結一下,在進行operator new重載時應注意:

1.new operator是不可以重載的,可以重載的是operator new。new operator 首先調用 operator new,然後調用構造函數(如果有的話)。new operator的這個行爲是不可以重載的,可以重載的僅僅是operator new,也就是內存分配。

2.重載operator new是一件必須十分小心的事情,在編寫MFC程序或者你所編寫的系統重載了全局的operator new時,尤其需要注意,同時應注意所有的#include頭文件最好添加在所有define之前,以免造成受到後續對new的重定義的影響。你可以嘗試在你的MFC程序的#define new DEBUG_NEW一句之後,添加#include <vector>,你會收到一大堆莫名奇妙的錯誤提示(DEBUG編譯時才有),這正是由于#define new DEBUG_NEW和後面的static char THIS_FILE[] = __FILE__;造成的影響。

3.operator new/delete在性質上類似于靜態函數,你可以直接通過類名來訪問它們。

4.理解了operator new的基本概念,要理解頭文件NEW中的placement new/delete的實現也就不是什麽難事了,頭文件NEW中的placement new/delete的實現如下:

#ifndef __PLACEMENT_NEW_INLINE

#define __PLACEMENT_NEW_INLINE

inline void *__cdecl operator new(size_t, void *_P)

{return (_P); }

#if _MSC_VER >= 1200

inline void __cdecl operator delete(void *, void *)

{return; }

#endif

#endif

附:

(轉貼)C++的各種new簡介

1.new T

第一種new最簡單,調用類的(如果重載了的話)或者全局的operator new分配空間,然後用類型後面列的參數來調用構造函數,用法是

new TypeName(initial_args_list).

如果沒有參數,括號一般可以省略.例如

int *p=new int;

int *p=new int(10);

int *p=new foo('hello');

通過調用delete來銷毀:

delete p;

2. new T[]

這種new用來創建一個動態的對象數組,他會調用對象的operator new[]來分配內存(如果沒有則調用operator new,搜索順序同上),然後調用對象的31m默認構造函數初始化每個對象用法:

new TypeName[num_of_objects];

例如

int *p= new int[10];

銷毀時使用operator delete31m[]

3.new()T 和new() T[]

這是個帶參數的new,這種形式的new會調用operator new(size_t,OtherType)來分配內存,這裏的OtherType要和new括號裏的參數的類型兼容,這種語法通常用來在某個特定的地址構件對象,稱爲placement new,前提是operator new(size_t,void*)已經定義,通常編譯器已經提供了一個實現,包含<new>頭文件即可,這個實現只是簡單的把參數的指定的地址返回,因而new()運算符就會在括號裏的地址上創建對象.

需要說明的是,第二個參數不是一定要是void*,可以識別的合法類型,這時候由C++的重載機制來決定調用那個operator new.

當然,我們可以提供自己的operator new(size_,Type),來決定new的行爲,比如

char data[1000][sizeof(foo)];

inline void* operator new(size_t ,int n)

{

return data[n];

}

就可以使用這樣有趣的語法來創建對象:

foo *p=new(6) foo(); //把對象創建在data的第六個單元上的確很有意思

標准庫還提供了一個nothrow的實現:

void* operator new(std::size_t, const std::nothrow_t&) throw();

void* operator new[](std::size_t, const std::nothrow_t&) throw();

就可以實現調用new失敗時不抛出異常

new(nothrow) int(10);

// nothrow 是std::nothrow_t的一個實例

placement new 創建的對象不能直接delete來銷毀,而是要調用對象的析夠函數來銷毀對象,至于對象所占的內存如何處理,要看這塊內存的具體來源.

4. operator new(size_t)

這個的運算符分配參數指定大小的內存並返回首地址,可以爲自定義的類重載這個運算符,方法就是在類裏面聲明加上

void *operator new(size_t size)

{

//在這裏分配內存並返回其地址

}

無論是否聲明,類裏面重載的各種operator new和operator delete都是具有static屬性的.

一般不需要直接調用operator new,除非直接分配原始內存(這一點類似于C的malloc),在沖突的情況下要調用全局的operator加上::作用域運算符:

::operator new(1000); // 分配1000個31m字節

返回的內存需要回收的話,調用對應的operator delete

5.operator new[](size_t)

這個也是分配內存,,只不過是專門針對數組,也就是new T[]這種形式,當然,需要時可以顯式調用

6.operator new(size_t size, OtherType other_value)

和operator new[](size_t size, OtherType other_value)

參見上面的new()

需要強調的是,new用來創建對象並分配內存,它的行爲是不可改變的,可以改變的是各種operator new,我們就可以通過重載operator new來實現我們的內存分配方案.

參考資料:

1.PRB: Microsoft Foundation Classes DEBUG_NEW Does Not Work with GDI+. http://support.microsoft.com/default.aspx?scid=kb;en-us;317799

2.VC++6.0中內存泄漏檢測. http://blog.vckbase.com/bruceteen/archive/2004/10/28/1130.aspx

3.More Effective C++. Item 8: Understand the different meanings of new and delete.

 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
 
GDI+編程中的一條錯誤信息及其原因分析 GDI+編程中的一條錯誤信息及其原因分析 公司不讓用盜版,遂准備逐一將各軟件要麽換成開源的,要麽就自己寫,看了看,就數Acdsee最簡單了(有些高級功能根本用不著),行,從這個入手吧。 需求分析:基本的圖片查看功能,圖片格式轉換功能,基本的圖形變換功能。 技術可行性分析:MS提供的GDI+已經提供了比較專業的圖形顯示、格式轉換功能,而且簡單易用。 .... OK,就緒,開始幹吧。 但是在程序編寫的過程中,有條錯誤信息讓我很不解。程序中有如下語句: bmPhoto = new Bitmap( THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT, PixelFormat24bppRGB ); 每次DEBUG編譯的時候總是報告如下的錯誤: error C2660: 'new' : function does not take 3 parameters 開始以爲是Bitmap的構造函數的問題,但是查了一下,Bitmap明明有個構造函數: Bitmap(IN INT width, IN INT height, IN PixelFormat format = PixelFormat32bppARGB); 那會是什麽問題呢?上網討論了一下,最終將問題鎖定在MFC程序中的這樣一個宏定義上: #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif 這幾行從來都不會引起我們注意的代碼有什麽問題呢?爲什麽會使得我們的代碼報告如上所述的編譯錯誤呢? 讓我們來看看DEBUG_NEW的定義(在afx.h中): #if defined(_DEBUG) && !defined(_AFX_NO_DEBUG_CRT) // Memory tracking allocation void* AFX_CDECL operator new(size_t nSize, LPCSTR lpszFileName, int nLine); #define DEBUG_NEW new(THIS_FILE, __LINE__) #if _MSC_VER >= 1200 void AFX_CDECL operator delete(void* p, LPCSTR lpszFileName, int nLine); #endif 看到這裏你可能會想,new被define成了DEBUG_NEW,而後者又被define成了new(...),這不是成了個循環?非也。由于afx.h早于任何其它頭文件被包含(stdafx.h包含afxwin.h,afxwin.h又包含了afx.h,而MFC要求我們在任何有效代碼之前包含stdafx.h,當然,這不是必須的),所以DEBUG_NEW的定義早于後面的#define new DEBUG_NEW,也就是說這個define只對後面的代碼有效,對前面已經include了的afx.h中的代碼是無效的。 上面只是題外話,現在回到正題。 MFC重載operator new,是爲了方便定位內存泄漏,重載後的operator new會記錄下所分配的每塊內存對應的__FILE__和__LINE__信息。一般來講,標准的operator new的聲明如下: void *__cdecl operator new(size_t); 即它只有一個參數,只接收一個size信息。我們的如下代碼 int* pi = new int; // the same as int* pi = new int(); or int* pi = new int[1]; 等價于 int* tpi = (int*)operator new(sizeof(int)); // attention: this line cannot pass compilation if you have define DEBUG_NEW int* pi = tpi; 同理,定義DEBUG_NEW前,文章開頭報錯的這條語句: Bitmap* bmPhoto = new Bitmap( THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT, PixelFormat24bppRGB ); 等價于 Bitmap* tbmPhoto = (Bitmap*)operator new(sizeof(Bitmap)); tbmPhoto->Bitmap( THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT, PixelFormat24bppRGB ); // initialize variable Bitmap* bmPhoto = tbmPhoto; 但是現在,由于DEBUG_NEW使用的是被重載的operator new: void* AFX_CDECL operator new(size_t nSize, LPCSTR lpszFileName, int nLine); 上述代碼等價于: Bitmap* tbmPhoto = (Bitmap*)operator new(sizeof(Bitmap), __FILE__, __LINE__); tbmPhoto->BitmapBitmap( THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT, PixelFormat24bppRGB ); // initialize variable Bitmap* bmPhoto = tbmPhoto; 回過頭來看gdiplus.h中的operator new的聲明(在GdiplusBase.h中): class GdiplusBase { public: void (operator delete)(void* in_pVoid) { DllExports::GdipFree(in_pVoid); } void* (operator new)(size_t in_size) { return DllExports::GdipAlloc(in_size); } void (operator delete[])(void* in_pVoid) { DllExports::GdipFree(in_pVoid); } void* (operator new[])(size_t in_size) { return DllExports::GdipAlloc(in_size); } }; 它重載了operator new,並且沒有提供一個可以容納3個參數的operator new,同時基于這樣一個事實: 不同命名域(指全局命名空間與有名命名空間之間,父類與子類,全局與類內部)內進行重載時,下一級的命名空間會覆蓋掉上一級的定義,除非顯示調用上一級的定義。 因此,全局的重新定義的operator new並不能用于Bitmap類。也正因爲這一原因,編譯器會報告: Bitmap* tbmPhoto = (Bitmap*)Bitmap::operator new(sizeof(Bitmap), __FILE__, __LINE__); error C2660: 'new' : function does not take 3 parameters 知道了這一點,要修正這一問題,只需給class GdiplusBase多重載幾個operator new即可。修正後的class GdiplusBase如下: #ifdef _DEBUG namespace Gdiplus { namespace DllExports { #include <GdiplusMem.h> }; #ifndef _GDIPLUSBASE_H #define _GDIPLUSBASE_H class GdiplusBase { public: void (operator delete)(void* in_pVoid) { DllExports::GdipFree(in_pVoid); } void* (operator new)(size_t in_size) { return DllExports::GdipAlloc(in_size); } void (operator delete[])(void* in_pVoid) { DllExports::GdipFree(in_pVoid); } void* (operator new[])(size_t in_size) { return DllExports::GdipAlloc(in_size); } void * (operator new)(size_t nSize, LPCSTR lpszFileName, int nLine) { return DllExports::GdipAlloc(nSize); } void operator delete(void* p, LPCSTR lpszFileName, int nLine) { DllExports::GdipFree(p); } }; #endif // #ifndef _GDIPLUSBASE_H } #endif // #ifdef _DEBUG OK,問題已解決,其實這只是個重載operator new的問題,但這個問題由于DEBUG_NEW這個不起眼的宏,倒還真變得有點複雜。 最後總結一下,在進行operator new重載時應注意: 1.new operator是不可以重載的,可以重載的是operator new。new operator 首先調用 operator new,然後調用構造函數(如果有的話)。new operator的這個行爲是不可以重載的,可以重載的僅僅是operator new,也就是內存分配。 2.重載operator new是一件必須十分小心的事情,在編寫MFC程序或者你所編寫的系統重載了全局的operator new時,尤其需要注意,同時應注意所有的#include頭文件最好添加在所有define之前,以免造成受到後續對new的重定義的影響。你可以嘗試在你的MFC程序的#define new DEBUG_NEW一句之後,添加#include <vector>,你會收到一大堆莫名奇妙的錯誤提示(DEBUG編譯時才有),這正是由于#define new DEBUG_NEW和後面的static char THIS_FILE[] = __FILE__;造成的影響。 3.operator new/delete在性質上類似于靜態函數,你可以直接通過類名來訪問它們。 4.理解了operator new的基本概念,要理解頭文件NEW中的placement new/delete的實現也就不是什麽難事了,頭文件NEW中的placement new/delete的實現如下: #ifndef __PLACEMENT_NEW_INLINE #define __PLACEMENT_NEW_INLINE inline void *__cdecl operator new(size_t, void *_P) {return (_P); } #if _MSC_VER >= 1200 inline void __cdecl operator delete(void *, void *) {return; } #endif #endif 附: (轉貼)C++的各種new簡介 1.new T 第一種new最簡單,調用類的(如果重載了的話)或者全局的operator new分配空間,然後用類型後面列的參數來調用構造函數,用法是 new TypeName(initial_args_list). 如果沒有參數,括號一般可以省略.例如 int *p=new int; int *p=new int(10); int *p=new foo('hello'); 通過調用delete來銷毀: delete p; 2. new T[] 這種new用來創建一個動態的對象數組,他會調用對象的operator new[]來分配內存(如果沒有則調用operator new,搜索順序同上),然後調用對象的31m默認構造函數初始化每個對象用法: new TypeName[num_of_objects]; 例如 int *p= new int[10]; 銷毀時使用operator delete31m[] 3.new()T 和new() T[] 這是個帶參數的new,這種形式的new會調用operator new(size_t,OtherType)來分配內存,這裏的OtherType要和new括號裏的參數的類型兼容,這種語法通常用來在某個特定的地址構件對象,稱爲placement new,前提是operator new(size_t,void*)已經定義,通常編譯器已經提供了一個實現,包含<new>頭文件即可,這個實現只是簡單的把參數的指定的地址返回,因而new()運算符就會在括號裏的地址上創建對象. 需要說明的是,第二個參數不是一定要是void*,可以識別的合法類型,這時候由C++的重載機制來決定調用那個operator new. 當然,我們可以提供自己的operator new(size_,Type),來決定new的行爲,比如 char data[1000][sizeof(foo)]; inline void* operator new(size_t ,int n) { return data[n]; } 就可以使用這樣有趣的語法來創建對象: foo *p=new(6) foo(); //把對象創建在data的第六個單元上的確很有意思 標准庫還提供了一個nothrow的實現: void* operator new(std::size_t, const std::nothrow_t&) throw(); void* operator new[](std::size_t, const std::nothrow_t&) throw(); 就可以實現調用new失敗時不抛出異常 new(nothrow) int(10); // nothrow 是std::nothrow_t的一個實例 placement new 創建的對象不能直接delete來銷毀,而是要調用對象的析夠函數來銷毀對象,至于對象所占的內存如何處理,要看這塊內存的具體來源. 4. operator new(size_t) 這個的運算符分配參數指定大小的內存並返回首地址,可以爲自定義的類重載這個運算符,方法就是在類裏面聲明加上 void *operator new(size_t size) { //在這裏分配內存並返回其地址 } 無論是否聲明,類裏面重載的各種operator new和operator delete都是具有static屬性的. 一般不需要直接調用operator new,除非直接分配原始內存(這一點類似于C的malloc),在沖突的情況下要調用全局的operator加上::作用域運算符: ::operator new(1000); // 分配1000個31m字節 返回的內存需要回收的話,調用對應的operator delete 5.operator new[](size_t) 這個也是分配內存,,只不過是專門針對數組,也就是new T[]這種形式,當然,需要時可以顯式調用 6.operator new(size_t size, OtherType other_value) 和operator new[](size_t size, OtherType other_value) 參見上面的new() 需要強調的是,new用來創建對象並分配內存,它的行爲是不可改變的,可以改變的是各種operator new,我們就可以通過重載operator new來實現我們的內存分配方案. 參考資料: 1.PRB: Microsoft Foundation Classes DEBUG_NEW Does Not Work with GDI+. http://support.microsoft.com/default.aspx?scid=kb;en-us;317799 2.VC++6.0中內存泄漏檢測. http://blog.vckbase.com/bruceteen/archive/2004/10/28/1130.aspx 3.More Effective C++. Item 8: Understand the different meanings of new and delete.
󰈣󰈤
王朝萬家燈火計劃
期待原創作者加盟
 
 
 
>>返回首頁<<
 
 
 
 
 
 熱帖排行
 
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有