Qt debug模式和release模式的区别

这几天因为一个bug纠结了很久,始终找不到问题所在。今天终于揪出“凶手”了,特此记录“缉凶”过程,当找到这个bug的原因,连我都不敢相信这是个低级错误造成的问题。

一、现象

1、编写的myapp.exe程序,无论是在debug模式还是在release模式下编译都正常。在debug模式下运行正常,但在release模式下运行,UI启动后Windows就弹出程序错误的警告,之后程序挂掉。

2、即使在debug模式下运行正常,但会出现如下提示:

QPainter::begin: Painter already active
且对窗口resize时,也会出现程序错误的警告,然后程序也挂掉。

二、代码片段

1、PainterView类中声明一个QPainter的成员painter以及其他成员:

QPainter *painter;
int x;
int y;
2、在构造函数中对painter进行初始化:

painter = new QPainter;
3、重新实现的paintEvent()函数如下:
void PainterView::paintEvent(QPaintEvent *)
{
	painter->begin(this);
	……
  	int x_scale = RESOLUTION_X/x;   // 坐标映射
        int y_scale = RESOLUTION_Y/y;
        painter->drawLine(0, 0,  x_scale,  y_scale);  // 与原点的连线
        ……
        painter->end();
}

paintEvent()中实现绘制一个大的矩形窗口,并在该窗口中实时显示手指在下位机上面的运动轨迹并显示手指的坐标点与原点之间的距离。

三、 QPainter的提示

QPainter::begin: Painter already active
是说已经存在painter在活动,根据程序中,就只有在paintEvent()中对QPainter进行操作,也就把问题定位在paintEvent()中。查看QPainter的document中,给如如下信息:

QPainter::QPainter ()
构造绘制工具。
注意所有绘制工具的设置(setPen、setBrush等等)在当begin()被调用时会被重新设置为默认值。
请参考begin()和end()。

QPainter::QPainter ( const QPaintDevice * pd, bool unclipped = FALSE )

构造一个立即开始在绘制设备pd绘制的绘制工具。如果unclipped为真,依赖于底层图形系统,绘制工具将在绘制设备的子对象上绘制。
这个构造函数对短期的绘制工具是很方便的,例如,在一个绘制事件中并且也该被立即使用。构造函数为你调用begin()并且QPainter的析构函数会自动调用end()。

这是使用begin()和end()的实例:

        void MyWidget::paintEvent( QPaintEvent * )
        {
            QPainter p;
            p.begin( this );
            p.drawLine( ... );  // 绘制代码
            p.end();
        }
    
使用这个构造函数的相同实例:

        void MyWidget::paintEvent( QPaintEvent * )
        {
            QPainter p( this );
            p.drawLine( ... );  // 绘制代码
        }
    
请参考begin()和end()。

也就是说,在我们的PainterView的构造函数中定义了

painter= new QPainter;
并为其分配存储空间,那么painter就会自己调用begin,当在PainterView的析构函数中,释放painter的分配空间,它会自己调用end()函数,我们无需显式调用begin()和end().

如果要显式调用begin()和end(),那么就不要把painter作为PainterView的成员,而是作为paintEvent的局部变量出现。如上document做的那样。

四、myapp.exe为何在两种模式下表现不一致

1、根据现象2我们知道,在debug模式下myapp.exe虽然能够启动并工作,但一旦resize,那么就会导致程序挂掉,跟release模式下的现象一致。在代码中有接收到resize的event,在paintEvent()中实现,因此可以把问题定位到该函数中。

2、相信大家从代码片段中就可以看出代码中存在的两个问题:

第一:成员x和y没有做初始化;

第二:在paintEvent()中进行坐标映射的时候有进行除法运算,但是却没有对除数做0判断,因为除以0是不允许的。

3、debug和release模式下x和y默认的值:

在debug模式下,x和y默认的值如下:

x = 3014708;
y = 6029360;

在release模式下,x和y默认的值为:

x = 0;
y = 0;

从上面可以看出,debug和release模式下对于变量的初始化采取不同的态度。debug模式下不对成员变量进行初始化,而在release模式下会初始化为0.这也就能够解释为什么debug模式下可以用(但是坐标映射是错的)而release模式下程序会挂掉(因为程序中做了除0的操作)。

上述的区别可以用这里[whatto do if debug runs fine, but release crashes]找到,就是因为里面提到:

This kind of problem is often due to unitialized variables. I'd start there looking for your problem.
Debug mode is more forgiving because it is often configured to initialize variables that have not been explicitly initialized.
Perhaps you're deleting an unitialized pointer. In debug mode it works because pointer was nulled and delete ptr will be ok on NULL. 
On release it's some rubbish, then delete ptr will actually cause a problem.

4、debug模式和release模式的其他区别

这个区别可以用makefile上面看到,如图:


从上面可以看出,在release下的DEFINES多加了一个宏 -DQT_NO_DEBUG 来关闭debug信息,而且在CFLAGS和CXXFLAGS中编译选项使用-O2进行代码的优化。

五、总结

这个bug是没有养成良好的代码书写习惯导致的。首先一定要养成初始化变量的习惯,再然后对于有除操作的,应该先判断除数是否为0.



你可能感兴趣的:(Qt)