调试技术
在此,我们提供一些有用的提示,以帮助您调试基于 Qt 的软件。
配置 Qt 以进行调试
当配置 Qt 以进行安装时,可以确保构建过程中包含调试符号,这可以使追踪应用程序和库中的错误变得更加容易。然而,在某些平台上,以调试模式构建 Qt 可能会使应用程序的体积比预期大。
macOS 和 Xcode 中的调试
带/不带框架的调试
关于调试库和框架的基本知识可以在 developer.apple.com 中找到: Apple 技术概述 TN2124。
当您构建 Qt 时,默认情况下将构建框架,在框架内您会发现一个发布版本和一个调试版本(例如,QtCore 和 QtCore_debug)。如果您在构建 Qt 时传递 -no-framework 标志,则对于每个 Qt 库将构建两个动态库(例如,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 | 应用程序不应抓取 鼠标 或 键盘。在 Linux 下,当程序在 gdb 调试器中运行时,此选项默认设置。 |
-dograb | 忽略任何隐式或显式的 -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 的输出对用户不可见。IDE通常会重定向并显示输出,但在从命令行运行应用程序时,调试输出会丢失。为了获取输出,将此环境变量设置为new 以便应用程序分配新的控制台,或设置为attach 以便应用程序尝试连接到父进程的控制台。 |
警告和调试信息
Qt包含全局C++宏,用于写入警告和调试文本。原始宏使用默认的日志类别;分类的日志宏允许您指定类别。您可以使用它们进行以下目的
原始宏 | 分类宏 | 用途 |
---|---|---|
qDebug() | qCDebug() | 用于编写自定义的调试输出 |
qInfo() | qCInfo() | 用于信息性消息 |
qWarning() | qCWarning() | 用于报告应用程序或库中的警告和可恢复的错误 |
qCritical() | qCCritical() | 用于写入关键错误消息并报告系统错误 |
qFatal() | - | 用于在退出前编写有关致命错误的字符串 |
如果您包含<QtDebug>
头文件,则qDebug()
宏还可以用作输出流。例如
qDebug() << "Widget" << widget << "at position" << widget->pos();
这些宏的实现将打印到Unix/X11和macOS下的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()非常有用。如果您使用对象名称,则更有效,但不使用名称时通常也很有用。
在QML中,dumpItemTree()起到相同的作用。
为qDebug()流运算符提供支持
您可以将qDebug()所用的流运算符实现,为您的类提供调试支持。实现流的类是QDebug
。使用QDebugStateSaver
来暂时保存流的格式化选项。使用nospace()和QTextStream操作符进一步自定义格式。
以下是一个表示二维坐标的类的示例。
QDebug operator<<(QDebug dbg, const Coordinate &c) { QDebugStateSaver saver(dbg); dbg.nospace() << "(" << c.x() << ", " << c.y() << ")"; return dbg; }
自定义类型与Qt的元对象系统的集成在创建自定义Qt类型文档中有更详细的介绍。
调试宏
头文件<QtGlobal>
包含一些调试宏和#define
。
三个重要的宏是
- Q_ASSERT(cond),其中
cond
是一个布尔表达式,如果cond
为假,则写入警告“ASSERT: ‘cond’ 在文件 xyz.cpp,行 234”并退出。 - Q_ASSERT_X(cond, where, what),其中
cond
是一个布尔表达式,where
是一个位置,what
是一个信息,如果cond
为假,则写入警告:“在where
处断言失败:‘what
’,文件 xyz.cpp,行 234”并退出。 - Q_CHECK_PTR(ptr),其中
ptr
是一个指针。如果ptr
为0,则写入警告“在文件 xyz.cpp,行 234:内存不足”并退出。
这些宏对于检测程序错误很有用,例如像这样
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的调试版本。
常见错误
有一种错误非常常见,值得在此提及:如果您在类声明中包含了Q_OBJECT宏并运行元对象编译器(moc
),但是忘记将moc
生成的目标代码链接到您的可执行文件,您将收到非常混乱的错误信息。任何关于缺少vtbl
、_vtbl
、__vtbl
或类似内容的链接错误可能是此问题造成的。
© 2024 Qt公司有限公司。本文件中包含的文档贡献是各自所有者版权的归属。本提供在此的文档根据由自由软件基金会发布的GNU自由文档许可版本1.3的条款进行许可。Qt及其相关标志是芬兰和/或世界上其他国家的Qt公司有限的商标。所有其他商标均为各自所有者的财产。