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 识别.

OptionDescription
-nograb应用程序应该从不抓取 鼠标键盘. 当应用程序在 Linux 上的 gdb 调试时, 默认设置这个选项.
-dograb忽略任何隐式或显式的 -nograb. 即使 -nograb 是命令行的最后一个选项, -dograb 也优先于 -nograb.

Environment Variables Recognized by Qt

在运行时, Qt 应用程序会识别许多环境变量, 其中一些有助于调试:

VariableDescription
QT_DEBUG_PLUGINS设置为非0值, Qt 会尝试打印加载的每个 (C++) 插件的诊断信息.
QML_IMPORT_TRACE设置为非0值, QML 会从导入加载机制中打印诊断信息.
QT_HASH_SEED设置为整数值, 每个应用程序会禁用 QHashQSet 使用新的随机排序因子, 这在某些情况下可能导致测试和调试变得困难.

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 in where: '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 或类似内容的链接错误都可能是此问题造成的.