C++(Qt)软件调试---qmake编译优化和生成调试信息(9) 原创

C++(Qt)软件调试—qmake编译优化和生成调试信息(9)

更多精彩内容
👉个人内容分类汇总 👈
👉C++软件调试、异常定位 👈
👉PDF版本下载 👈

1、前言

1.1 编译器优化是什么

编译器编译优化是指编译器在编译源代码为目标代码的过程中,通过对代码结构和语义的分析,自动优化目标代码的生成方式,编译器编译优化可以提高程序的性能和可靠性,但也可能会对程序的可读性和可维护性产生负面影响。因此,开发者需要在代码调试和维护的过程中进行权衡和选择,以获得最佳的程序性能和开发效率。

编译优化主要包括以下几个方面:

  1. 算法优化:通过优化算法的复杂度,减少程序执行的时间和空间消耗。
  2. 代码优化:通过优化代码的结构、指令的选择和数据存储方式等,减少程序执行的时间和空间消耗。
  3. 循环优化:通过分析循环结构,优化循环的执行过程,减少循环的执行时间和空间消耗。
  4. 内联函数优化:通过将函数的代码直接嵌入到调用该函数的代码中,减少函数调用的开销,提高程序的执行效率。
  5. 代码生成优化:通过对目标代码的生成方式进行优化,减少目标代码的大小和执行时间。

1.2 调试信息是什么

  • C++调试信息是在编译C++程序时生成的一些附加信息,它包括了程序中各个变量、函数以及类的定义和使用等详细信息。

  • 调试信息可以帮助开发人员在程序运行时快速定位和解决问题,特别是在出现崩溃、错误或异常情况时,可以帮助开发人员追踪到具体的代码行数和错误原因。

  • 调试信息可以通过编译器选项来开启和关闭,通常在开发和测试阶段开启,而在正式发布时关闭。

1.3 测试环境

系统环境:ubuntu22.04、Windows10;

