首 页 | 新 闻 | 技术中心 | 第二书店 | 《程序员》 | 《开发高手》 | 社 区 | 黄 页 | 人 才
移 动专 题SUNIBM微 软微 创精 华Donews人 邮
我的技术中心 
我的分类 我的文档
全部文章 发表文章
专栏管理 使用说明



 RSS 订阅 
最新文档列表
Windows/.NET
.NET  (rss)    
Visual C++  (rss)    
Delphi  (rss)    
Visual Basic  (rss)    
ASP  (rss)    
JavaScript  (rss)    
Java/Linux
Java  (rss)    
Perl  (rss)    
综合
其他开发语言  (rss)    
文件格式  (rss)    
企业开发
游戏开发  (rss)    
网站制作技术  (rss)    
数据库
数据库开发  (rss)    
软件工程
其他  (rss)    

积极原创作者 
tellmenow (22)
cutemouse (22)
softj (78)
iiprogram (69)
qdzx2008 (50)
goodboy1881 (14)
wangchinaking (58)
fancyhf (1)
harrymeng (41)
yjz0065 (113)
CSDN - 文档中心 - Visual C++ 阅读:4574   评论: 4    参与评论
标题   GDI+编程中的一条错误信息及其原因分析     选择自 mynote 的 Blog
关键字   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 newnew 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 newoperator 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.

相关文章
对该文的评论
CSDN 网友 ( 2005-10-10)
mslk 高明!!
CSDN 网友 ( 2005-04-08)
把mfc的debug_new删除就可以了.
squallviii ( 2005-04-05)
以前遇到过这个问题,当时没有时间弄,现在清楚了!
mfc的宏定义好麻烦,需要自己在大脑中递归很多次,完全是思维考验。
现在的FrameWork都已经屏蔽这些底层对开发的影响了。
mslk ( 2005-04-03)
不用这么复杂的解决这个问题,new Bitmap(...)改为 ::new 就可以了