我们在此介绍一些有用的提示,以帮助您调试基于 Qt 的软件。
为调试配置 Qt
在配置安装 Qt 时,可以确保在构建 Qt 时包含调试符号,这样可以更轻松地跟踪应用程序和库中的错误。不过,在某些平台上,以调试模式构建 Qt 会导致应用程序的大小超出预期。
在 macOS 和 Xcode 中调试
使用/不使用框架调试
您需要了解的有关调试库和框架的基本知识可在 developer.apple.com 中找到:Apple 技术说明 TN2124。
当你构建 Qt 时,默认情况下会构建框架,在框架内你会找到发布和调试版本(例如QtCore 和 QtCore_debug)。如果在联编 Qt 时传递-no-framework 标志,则会为每个 Qt 库联编两个 dylib(例如,libQtCore.4.dylib 和 libQtCore_debug.4.dylib)。
链接时会发生什么取决于是否使用框架。我们看不出有什么令人信服的理由推荐使用其中一种。
使用框架
由于发布库和调试库都在框架内,因此只需针对框架链接应用程序即可。然后,当你在调试器中运行时,你将得到发布版本或调试版本,这取决于你是否设置了DYLD_IMAGE_SUFFIX 。如果不设置,默认情况下得到的是发布版本(即非 _debug)。如果设置了DYLD_IMAGE_SUFFIX=_debug ,则会得到调试版本。
不带框架:
当您让qmake生成带有调试配置的 Makefile 时,它会与 _debug 版本的库链接,并为应用程序生成调试符号。然后,在 GDB 中运行此程序,就像在其他平台上运行 GDB 一样,你就可以在 Qt 内部进行跟踪了。
Qt 可识别的命令行选项
运行 Qt 应用程序时,可以指定几个有助于调试的命令行选项。QApplication 可识别这些选项。
选项说明
-nograb应用程序不应抓取the mouse 或the keyboard 。当程序在 Linux 下的gdb 调试器中运行时,默认设置为该选项。
-dograb忽略任何隐式或显式-nograb 。即使-nograb 在命令行中排在最后,-dograb 也会优先于-nograb 。
Qt 可识别的环境变量
运行时,Qt 应用程序会识别许多环境变量,其中一些变量对调试很有帮助:
变量说明
QT_DEBUG_PLUGINS设置为非零值可使 Qt 打印出它试图加载的每个 (C++) 插件的诊断信息。
QML_IMPORT_TRACE设置为非零值可使 QML 从导入加载机制中打印出诊断信息。
QT_HASH_SEED设置为整数值可禁用QHash 和QSet ,每次运行应用程序时使用新的随机排序,这在某些情况下可能会给测试和调试带来困难。
QT_WIN_DEBUG_CONSOLE在 Windows 中,GUI 应用程序不连接控制台,因此写入stdout 和stderr 的输出对用户来说是不可见的。集成开发环境通常会重定向并显示输出,但从命令行运行应用程序时,调试输出会丢失。要访问输出,可将此环境变量设置为new ,使应用程序分配一个新的控制台,或设置为attach ,使应用程序尝试附加到父进程的控制台。
警告和调试信息
Qt 包含用于写出警告和调试文本的全局 C++ 宏。普通宏使用默认的logging category ;分类日志宏允许您指定类别。您可以将它们用于以下目的:
普通宏分类宏目的
qDebug()qCDebug()用于编写自定义调试输出
qInfo()qCInfo()用于输入信息
qWarning()qCWarning()用于报告应用程序或库中的警告和可恢复错误
qCritical()qCCritical()用于编写关键错误信息和报告系统错误
qFatal()-用于在退出前写入致命错误信息
如果包含
qDebug() << "Widget" << widget << "at position" << widget->pos();
在 Unix/X11 和 macOS 下,这些宏的 Qt 实现会打印到stderr 输出。在 Windows 下,如果是控制台应用程序,文本将发送到控制台;否则,将发送到调试器。
默认情况下,只打印消息。通过设置QT_MESSAGE_PATTERN 环境变量,可以包含更多信息。例如
QT_MESSAGE_PATTERN="[%{time process} %{type}] %{appname} %{category} %{function} - %{message}"
格式记录在qSetMessagePattern() 中。您也可以使用qInstallMessageHandler() 安装自己的消息处理程序。
如果设置了QT_FATAL_WARNINGS 环境变量,qWarning() 会在打印警告信息后退出。这样就可以很容易地在调试器中获得回溯信息。
qDebug()、qInfo() 和qWarning() 是调试工具。在编译过程中,可以通过定义QT_NO_DEBUG_OUTPUT 、QT_NO_INFO_OUTPUT 或QT_NO_WARNING_OUTPUT 来编译它们。
当应用程序出现异常外观或行为时,调试函数QObject::dumpObjectTree() 和QObject::dumpObjectInfo() 通常很有用。如果使用object names 则比不使用更有用,但即使没有名称也经常有用。
在 QML 中,dumpItemTree() 也有同样的作用。
为 qDebug() 流操作符提供支持
你可以实现qDebug() 使用的流操作符,为你的类提供调试支持。实现流运算符的类是QDebug 。使用QDebugStateSaver 可临时保存流的格式化选项。使用nospace() 和QTextStream manipulators 可进一步自定义格式。
下面是一个表示 2D 坐标的类的示例。
QDebug operator<<(QDebug dbg, const Coordinate &c)
{
QDebugStateSaver saver(dbg);
dbg.nospace() << "(" << c.x() << ", " << c.y() << ")";
return dbg;
}
创建自定义 Qt 类型》(Creating Custom Qt Types)文档更深入地介绍了自定义类型与Qt元对象系统的集成。
调试宏
头文件
三个重要的宏是
Q_ASSERT(cond),其中cond 是布尔表达式,写入警告 "ASSERT:'cond' in file xyz.cpp, line 234",如果cond 为 false 则退出。
Q_ASSERT_X(cond,where,what),其中cond 是布尔表达式,where 是位置,what 是信息,写入警告:"ASSERT failure inwhere: 'what', file xyz.cpp, line 234" 如果cond 为 false,则退出。
Q_CHECK_PTR(ptr),其中ptr 是指针。写入警告 "在文件 xyz.cpp 第 234 行:超出内存 "的警告,如果ptr 为 0 则退出。
这些宏对于检测程序错误非常有用,例如像这样
char *alloc(int size)
{
Q_ASSERT(size > 0);
char *ptr = new char[size];
Q_CHECK_PTR(ptr);
return ptr;
}
Q_ASSERT()、Q_ASSERT_X() 和Q_CHECK_PTR() 在编译过程中如果定义了QT_NO_DEBUG ,则扩展为无。因此,这些宏的参数不应产生任何副作用。下面是Q_CHECK_PTR() 的一个错误用法:
char *alloc(int size)
{
char *ptr;
Q_CHECK_PTR(ptr = new char[size]); // WRONG
return ptr;
}
如果这段代码在编译时定义了QT_NO_DEBUG ,那么Q_CHECK_PTR() 表达式中的代码将不会执行,alloc将返回一个未初始化的指针。
Qt 库包含数以百计的内部检查,当检测到编程错误时会打印警告信息。因此,我们建议您在开发基于 Qt 的软件时使用调试版本的 Qt。
QML 中也可以记录日志和categorized logging 。
常见错误
有一个非常常见的错误值得在此一提:如果您在类声明中包含Q_OBJECT 宏,并运行Meta-Object Compiler(moc),但忘记将moc 生成的对象代码链接到可执行文件中,您将得到非常混乱的错误信息。任何抱怨缺少vtbl,_vtbl,__vtbl 或类似内容的链接错误都可能是这个问题造成的。