编译器:g++ (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0、MinGW7.3.0 64-bit、MSVC2017-64;

2、Qt编译模式说明

Qt是一个跨平台的GUI应用程序框架,支持C++编程语言。Qt提供了三种不同的构建模式:Debug、Profile和Release,每种模式都有不同的用途和优化级别。

有的Bug只有在Release模式下才能存在或者只有在Debug模式下高性能运行才能出现,这时就需要我们通过修改编译器选项来进行调试。

  1. Debug模式

Debug模式是开发阶段使用的模式,旨在提供最好的调试工具支持。

在Debug模式下,编译器会生成完整的符号表和调试信息,以便在程序崩溃或出现错误时,开发人员可以轻松地跟踪和调试代码。

此外,Debug模式还会关闭代码优化,以确保程序的行为与代码的预期行为一致。

因此,Debug模式下生成的可执行文件通常比其他模式下的可执行文件更大。

使用MSVC编译器时会生成符号表PDB文件;

使用GCC、MinGW编译器时会将调试信息全部写入可执行程序中,所以可执行程序比较大。

  1. Profile模式

Profile模式是用于性能分析的模式。(相当于Release模式下增加调试信息,使用的也是Release的库)

在Profile模式下,编译器会生成符号表和调试信息,但会开启一些代码优化,以提高程序的性能。

此外,编译器还会为程序生成额外的性能分析信息,以便开发人员可以查看程序的性能瓶颈和优化程序。

因此,Profile模式下生成的可执行文件比Debug模式下的文件稍小。

使用MSVC编译器时会生成符号表PDB文件;

使用GCC、MinGW编译器时会将调试信息写到一个.debug文件中,对可执行程序影响不大。

  1. Release模式

Release模式是用于发布的模式。

在Release模式下,编译器会进行更加严格的代码优化,以提高程序的性能和减小程序的大小。

此外,编译器还会删除符号表和调试信息,以减小可执行文件的大小。

因此,Release模式下生成的可执行文件比Debug和Profile模式下的文件更小,但不适合调试。

总的来说,Debug、Profile和Release模式都有各自的优点和缺点。

在开发过程中,建议使用Debug模式进行调试,以便更容易地查找和修复错误。

在发布之前,建议使用Profile模式进行性能分析和优化,并使用Release模式发布程序以提高程序的性能和减小程序的大小。

3、比较Linux下Qt三种编译模式

1.1 编译生成文件比较

  • 如下图所示:

    • Debug模式编译生成的可执行程序非常大;
    • Release模式生成的可执行程序很小,几乎只有Debug模式的1/30;
    • Profile模式生成的可执行程序和Release模式的差不多大,但是额外生成了一个.debug文件,包含了调试信息。

    在这里插入图片描述

1.2 编译器配置比较

  1. 在Linux下Qt编译后会将QMake的pro文件转换为make使用的Makefile文件,可以通过对比Debug、Profile、Release三种模式编译生成的Makefile文件,了解这三个模式干了啥,有什么区别;

  2. 打开一个生成的Makefile文件,下图中框选部分就是编译器、选项的配置所在;

    在这里插入图片描述

  3. 下图中比较了Debug模式和Release模式的区别,左侧为Debug模式,右侧为Release模式,主要对比CFLAGS、CXXFLAGS、LFLAGS这三个变量;

    • Debug模式:
      • CFLAGS、CXXFLAGS:使用-g参数生成调试信息,默认为-g2,没有指定优化参数-O则默认关闭优化。
    • Release模式:
      • CFLAGS、CXXFLAGS:使用了-O2参数开启了编译优化,没有使用-g参数则默认不生成调试信息;
      • LFLAGS:增加了-O1参数表示链接器开启了1级别编译优化。

    在这里插入图片描述

  4. 下图中比较了Debug模式和Profile模式的区别,左侧为Debug模式,右侧为Profile模式,主要对比CFLAGS、CXXFLAGS、LFLAGS这三个变量;

    • CFLAGS、CXXFLAGS:Profile模式在Debug模式的基础上使用了-O2参数开启了编译优化,同时使用-g参数生成调试信息;

    在这里插入图片描述

  5. 下图中比较了Release模式和Profile模式的区别,左侧为Profile模式,右侧为Release模式,主要对比CFLAGS、CXXFLAGS、LFLAGS这三个变量;

    • CFLAGS、CXXFLAGS:Release和Profile都使用了-O2参数开启了编译优化,Profile增加了使用-g参数生成调试信息;
    • LFLAGS:相对于Release,Profile没有使用-O1参数,关闭了链接器优化。

    在这里插入图片描述

1.3 调试效果比较

  1. 新建一个Qt工程,在程序中添加下面测试代码;

    inline void test()
    {
        int* a = nullptr;
        *a = 10;
    }
    
    void Widget::on_pushButton_clicked()
    {
        test();
    }
    
  2. 切换为Release模式,点击调试运行;

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HVHauCcp-1681024688399)(C++(Qt)]软件调试—Qt编译优化和生成调试信息(9).assets/image-20230409101100262.png)

  3. 程序运行起来后点击触发测试代码的按键,如下图所示,可以看出:

    • 错误类型:段错误;
    • 异常位置:Widget::on_pushButton_clicked() 0x555555557714

    在这里插入图片描述

  4. 切换为Profile模式,点击调试运行,程序运行起来后点击触发测试代码的按键,如下图所示,可以看出:

    • 错误类型:段错误;
    • 异常所在文件:widget.cpp;
    • 异常所在函数:void Widget::on_pushButton_clicked();
    • 异常所在行号:25行;
    • 并且直接在源码中定位到异常所在行号,可以看出定位的异常位置是调用test()函数的位置,而不是准确的异常位置。

    在这里插入图片描述

  5. 切换为Debug模式,点击调试运行,程序运行起来后点击触发测试代码的按键,如下图所示,可以看出:

    • 错误类型:段错误;
    • 异常所在文件:widget.cpp;
    • 异常所在函数:inline void test();
    • 异常所在行号:20行;
    • 并且直接在源码中定位到异常所在行号,可以看出定位的异常位置就是实际错误的位置,没有出现偏差。

    在这里插入图片描述

