这篇文章上次修改于 291 天前,可能其部分内容已经发生变化,如有疑问可询问作者。

最近使用Qt新写了一个界面插件,插件被调用显示后各项功能都正常;但当插件使用结束被释放后总是导致程序莫名奇妙崩溃,被这个问题困扰很久后终于发现原因是由于QStringLiteral的使用导致。

1. 开发环境

  • Windows 11
  • Visual Stuido 2022
  • Qt 5.15.2

2. 插件功能

插件以动态库接口方式提供,主要包括创建和删除QWidget的接口。

插件界面可简化为一个QListWidget,构造实现如下,其他部分省略:

ListForm::ListForm(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::ListForm)
{
    ui->setupUi(this);

    QListWidgetItem* item = new QListWidgetItem();
    item->setText(QStringLiteral("新QStringLiteral条目"));
    ui->listWidget->addItem(item);
}

3. 崩溃复现

调用方使用QLibrary动态加载此库后创建QWidget后设置到界面;不需要此控件后通过动态库接口删除所创建的QWidget卸载动态库

如果程序的新界面包含itemView相关的Qt控件(如QListWidgetQTableWigetQTableWidget及对应的View)时,程序会崩溃,程序堆栈如下:

Qt5Core.dll!QString::~QString() 行 1307    C++
Qt5Widgets.dll!QStyleOptionViewItem::`scalar deleting destructor'(unsigned int)    C++
Qt5Widgets.dll!QCommonStyle::subElementRect(QStyle::SubElement sr, const QStyleOption * opt, const QWidget * widget) 行 3144    C++
Qt5Widgets.dll!QWindowsStyle::subElementRect(QStyle::SubElement sr, const QStyleOption * opt, const QWidget * w) 行 1900    C++
qwindowsvistastyle.dll!QWindowsXPStyle::subElementRect(QStyle::SubElement sr, const QStyleOption * option, const QWidget * widget) 行 1323    C++
qwindowsvistastyle.dll!QWindowsVistaStyle::subElementRect(QStyle::SubElement element, const QStyleOption * option, const QWidget * widget) 行 1938    C++
Qt5Widgets.dll!QCommonStyle::drawControl(QStyle::ControlElement element, const QStyleOption * opt, QPainter * p, const QWidget * widget) 行 2278    C++
Qt5Widgets.dll!QWindowsStyle::drawControl(QStyle::ControlElement ce, const QStyleOption * opt, QPainter * p, const QWidget * widget) 行 1865    C++
qwindowsvistastyle.dll!QWindowsXPStyle::drawControl(QStyle::ControlElement element, const QStyleOption * option, QPainter * p, const QWidget * widget) 行 2460    C++
qwindowsvistastyle.dll!QWindowsVistaStyle::drawControl(QStyle::ControlElement element, const QStyleOption * option, QPainter * painter, const QWidget * widget) 行 1482    C++
Qt5Widgets.dll!QStyleSheetStyle::drawControl(QStyle::ControlElement ce, const QStyleOption * opt, QPainter * p, const QWidget * w) 行 4177    C++
Qt5Widgets.dll!QStyledItemDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) 行 389    C++
Qt5Widgets.dll!QTableViewPrivate::drawCell(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) 行 1006    C++
Qt5Widgets.dll!QTableView::paintEvent(QPaintEvent * event) 行 1526    C++
Qt5Widgets.dll!QWidget::event(QEvent * event) 行 9082    C++

堆栈提示崩溃位置为Qt框架内部。详细分析后总结出崩溃原因:绘制新的控件中的Item时,在销毁缓存的上一次绘制的item的QStyleOptionViewItem样式时崩溃,具体原因为QString QStyleOptionViewItem::text已经无效。

QStyleOptionViewItem::text代表绘制item的所要显示的文字信息,其他自动都能正常,为何单独text成员会失效了,令人难以理解。

4. 问题修复

经过长时间研究测试后,发现失效的原因为使用了QStringLiteral设置了插件界面中QListWidget条目的显示问题,可能再加上QString隐式数据共享机制,当插件被程序卸载后,条目中的text信息失效,进而QStyleOptionViewItem::text也失效,导致析构QStyleOptionViewItem时程序崩溃。

所以,解决办法时使用QString("")QString::fromUtf8()等方法设置QListWidgetItem的文字信息。经测试替换后程序不再崩溃。

5. 进一步思考

Qt的帮助手册中关于QStringLiteral的描述如下:

QStringLiteral(str)

The macro generates the data for a QString out of the string literal str at compile time. Creating a QString from it is free in this case, and the generated string data is stored in the read-only segment of the compiled object file.

  1. 使用QStringLiteral生成的字符串效率更高,但字符串数据存储在编译后的目标文件的只读段中;
  2. QString的浅拷贝是的在进行赋值时只增加了一个对相应只读数据段的一个引用计数;
  3. 动态库卸载后相应只读数据段从进程控件一并销毁,最终导致QStyleOptionViewItem::text失效

总结:对QStringLiteral的使用应谨慎,如果效率要求不高建议使用常规构建QString的方法以免发生奇怪问题。