更多精彩内容 |
---|
个人内容分类汇总 |
C++软件调试、异常定位 |
PDF版本下载 |
编译器编译优化是指编译器在编译源代码为目标代码的过程中,通过对代码结构和语义的分析,自动优化目标代码的生成方式,编译器编译优化可以提高程序的性能和可靠性,但也可能会对程序的可读性和可维护性产生负面影响。因此,开发者需要在代码调试和维护的过程中进行权衡和选择,以获得最佳的程序性能和开发效率。
编译优化主要包括以下几个方面:
- 算法优化:通过优化算法的复杂度,减少程序执行的时间和空间消耗。
- 代码优化:通过优化代码的结构、指令的选择和数据存储方式等,减少程序执行的时间和空间消耗。
- 循环优化:通过分析循环结构,优化循环的执行过程,减少循环的执行时间和空间消耗。
- 内联函数优化:通过将函数的代码直接嵌入到调用该函数的代码中,减少函数调用的开销,提高程序的执行效率。
- 代码生成优化:通过对目标代码的生成方式进行优化,减少目标代码的大小和执行时间。
C++调试信息是在编译C++程序时生成的一些附加信息,它包括了程序中各个变量、函数以及类的定义和使用等详细信息。
调试信息可以帮助开发人员在程序运行时快速定位和解决问题,特别是在出现崩溃、错误或异常情况时,可以帮助开发人员追踪到具体的代码行数和错误原因。
调试信息可以通过编译器选项来开启和关闭,通常在开发和测试阶段开启,而在正式发布时关闭。
系统环境:ubuntu22.04、Windows10;
编译器:g++ (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0、MinGW7.3.0 64-bit、MSVC2017-64;
Qt是一个跨平台的GUI应用程序框架,支持C++编程语言。Qt提供了三种不同的构建模式:Debug、Profile和Release,每种模式都有不同的用途和优化级别。
有的Bug只有在Release模式下才能存在或者只有在Debug模式下高性能运行才能出现,这时就需要我们通过修改编译器选项来进行调试。
Debug模式是开发阶段使用的模式,旨在提供最好的调试工具支持。
在Debug模式下,编译器会生成完整的符号表和调试信息,以便在程序崩溃或出现错误时,开发人员可以轻松地跟踪和调试代码。
此外,Debug模式还会关闭代码优化,以确保程序的行为与代码的预期行为一致。
因此,Debug模式下生成的可执行文件通常比其他模式下的可执行文件更大。
使用MSVC编译器时会生成符号表PDB文件;
使用GCC、MinGW编译器时会将调试信息全部写入可执行程序中,所以可执行程序比较大。
Profile模式是用于性能分析的模式。(相当于Release模式下增加调试信息,使用的也是Release的库)
在Profile模式下,编译器会生成符号表和调试信息,但会开启一些代码优化,以提高程序的性能。
此外,编译器还会为程序生成额外的性能分析信息,以便开发人员可以查看程序的性能瓶颈和优化程序。
因此,Profile模式下生成的可执行文件比Debug模式下的文件稍小。
使用MSVC编译器时会生成符号表PDB文件;
使用GCC、MinGW编译器时会将调试信息写到一个
.debug
文件中,对可执行程序影响不大。
Release模式是用于发布的模式。
在Release模式下,编译器会进行更加严格的代码优化,以提高程序的性能和减小程序的大小。
此外,编译器还会删除符号表和调试信息,以减小可执行文件的大小。
因此,Release模式下生成的可执行文件比Debug和Profile模式下的文件更小,但不适合调试。
总的来说,Debug、Profile和Release模式都有各自的优点和缺点。
在开发过程中,建议使用Debug模式进行调试,以便更容易地查找和修复错误。
在发布之前,建议使用Profile模式进行性能分析和优化,并使用Release模式发布程序以提高程序的性能和减小程序的大小。
如下图所示:
.debug
文件,包含了调试信息。在Linux下Qt编译后会将QMake的pro文件转换为make使用的Makefile文件,可以通过对比Debug、Profile、Release三种模式编译生成的Makefile文件,了解这三个模式干了啥,有什么区别;
打开一个生成的Makefile文件,下图中框选部分就是编译器、选项的配置所在;
下图中比较了Debug模式和Release模式的区别,左侧为Debug模式,右侧为Release模式,主要对比CFLAGS、CXXFLAGS、LFLAGS这三个变量;
-g
参数生成调试信息,默认为-g2
,没有指定优化参数-O
则默认关闭优化。-O2
参数开启了编译优化,没有使用-g
参数则默认不生成调试信息;-O1
参数表示链接器开启了1级别编译优化。下图中比较了Debug模式和Profile模式的区别,左侧为Debug模式,右侧为Profile模式,主要对比CFLAGS、CXXFLAGS、LFLAGS这三个变量;
-O2
参数开启了编译优化,同时使用-g
参数生成调试信息;下图中比较了Release模式和Profile模式的区别,左侧为Profile模式,右侧为Release模式,主要对比CFLAGS、CXXFLAGS、LFLAGS这三个变量;
-O2
参数开启了编译优化,Profile增加了使用-g
参数生成调试信息;-O1
参数,关闭了链接器优化。新建一个Qt工程,在程序中添加下面测试代码;
inline void test()
{
int* a = nullptr;
*a = 10;
}
void Widget::on_pushButton_clicked()
{
test();
}
切换为Release模式,点击调试运行;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HVHauCcp-1681024688399)(C++(Qt)]软件调试—Qt编译优化和生成调试信息(9).assets/image-20230409101100262.png)
程序运行起来后点击触发测试代码的按键,如下图所示,可以看出:
切换为Profile模式,点击调试运行,程序运行起来后点击触发测试代码的按键,如下图所示,可以看出:
切换为Debug模式,点击调试运行,程序运行起来后点击触发测试代码的按键,如下图所示,可以看出:
由于Qt在Profile模式编译时将调试信息额外写入到了.debug
文件,所以在使用GDB调试时需要指定加载.debug
文件才可以调试程序;
以下是使用GDB调试程序时如何使用.debug文件的详细说明:
确保可执行文件和.debug文件都在同一目录中。
启动GDB并加载可执行文件:
gdb test
GDB加载.debug文件:
symbol-file test.debug
开始运行程序:
run
如下图所示:
.debug
文件,包含了调试信息。在Windows下打开编译生成的build*文件夹会发现,和Linux下有很大区别,如下图所示,多了两个release、debug文件夹,又多了Makefile.Release、Makefile.Debug文件,到底是使用Makefile.Release、Makefile.Debug还是Makefile文件呢?
打开Makefile文件会发现没有编译器配置项;
其实编译器配置项都在Makefile.Release和Makefile.Debug中,那使用哪一个呢,其实这里有一个判断的小技巧,打开debug和release文件夹,哪个文件夹里有编译的中间文件就使用哪一个,一般情况下Debug模式编译就会在debug文件夹下生成中间文件,使用的就是Makefile.Debug文件,而Release模式下编译就会在release文件夹下生成编译的中间文件,使用Makefile.Release,那Profile模式呢,打开build-untitled5-Desktop_Qt_5_12_5_MinGW_64_bit-Release文件夹,在里面的release文件夹中发现有编译生成的中间文件,就说明使用的是Makefile.Release文件进行编译的。
下图中比较了Debug模式和Release模式的区别,左侧为Debug模式,右侧为Release模式,主要对比CFLAGS、CXXFLAGS、LFLAGS这三个变量;
-g
参数生成调试信息,默认为-g2
,没有指定优化参数-O
则默认关闭优化。-O2
参数开启了编译优化,没有使用-g
参数则默认不生成调试信息;-s
参数从可执行文件中删除所有符号表和重新定位信息。(这里和Linux下不一样)下图中比较了Debug模式和Profile模式的区别,左侧为Debug模式,右侧为Profile模式,主要对比CFLAGS、CXXFLAGS、LFLAGS这三个变量;
-O2
参数开启了编译优化,同时使用-g
参数生成调试信息;下图中比较了Release模式和Profile模式的区别,左侧为Profile模式,右侧为Release模式,主要对比CFLAGS、CXXFLAGS、LFLAGS这三个变量;
-O2
参数开启了编译优化,Profile增加了使用-g
参数生成调试信息;-s
参数从可执行文件中删除所有符号表和重新定位信息。使用相同的测试代码。
切换为Release模式,点击调试运行,程序运行起来后点击触发测试代码的按键,如下图所示,可以看出:
-s
参数,所以异常位置除了地址什么都看不到。切换为Profile模式,点击调试运行,程序运行起来后点击触发测试代码的按键,如下图所示,可以看出:
-O2
参数优化编译,导致内联函数被展开了。切换为Debug模式,点击调试运行,程序运行起来后点击触发测试代码的按键,如下图所示,可以看出:
如下图所示:
下图中比较了Debug模式和Release模式的区别,左侧为Debug模式,右侧为Release模式,主要对比CFLAGS、CXXFLAGS、LFLAGS这三个变量;
-Zi
参数生成调试信息,没有指定优化参数-O
则默认关闭优化,-MDd
是指定-MD
的Debug库。/DEBUG
参数,将链接对象和库文件中的调试信息放入程序数据库 (PDB) 文件。 它会在程序的后续生成期间更新 PDB(使用了这个参数后-Zi
才有效),指定 /DEBUG
时隐式使用 /INCREMENTAL
。-O2
参数开启了编译优化,没有使用-Zi
参数则默认不生成调试信息;/INCREMENTAL:NO
参数,未选择增量链接,不会生成.ilk
文件。下图中比较了Debug模式和Profile模式的区别,左侧为Debug模式,右侧为Profile模式,主要对比CFLAGS、CXXFLAGS、LFLAGS这三个变量;
-Zi
参数生成调试信息,Profile使用了优化参数-O2
开启编译优化。/DEBUG
参数生成PDB文件,并且Profile中使用了/INCREMENTAL:NO
参数,未选择增量链接,使用了/OPT:REF
参数开启了链接器优化,清除从未引用的函数和数据(称为 COMDAT)。下图中比较了Profile模式和Release模式的区别,左侧为Profile模式,右侧为Release模式,主要对比CFLAGS、CXXFLAGS、LFLAGS这三个变量;
-O2
参数开启编译优化,Profile使用了-Zi
参数生成调试信息。/DEBUG
参数生成PDB文件,使用了/OPT:REF
参数开启了链接器优化,清除从未引用的函数和数据(称为 COMDAT)。使用相同的测试代码。
切换为Release模式,点击调试运行,程序运行起来后点击触发测试代码的按键,如下图所示,可以看出:
切换为Profile模式,点击调试运行,程序运行起来后点击触发测试代码的按键,如下图所示,可以看出:
切换为Profile模式,点击调试运行,程序运行起来后点击触发测试代码的按键,如下图所示,可以看出:
将下列代码加入到工程的Pro文件中可以支持在GCC、MinGW、MSVC编译器的Release模式下关闭优化,生成调试信息。
注意:下列代码仅使用于调试,会降低程序性能,如果要发布程序最好将下面代码注释掉再编译。
msvc:CONFIG(release, debug|release) {
QMAKE_CFLAGS_RELEASE -= -O2 # 取消C优化
QMAKE_CFLAGS_RELEASE += -Zi # 生成调试信息,放到pdb文件中
QMAKE_CXXFLAGS_RELEASE -= -O2 # 取消C++优化
QMAKE_CXXFLAGS_RELEASE += -Zi # 生成调试信息
QMAKE_LFLAGS_RELEASE -= /INCREMENTAL:NO # 选择增量链接
QMAKE_LFLAGS_RELEASE += /DEBUG # 将调试信息放到PDB文件中
message(MSVC编译器Release关闭优化,生成调试信息使用)
}
mingw:CONFIG(release, debug|release) {
QMAKE_CFLAGS_RELEASE -= -O2 # 取消C优化
QMAKE_CFLAGS_RELEASE += -O0 # 显示指定禁止优化
QMAKE_CFLAGS_RELEASE += -g # 生成C调试信息
QMAKE_CXXFLAGS_RELEASE -= -O2 # 取消C++优化
QMAKE_CXXFLAGS_RELEASE += -O0 # 显示指定禁止优化
QMAKE_CXXFLAGS_RELEASE += -g # 生成C++调试信息
QMAKE_LFLAGS_RELEASE -= -Wl,-s # 取消Release模式删除所有符号表和重新定位信息的设置
QMAKE_LFLAGS_RELEASE += -g # 链接器生成调试信息
message(Mingw编译器Release关闭优化,生成调试信息使用)
}
# 如果不加unix,MinGW也会进入这里
unix:gcc:CONFIG(release, debug|release) {
QMAKE_CFLAGS_RELEASE -= -O2 # 取消C优化
QMAKE_CFLAGS_RELEASE += -O0 # 显示指定禁止优化
QMAKE_CFLAGS_RELEASE += -g # 生成C调试信息
QMAKE_CXXFLAGS_RELEASE -= -O2 # 取消C++优化
QMAKE_CXXFLAGS_RELEASE += -O0 # 显示指定禁止优化
QMAKE_CXXFLAGS_RELEASE += -g # 生成C++调试信息
QMAKE_LFLAGS_RELEASE -= -Wl,-O1 # 取消Release模式链接器优化
QMAKE_LFLAGS_RELEASE += -g # 链接器生成调试信息
message(GCC编译器Release关闭优化,生成调试信息使用)
}
通过查看Qt编译生成的Makefile文件可以学习到Qt内部对不同编译模式做了哪些工作;
Qt默认的三种编译模式基本可以适用于大部分开发调试的场景;
当我们遇见特殊的问题时就需要手动修改编译器选项,在程序编译时选择合适的优化参数和生成调试信息参数,在运行性能、程序大小、调试方便三个方向进行权衡利弊。
例如在不需要考虑性能时可以完全关闭优化,生成尽可能多的调试信息,以方便调试;
而有些程序运行需要一定的性能,就可以选择开启一定较低级别的优化。
文章中所述内容多有不足,欢迎一起交流学习。