1.4 GDB调试Profile程序

  1. 由于Qt在Profile模式编译时将调试信息额外写入到了.debug文件,所以在使用GDB调试时需要指定加载.debug文件才可以调试程序;

  2. 以下是使用GDB调试程序时如何使用.debug文件的详细说明:

    1. 确保可执行文件和.debug文件都在同一目录中。

    2. 启动GDB并加载可执行文件:

      gdb test
      
    3. GDB加载.debug文件:

      symbol-file test.debug
      
    4. 开始运行程序:

      run
      

4、比较Windows下Qt - Mingw三种编译模式

1.1 编译生成文件比较

  • 如下图所示:

    • Debug模式编译生成的可执行程序非常大;
    • Release模式生成的可执行程序很小,几乎只有Debug模式的1/43;
    • Profile模式生成的可执行程序是Release模式的差不多2.7被,还额外生成了一个.debug文件,包含了调试信息。
    • 从比较结果看明显和linux下不一样,虽然说Mingw也是GCC,但是Qt使用Mingw和GCC肯定还是有一些区别。

    在这里插入图片描述

1.2 编译器配置比较

  1. 在Windows下打开编译生成的build*文件夹会发现,和Linux下有很大区别,如下图所示,多了两个release、debug文件夹,又多了Makefile.Release、Makefile.Debug文件,到底是使用Makefile.Release、Makefile.Debug还是Makefile文件呢?

    在这里插入图片描述

  2. 打开Makefile文件会发现没有编译器配置项;

    在这里插入图片描述

  3. 其实编译器配置项都在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文件进行编译的。

  4. 下图中比较了Debug模式和Release模式的区别,左侧为Debug模式,右侧为Release模式,主要对比CFLAGS、CXXFLAGS、LFLAGS这三个变量;

    • Debug模式:
      • CFLAGS、CXXFLAGS:使用-g参数生成调试信息,默认为-g2,没有指定优化参数-O则默认关闭优化。
    • Release模式:
      • CFLAGS、CXXFLAGS:使用了-O2参数开启了编译优化,没有使用-g参数则默认不生成调试信息;
      • LFLAGS:增加了-s参数从可执行文件中删除所有符号表和重新定位信息。(这里和Linux下不一样)

    在这里插入图片描述

  5. 下图中比较了Debug模式和Profile模式的区别,左侧为Debug模式,右侧为Profile模式,主要对比CFLAGS、CXXFLAGS、LFLAGS这三个变量;

    • CFLAGS、CXXFLAGS:Profile模式在Debug模式的基础上使用了-O2参数开启了编译优化,同时使用-g参数生成调试信息;

    在这里插入图片描述

  6. 下图中比较了Release模式和Profile模式的区别,左侧为Profile模式,右侧为Release模式,主要对比CFLAGS、CXXFLAGS、LFLAGS这三个变量;

    • CFLAGS、CXXFLAGS:Release和Profile都使用了-O2参数开启了编译优化,Profile增加了使用-g参数生成调试信息;
    • LFLAGS:相对于Release,Profile没有使用-s参数从可执行文件中删除所有符号表和重新定位信息。

    在这里插入图片描述

1.3 调试效果比较

  1. 使用相同的测试代码。

  2. 切换为Release模式,点击调试运行,程序运行起来后点击触发测试代码的按键,如下图所示,可以看出:

    • 错误类型:段错误;
    • 异常位置:0x401720
    • 由于Release模式编译使用了-s参数,所以异常位置除了地址什么都看不到。

    在这里插入图片描述

  3. 切换为Profile模式,点击调试运行,程序运行起来后点击触发测试代码的按键,如下图所示,可以看出:

    • 错误类型:段错误;
    • 异常所在文件:widget.cpp;
    • 异常所在函数:void Widget::on_pushButton_clicked();
    • 异常所在行号:25行;
    • 并且直接在源码中定位到异常所在行号,可以看出定位的异常位置是调用test()函数的位置,而不是准确的异常位置,这时因为Profile使用了-O2参数优化编译,导致内联函数被展开了。

    在这里插入图片描述

  4. 切换为Debug模式,点击调试运行,程序运行起来后点击触发测试代码的按键,如下图所示,可以看出:

    • 错误类型:段错误;
    • 异常所在文件:widget.cpp;
    • 异常所在函数:inline void test();
    • 异常所在行号:20行;
    • 成功定位到源码中使用空指针的一行。

    在这里插入图片描述

