博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
GDI+编程中的一条错误信息及其原因分析
阅读量:3519 次
发布时间:2019-05-20

本文共 7363 字,大约阅读时间需要 24 分钟。

        公司不让用盗版,遂准备逐一将各软件要么换成开源的,要么就自己写,看了看,就数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
.

转载地址:http://qqxqj.baihongyu.com/

你可能感兴趣的文章
springBoot官方入门篇一 引入依赖并运行
查看>>
java大牛25点
查看>>
LSTM 讲解
查看>>
SpringBoot中启动的端口与设置的端口不一致
查看>>
Lua 元表及元方法
查看>>
C#常用的设计模式
查看>>
C#-快速排序算法
查看>>
docker 部署SpringBoot项目
查看>>
mybatis基础知识(四)&输入映射与输出映射
查看>>
gitflow工作流
查看>>
【MongoDB】update修改器($set、$unset、$inc、$push、$pull、$pop)
查看>>
JAVA 继承
查看>>
电脑键盘突然不能打字,很多键变成快捷键了
查看>>
Hbase表映射Hive表三种方法
查看>>
Java中获取List长度
查看>>
this关键字有什么用处?怎么用? 1.访问成员变量,区分成员变量和局部变量。 2.访问成员方法。 3.访问构造方法。 4.返回对当前对象的引用 5.将对当前对象的引用作为参数传递给其他方法。
查看>>
自学sql
查看>>
基于Springboot的社区开发项目
查看>>
nowcoder 左神算法1
查看>>
code刷题
查看>>