3. Qt特有类型打印
调试Qt程序时,最头疼的是什么?我个人觉得,就是那些Qt特有的容器和字符串类型。你用print命令一看,满屏都是内部指针和引用计数,根本看不到实际数据。嗯,今天我们就来彻底解决这个问题。
3.1 为什么直接打印Qt类型会“乱码”?
先看个例子。你写了个简单的Qt程序:
QString name = "Hello Qt";
int value = 42;
qDebug() << name << value; // 输出正常
在GDB里用print name,你看到的可能是:
(gdb) print name
$1 = {d = 0x555555559280}
就一个指针地址?数据呢?
说白了,QString内部用的是隐式共享(写时复制)机制。它不直接存储字符数组,而是存一个指向QStringData的指针。GDB默认不知道该怎么解析这个结构。
我在项目中遇到过这种情况:一个QString明明赋值了,但打印出来就是个地址。当时排查了半天,以为是赋值没成功,后来才发现是GDB不会“翻译”Qt类型。从那以后,我就养成了配置Qt打印脚本的习惯。
3.2 配置Qt打印脚本(一步到位)
Qt官方其实提供了GDB的打印脚本,就在Qt安装目录里。你只需要加载它:
(gdb) source /path/to/Qt/版本号/gcc_64/share/qtcreator/debugger/qttypes.py
加载之后,再试试:
(gdb) print name
$1 = "Hello Qt"
是不是清爽多了?
~/.gdbinit 文件里,每次启动GDB自动加载,省得每次都敲一遍。
3.3 QString的打印技巧
有时候你不想加载整个脚本,或者环境受限。那怎么办?手动打印呗。
QString的内部结构大致是这样的:
struct QStringData {
QtPrivate::RefCount ref;
int size;
uint alloc;
uint capacity;
qptrdiff offset; // 到字符数据的偏移
// 字符数据紧随其后
};
你可以这样手动获取字符串内容:
(gdb) print name.d_ptr->data()
$2 = 0x5555555592a0 "Hello Qt"
或者更直接一点:
(gdb) print name.toStdString()
$3 = "Hello Qt"
我个人习惯用toStdString(),因为GDB能直接调用Qt的成员函数。不过要注意,这需要程序在运行时能正常调用该函数,不能是崩溃后的core dump。
d_ptr->data()这种原始方式。
3.4 QList的打印技巧
QList的打印比QString复杂一点。直接print会看到:
(gdb) print list
$4 = {d = 0x5555555593c0}
加载Qt脚本后,可以这样:
(gdb) print list
$5 = QList<int>(3, {1, 2, 3})
手动打印的话,可以用:
(gdb) print list.size()
$6 = 3
(gdb) print list[0]
$7 = 1
(gdb) print list[1]
$8 = 2
为什么能直接用下标?因为GDB会调用operator[]。但要注意,如果列表元素是QString,打印出来可能还是地址,需要再套一层toStdString()。
我曾经调试过一个QList<QString>,里面存了上百个文件名。用print list直接打印,终端刷屏了。后来我学会了用print list.size()先看大小,再用print list[0]、print list[1]逐个检查,效率高多了。
3.5 QMap的打印技巧
QMap是键值对容器,打印起来更麻烦。加载脚本后:
(gdb) print map
$9 = QMap<QString, int>(2, {["apple"] = 5, ["banana"] = 3})
手动打印的话,可以这样遍历:
(gdb) print map.size()
$10 = 2
(gdb) print map.keys()
$11 = {"apple", "banana"}
(gdb) print map.values()
$12 = {5, 3}
或者直接查某个键:
(gdb) print map["apple"]
$13 = 5
这里有个坑:如果键不存在,map["key"]会插入一个默认值。所以查之前最好先用contains()确认:
(gdb) print map.contains("orange")
$14 = false
3.6 QVector的打印技巧
QVector和QList类似,但内部是连续内存存储。加载脚本后:
(gdb) print vec
$15 = QVector<double>(4, {3.14, 2.71, 1.41, 1.73})
手动打印:
(gdb) print vec.size()
$16 = 4
(gdb) print vec.data()[0]
$17 = 3.14
(gdb) print vec.data()[1]
$18 = 2.71
data()返回的是底层数组指针,用下标访问效率很高。我在项目中经常用这个技巧来检查大数据量的QVector,比如图像像素数据。
3.7 知识体系一览
下面这张图总结了本章的核心内容,方便你快速回顾:
3.8 避坑指南
最后,分享几个我踩过的坑:
- core dump里别用成员函数:程序都挂了,还调用
toStdString()?大概率会报错。这时候老老实实用d_ptr->data()。 - QMap用[]要小心:我曾经在调试时用了
print map["nonexist"],结果程序里多了一个空值键值对,排查了好久才发现是GDB“帮”我插入的。 - 大数据容器别直接print:一个
QList里存了10万个元素,你直接print,终端卡死别怪我。先看size(),再取前几个看看就够了。 - 脚本版本要匹配:Qt4和Qt5的
qttypes.py不一样,别拿Qt5的脚本去调试Qt4程序,会报错。
嗯,掌握了这些技巧,调试Qt程序时再也不用对着满屏的指针发呆了。下次遇到QString、QList这些类型,直接上手,心里有底。
可可精品资料库