| 订阅 | 在线投稿
分享
 
 
 

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- 王朝网络 版权所有