5、比较Windows下Qt - MSVC三种编译模式

1.1 编译生成文件比较

  • 如下图所示:

    • Debug模式编译产生了三个文件,exe、ilk、pdb,生成的可执行程序只是比Release模式编译的可执行程序大两倍多一点,;
    • Release模式编译只生成了一个exe,生成的可执行程序很小比较小,只有29k;
    • Profile模式编译生成了两个文件,exe和pdb,生成的可执行程序和Release模式的一样大。

    在这里插入图片描述

1.2 编译器配置比较

  1. 下图中比较了Debug模式和Release模式的区别,左侧为Debug模式,右侧为Release模式,主要对比CFLAGS、CXXFLAGS、LFLAGS这三个变量;

    • Debug模式:
      • CFLAGS、CXXFLAGS:使用-Zi参数生成调试信息,没有指定优化参数-O则默认关闭优化,-MDd是指定-MD的Debug库。
      • LFLAGS:使用了/DEBUG参数,将链接对象和库文件中的调试信息放入程序数据库 (PDB) 文件。 它会在程序的后续生成期间更新 PDB(使用了这个参数后-Zi才有效),指定 /DEBUG 时隐式使用 /INCREMENTAL
    • Release模式:
      • CFLAGS、CXXFLAGS:使用了-O2参数开启了编译优化,没有使用-Zi参数则默认不生成调试信息;
      • LFLAGS:使用了/INCREMENTAL:NO参数,未选择增量链接,不会生成.ilk文件。

    在这里插入图片描述

  2. 下图中比较了Debug模式和Profile模式的区别,左侧为Debug模式,右侧为Profile模式,主要对比CFLAGS、CXXFLAGS、LFLAGS这三个变量;

    • CFLAGS、CXXFLAGS:Debug和Profile都使用-Zi参数生成调试信息,Profile使用了优化参数-O2开启编译优化。
    • LFLAGS:Debug和Profile都使用了/DEBUG参数生成PDB文件,并且Profile中使用了/INCREMENTAL:NO参数,未选择增量链接,使用了/OPT:REF参数开启了链接器优化,清除从未引用的函数和数据(称为 COMDAT)。

    在这里插入图片描述

  3. 下图中比较了Profile模式和Release模式的区别,左侧为Profile模式,右侧为Release模式,主要对比CFLAGS、CXXFLAGS、LFLAGS这三个变量;

    • CFLAGS、CXXFLAGS:Debug和Profile都使用-O2参数开启编译优化,Profile使用了-Zi参数生成调试信息。
    • LFLAGS:相对于Release,Profile使用了/DEBUG参数生成PDB文件,使用了/OPT:REF参数开启了链接器优化,清除从未引用的函数和数据(称为 COMDAT)。

    在这里插入图片描述

1.3 调试效果比较

  1. 使用相同的测试代码。

  2. 切换为Release模式,点击调试运行,程序运行起来后点击触发测试代码的按键,如下图所示,可以看出:

    • 异常代码:0x0000005(表示”访问冲突异常“或者”内存访问异常“);
    • 异常信息:写入访问冲突发生在:0x0(基本表示是空指针);
    • 异常位置:0x7ff65fbb1222
    • 由于Release模式编译开启了编译优化,未生成调试信息,所以异常位置除了地址什么都看不到。

    在这里插入图片描述

  3. 切换为Profile模式,点击调试运行,程序运行起来后点击触发测试代码的按键,如下图所示,可以看出:

    • 异常代码:0x0000005(表示”访问冲突异常“或者”内存访问异常“);
    • 异常信息:写入访问冲突发生在:0x0(基本表示是空指针);
    • 异常位置:0x7ff65fbb1222;
    • 异常所在文件:widget.cpp;
    • 异常所在函数:void Widget::on_pushButton_clicked();
    • 异常所在行号:31 (不准确)
    • 在两个button的槽函数中都有使用到test()函数,运行时点击的是button1,提示的异常函数名称正确,但是异常行号和在代码中定位的异常位置却是在button2的槽函数中,存在误差

    在这里插入图片描述

  4. 切换为Debug模式,点击调试运行,程序运行起来后点击触发测试代码的按键,如下图所示,可以看出:

    • 异常代码:0x0000005(表示”访问冲突异常“或者”内存访问异常“);
    • 异常信息:写入访问冲突发生在:0x0(基本表示是空指针);
    • 异常位置:0x7ff622694210;
    • 异常所在文件:widget.cpp;
    • 异常所在函数:void Widget::on_pushButton_clicked();
    • 异常所在行号:20;
    • 成功定位到源码中使用空指针的一行。

    在这里插入图片描述

