Debugging Techniques
在这里, 我们提供一些有用的建议帮助你调试基于 Qt 的软件.
Configuring Qt for Debugging
编译Qt时, 配置 configuring, 可以构建调试符号, 以便更轻松地跟踪应用程序和库中的错误. 然而, 在某些平台上, 构建 Qt 的 debug 模式, 会导致应用程序更大.
Debugging in macOS and Xcode
Debugging With/Without Frameworks
你需要了解的有关调试库和框架的基本内容可以参见 Apple Technical Note 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 生成 debug 配置的 Makefile 时, 你将链接到库的 _debug 版本, 并为应用程序生成调试符号. 在 GDB 中运行该程序将像在其他平台上运行 GDB 一样, 并且你将能够在 Qt 内部进行跟踪.
Command Line Options Recognized by Qt
运行 Qt 应用程序时, 你可以指定多个有助于调试的命令行选项. 这些选项会被 QApplication 识别.
Option | Description |
---|---|
-nograb | 应用程序应该从不抓取 鼠标 或 键盘. 当应用程序在 Linux 上的 gdb 调试时, 默认设置这个选项. |
-dograb | 忽略任何隐式或显式的 -nograb . 即使 -nograb 是命令行的最后一个选项, -dograb 也优先于 -nograb . |
Environment Variables Recognized by Qt
在运行时, Qt 应用程序会识别许多环境变量, 其中一些有助于调试:
Variable | Description |
---|---|
QT_DEBUG_PLUGINS | 设置为非0值, Qt 会尝试打印加载的每个 (C++) 插件的诊断信息. |
QML_IMPORT_TRACE | 设置为非0值, QML 会从导入加载机制中打印诊断信息. |
QT_HASH_SEED | 设置为整数值, 每个应用程序会禁用 QHash 和 QSet 使用新的随机排序因子, 这在某些情况下可能导致测试和调试变得困难. |
Warning and Debugging Messages
Qt 包含一些全局宏, 输出警告和调试的文本. 你可以使用它们实现如下意图:
- qDebug() 输出自定义调试信息.
- qInfo() 输出信息类消息.
- qWarning() 输出警告和可恢复错误.
- qCritical() 输出严重错误和系统错误信息.
- qFatal() 输出退出前致命错误信息.
如果你包含 <QtDebug> 头文件, 则 qDebug()
宏也可以作为输出流. 如:
qDebug() << "Widget" << widget << "at position" << widget->pos();
在 Unix/X11 和 macOS 上, Qt 的默认实现把这些宏输出到 stderr
. 在 Windows 上, 如果是控制台程序, 文本输出到控制台; 否则, 文本会输出到调试器.
默认情况下, 仅打印消息. 你可以设置 QT_MESSAGE_PATTERN
环境变量, 包含其他信息. 例如:
QT_MESSAGE_PATTERN="[%{type}] %{appname} (%{file}:%{line}) - %{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 比不适用更有用, 但是即使没有名称也很有用.
Providing Support for the qDebug() Stream Operator
你可以实现 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.
Debugging Macros
头文件 <QtGlobal>
包含一些调试宏和 #define
定义.
下面是3个重要的宏:
- Q_ASSERT(cond),
cond
是一个布尔表达式, 如果cond
为 false, 警告提示: "ASSERT: 'cond' in file xyz.cpp, line 234". - Q_ASSERT_X(cond, where, what),
cond
是一个布尔表达式,where
是定位,what
是信息, 如果cond
为 false, 警告提示: "ASSERT failure inwhere
: 'what
', file xyz.cpp, line 234". - Q_CHECK_PTR(ptr),
ptr
是一个指针. 如果ptr
为 0, 警告提示: "In file xyz.cpp, line 234: Out of memory".
这些宏可以检查程序错误, 如下所示:
char *alloc(int size) { Q_ASSERT(size > 0); char *ptr = new char[size]; Q_CHECK_PTR(ptr); return ptr; }
如果编译时定义 QT_NO_DEBUG, Q_ASSERT(), Q_ASSERT_X(), Q_CHECK_PTR() 不会生效. 因此, 这些宏不应有包含其他功能. 如下所示, 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 的调试版本.
Common Bugs
在此提及一个非常常见的错误: 如果你在头文件中包含 Q_OBJECT, 并运行 元对象编译器 (moc
), 但是忘记将 moc
生成的对象代码链接到可执行程序, 你将得到非常混乱的错误信息. 任何缺少 vtbl
, _vtbl
, __vtbl
或类似内容的链接错误都可能是此问题造成的.