6、Qt编译器链接器配置用到的变量

  1. C语言编译器使用:
    1. QMAKE_CC:指定在构建包含C源代码的项目时将使用的C编译器。
    2. QMAKE_CFLAGS:指定用于构建项目的C编译器标志(选项)。适用于Debug和Release模式,这个变量的值通常由qmake或qmake.conf处理,很少需要修改。
    3. QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO:为Profile模式指定c编译器标志。(profile就是带调试信息的Release模式)
    4. QMAKE_CFLAGS_DEBUG:为Debug模式指定C编译器标志。
    5. QMAKE_CFLAGS_RELEASE:指定Release模式的C编译器标志。
  2. C++编译器使用:
    1. QMAKE_CXX:指定在构建包含c++源代码的项目时将使用的c++编译器。
    2. QMAKE_CXXFLAGS:指定用于构建项目的c++编译器标志(选项)。适用于Debug和Release模式,这个变量的值通常由qmake或qmake.conf处理,很少需要修改。
    3. QMAKE_CXXFLAGS_RELEASE_WITH_DEBUGINFO:为Profile模式指定c++编译器标志。(profile就是带调试信息的Release模式)
    4. QMAKE_CXXFLAGS_DEBUG:为Debug模式指定c++编译器标志。
    5. QMAKE_CXXFLAGS_RELEASE:为Release模式指定c++编译器标志。
  3. 链接器使用:
    1. QMAKE_LFLAGS:指定传递给链接器的一组通用标志。如果需要更改用于特定平台或项目类型的标志,请使用专用变量之一而不是此变量。
    2. QMAKE_LFLAGS_CONSOLE:指定用于生成控制台程序的链接器标志。(仅在 Windows 上使用)
    3. QMAKE_LFLAGS_DEBUG:指定Debug版本的链接器标志。
    4. QMAKE_LFLAGS_PLUGIN:指定用于生成插件的链接器标志。
    5. QMAKE_LFLAGS_RPATH:指定使用来自QMAKE_RPATHDIR的值所需的链接器标志。(仅在 Unix 平台上使用)
    6. QMAKE_LFLAGS_RELEASE:指定Release版本的链接器标志。
    7. QMAKE_LFLAGS_APP:指定用于生成应用程序的链接器标志。
    8. QMAKE_LFLAGS_WINDOWS:指定用于生成 Windows GUI 项目(即非控制台应用程序)的链接器标志。(仅在 Windows 上使用)

7、Qt在Release模式下关闭优化生成调试信息配置

  • 将下列代码加入到工程的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关闭优化,生成调试信息使用)
    }
    

8、总结

通过查看Qt编译生成的Makefile文件可以学习到Qt内部对不同编译模式做了哪些工作;

Qt默认的三种编译模式基本可以适用于大部分开发调试的场景;

当我们遇见特殊的问题时就需要手动修改编译器选项,在程序编译时选择合适的优化参数和生成调试信息参数,在运行性能、程序大小、调试方便三个方向进行权衡利弊。

例如在不需要考虑性能时可以完全关闭优化,生成尽可能多的调试信息,以方便调试;

而有些程序运行需要一定的性能,就可以选择开启一定较低级别的优化。

文章中所述内容多有不足,欢迎一起交流学习。

posted @ 2023-04-09 15:32  mahuifa  阅读(0)  评论(0编辑  收藏  举报  来源