C++(Qt)软件调试---验证GCC编译优化和生成调试信息(8)

news/2024/6/29 12:16:10 标签: c++, GCC, MinGW, 编译器, 软件调试

GCC8_1">C++(Qt)软件调试—验证GCC编译优化和生成调试信息(8)

文章目录

  • C++(Qt)软件调试---验证GCC编译优化和生成调试信息(8)
    • 1、前言
      • 1.1 编译器优化是什么
      • 1.2 调试信息是什么
      • 1.3 适用范围和测试环境
    • 2、C++代码编译-O参数验证
      • 1.1 准备工作
      • 1.2 验证不同-O参数对程序大小、性能的影响
    • 3、C++代码编译-g参数验证
      • 1.1 准备工作
      • 1.2 验证不同级别-g参数对大小、性能的影响
      • 1.3 验证不同级别-g参数对调试的影响
    • 4、总结

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

1、前言

1.1 编译器优化是什么

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

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

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

1.2 调试信息是什么

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

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

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

1.3 适用范围和测试环境

由于MinGWGCC基本相同,所以本文适用于GCCMinGW编译器

在本文中验证的结果仅供参考;

系统环境:ubuntu22.04;

编译器:g++ (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0;

2、C++代码编译-O参数验证

1.1 准备工作

  1. 下面验证会从编译后的程序大小运行性能两个方面进行比较,因为代码比较小,所以编译速度就不进行验证了。

  2. 下面是一个简单的代码示例,代码用于计算1到100000000的整数和,并输出计算结果和程序运行时间。用于验证g++编译参数-O的每个级别的效果:

    #include <iostream>
    #include <chrono>
    using namespace std;
    using namespace std::chrono;
    
    inline int add(int a, int b)
    {
        return a + b;
    }
    
    int main() 
    {
        int n = 100000000;
        int sum = 0;
        auto start = high_resolution_clock::now();  // 记录开始时间
        for (int i = 1; i <= n; ++i) 
        {
            sum = add(sum, i);
        }
        for (int i = 1; i <= n; ++i) 
        {
            sum = add(sum, i);
        }
        auto end = high_resolution_clock::now();  // 记录结束时间
        auto duration = duration_cast<microseconds>(end - start);  // 计算运行时间
        cout << "sum = " << sum << endl;
        cout << "time = " << duration.count() << " microseconds" << endl;
        return 0;
    }
    
  3. 我们可以在编译时使用不同的-O级别参数来比较不同优化级别、不同优化参数的效果。具体地,我们可以使用以下命令编译代码:

    g++  test.cpp -o test
    g++ -O0 test.cpp -o test-O0
    g++ -O1 test.cpp -o test-O1
    g++ -O2 test.cpp -o test-O2
    g++ -O3 test.cpp -o test-O3
    g++ -Os test.cpp -o test-Os
    g++ -Ofast test.cpp -o test-Ofast
    g++ -Og test.cpp -o test-Og
    

1.2 验证不同-O参数对程序大小、性能的影响

  1. 使用ll -Slr命令将所有编译后的程序按大小从小到大排序显示,如图所示,使用-Os参数编译的程序大小最小,使用-Ofast参数编译的程序大小最大,而优化级别0~3和大小没有对应关系,优化级别高的大小不一定小。

    其中没有使用-O参数和使用-O0参数编译的大小相同,可用看出程序编译时默认时关闭优化的。

    在这里插入图片描述

  2. 下面将每个程序执行10次,统计每个优化参数编译的程序运行消耗的时间,并将时间数据放入Excel中绘制折线图进行比较。

  3. 从数据看不同的优化参数对性能的影响天差地别。(注意:由于测试使用的代码比较简单,测试数据量较少,所以结果仅供参考,不同的代码优化后的性能区别不一样,不能一概而论)

    • 默认编译、-O0编译为一个一个级别,性能最低;
    • -O1-O2-Og-Os编译为一个级别,性能适中;
    • -O3-Ofast编译为一个级别,性能最高。

    在这里插入图片描述

    在这里插入图片描述

  4. 总的来说,使用适当的优化级别可以显著提高程序的运行效率,但也需要注意代码大小的影响,以及可能出现的优化错误和不可预测的行为。因此,在选择优化级别时,需要综合考虑代码的性能需求、可靠性和可维护性等方面的因素。

3、C++代码编译-g参数验证

1.1 准备工作

  1. 以下是一段用于验证g++编译参数-g的不同级别的代码:这里会验证使用不同级别-g参数编译后程序的大小,运行速度,调试信息内容。

    #include <iostream>
    #include <chrono>
    
    using namespace std::chrono;
    using namespace std;
    #define PI 3.14              // 未使用的宏定义
    #define ADD(x, y) (x + y)    // 使用的宏
    
    int g_x = 10;                 // 使用的全局变量
    int g_y = 20;                 // 未使用的全局变量
    const int g_cx = 100;         // 使用的全局常量
    const int g_cy = 200;         // 未使用的全局常量
    static int g_sx = 1000;       // 使用的static全局变量
    static int g_sy = 2000;       // 未使用static的全局变量
    
    int add(int a, int b)       // 未使用的函数
    {
        int temp = a + b;
        return temp;
    }
    
    void runTime()             // 验证运行时间
    {
        int n = 100000000;
        int sum = 0;
        auto start = high_resolution_clock::now();  // 记录开始时间
        for (int i = 1; i <= n; ++i) 
        {
            sum = add(sum, i);
        }
        for (int i = 1; i <= n; ++i) 
        {
            sum = add(sum, i);
        }
        auto end = high_resolution_clock::now();  // 记录结束时间
        auto duration = duration_cast<microseconds>(end - start);  // 计算运行时间
        cout << "sum = " << sum << endl;
        cout << "time = " << duration.count() << " microseconds" << endl;
    }
    
    int main ()
    {
        int a = 100;            // 使用的局部变量
        int b = 200;            // 未使用的局部变量
        const int ca = 101;            // 使用的局部常量
        const int cb = 202;            // 未使用的局部常量
        static int sa = 101;            // 使用的static变量
        static int sb = 202;            // 未使用的static变量
    
        cout << ADD(a, g_x) << endl; 
        cout << ADD(ca, g_cx) << endl; 
        cout << ADD(sa, g_sx) << endl; 
        
        runTime();
    
        return 0;
    }
    
  2. 在使用g++编译此代码时,可以通过-g参数设置不同的调试信息级别。具体来说,-g参数有以下不同级别:

    • -g:以操作系统的本机格式(stabs、COFF、XCOFF或DWARF)生成调试信息。GDB可以使用这些调试信息。
    • -g0:不生成任何调试信息。
    • -g1:生成基本的调试信息,包括外部变量、函数名和行号。
    • -g2:生成更详细的调试信息,包括局部变量和类型信息。
    • -g3:生成最详细的调试信息,包括源代码和宏定义。现在新版本的gcc编译器默认使用DWARF5,DWARF5是DWARF格式的最新版本,它在前几个版本的基础上增加了一些新的特性。然而,DWARF5并不支持展开宏定义。需要加上-gdwarf-2-gdwarf-3或者-gdwarf-4参数使用低版本的DWARF。
  3. 下面是使用不同级别的-g参数编译上述代码的命令和输出:旧版本的编译器不一定支持-gdwarf-4,可以选择使用-2

g++ test.cpp -o test          # 不使用-g参数
g++ -gdwarf-4 -g test.cpp -o test-g     # 使用默认-g参数
g++ -gdwarf-4 -g0 test.cpp -o test-g0   # 使用-g0参数
g++ -gdwarf-4 -g1 test.cpp -o test-g1   # 使用-g1参数
g++ -gdwarf-4 -g2 test.cpp -o test-g2   # 使用-g2参数
g++ -gdwarf-4 -g3 test.cpp -o test-g3   # 使用-g3参数

1.2 验证不同级别-g参数对大小、性能的影响

  1. 使用ll -Slr命令将所有编译后的程序按大小从小到大排序显示,如图所示,不使用-g参数和使用-g0参数编译的程序大小相同,使用-g-g2参数编译的程序大小相同,说明不使用-g参数编译默认为关闭调试信息,使用默认-g参数编译默认为2级调试信息。

在这里插入图片描述

  1. 分别将每个程序执行10次,将消耗的时间放到Excel中,如下图所示,不同级别的-g参数对程序运行速度基本没什么影响。(由于测试程序较为简单,测试数据量较少,所以结果仅代表个人观点)

在这里插入图片描述

1.3 验证不同级别-g参数对调试的影响

  1. 使用下列GDB命令来验证不同-g参数的效果:

    1. b main:在main函数开始位置设置一个断点。详细用法

    2. run:使用该命令在 GDB 下启动程序。详细用法

    3. next count:继续向下执行count行代码。详细用法

    4. info locals:打印所选帧的局部变量,每个变量都在单独的一行上。这些都是在所选帧的执行点可访问的所有变量(声明为静态或自动)。详细用法

    5. list main:打印以main函数开头为中心的源代码信息(如果找不到源代码文件则不显示)。详细用法

    6. list 1,10:打印1~10行的源代码信息。

    7. print x:打印变量x或者函数x的值,如果要打印局部变量的值,需要运行到局部变量所在作用域内部,执行到过函数所在行。详细用法

    8. info functions:打印所有已定义函数的名称和数据类型。此命令按源文件对其输出进行分组,并用其源行号注释每个函数定义。详细用法

    9. info variables:打印在函数之外定义的所有变量的名称和数据类型(即不包括局部变量)。打印的变量按源文件分组,并用它们各自的源行号进行注释。

    10. info source:显示有关当前源文件的信息,即包含当前执行点的函数的源文件:

      • 源文件的名称以及包含该源文件的目录,

      • 它编译的目录,

      • 其长度以行为单位,

      • 它是用哪种编程语言编写的,

      • 如果调试信息提供了它,则编译文件的程序(其可以包括例如编译器版本和命令行参数),

      • 可执行文件是否包括该文件的调试信息,如果是,信息的格式是什么(例如,STABS、Dwarf 2等),以及调试信息是否包括关于预处理器宏的信息。

    11. info macro ADD:显示指定宏的当前定义或所有定义,并描述建立该定义的源位置或编译器命令行。详细用法

  2. 分别使用四个终端窗口,使用gdb打开不同-g参数编译的可执行程序;

在这里插入图片描述

  1. 分别使用list 1,10命令打印源代码1~10行的信息,可看出:

    • -g0编译的test-g0显示:未加载任何符号表。使用“file”命令。

    • 使用-g1 -g2 -g3编译的可执行程序可调用源代码文件,打印源码信息。

在这里插入图片描述

  1. 分别执行info functions命令,可以看出:

    • -g0编译的test-g0无法显示定义的函数信息,只有在调试符号中可以看见函数名;

    • 使用-g1 -g2 -g3编译的可执行程序可显示定义的函数信息。

在这里插入图片描述

在这里插入图片描述

  1. 分别执行info variables 命令,可以看出:

    • test-g0无法显示定义的外部变量信息,只有在调试符号中包含外部变量名,main函数中定义的static变量名;

    • test-g1可以显示定义的普通外部变量信息,而定义的全局常量、static修饰的全局变量都只能在调试符号中看见名称;

    • test-g2、test-g3可以看见头文件中定义的变量、常量名称,test.cpp中定义的全局变量、静态全局变量,全局常量,而main函数中定义的静态局部变量只能在符号表中看见名称;

在这里插入图片描述

在这里插入图片描述

  1. 分别执行info source命令,可以看出:

    • test-g0无法打印信息;

    • test-g1、test-g2、test-g3均可以显示有关当前源文件的信息,ui并且可看出调试信息使用的是DWARF4格式。

在这里插入图片描述

  1. 分别执行info macro ADD命令,可以看出:

    • test-g0、test-g1、test-g2均不能显示宏定义信息;

    • test-g3可以宏ADD的详细信息。

在这里插入图片描述

  1. 使用cat -n test.cpp命令显示源代码文件,并且带上行号;

  2. 在gdb里分别使用b 54命令在第54行打上断点,可以看出:

    • test-g0无法打断点;

    • test-g1、test-g2、test-g3成功打上断点。

在这里插入图片描述

  1. 分别使用run命令运行可执行程序,可以看出:

    • test-g0直接执行完成并退出程序,无法停止;

    • test-g1、test-g2、tst-g3成功停止在断点位置。

在这里插入图片描述

  1. 分别执行info locals命令,可以看出:

    • test-g0由于程序执行完成退出了,无法打印局部变量信息,显示No frame selected.

    • test-g1显示没有局部变量No locals.;

    • test-g2、test-g3均成功打印所有的局部变量信息;

在这里插入图片描述

  1. 分别使用print命令打印定义的12个变量、常量,可以看出:

    • test-g0可以打印出局部变量、静态局部变量打印的一串{}中的数字不知道是什么;

    • test-g1可以打印出全局变量、局部变量、静态局部变量打印的一串{}中的数字不知道是什么;

    • test-g2、test-g3可以打印出所有变量、常量的值。

在这里插入图片描述

在这里插入图片描述

  1. 分别使用print命令打印程序中的3个函数,可以看出:

    • test-g0无法打印函数的调试符号信息,只有<>中的函数名称;

    • test-g1打印的{}中的调试符号全部都是void类型;

    • test-g2、test-g3打印的{}中的调试符号和函数的实际参数、返回值相同。

在这里插入图片描述

4、总结

我们可以通过学习GCC编译器参数,在程序编译时选择合适的优化参数和生成调试信息参数,在运行性能、程序大小、调试方便三个方向进行权衡利弊。

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

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

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


http://www.niftyadmin.cn/n/208853.html

相关文章

Flink CDC 详解

目录一、CDC 简介 ?二、Flink CDC 案例实操三、Flink-CDC 2.0四、核心原理分析一、CDC 简介 ? 什么是 CDC ? CDC 是 Change Data Capture&#xff08;变更数据获取&#xff09;的简称。核心思想是&#xff0c;监测并捕获数据库的变动&#xff08;包括数据或数据表的插入、…

STM32介绍

STM32 是意法半导体推出的 32 位 ARM Cortex-M 内核微控制器系列&#xff0c;具有高性能、低功耗、可靠性强等特点&#xff0c;广泛应用于工业控制、智能家居、汽车电子、医疗设备等领域。本文将详细介绍 STM32 系列的特点、性能指标、开发工具和应用领域等方面。 一、STM32 系…

MySQL及DOS窗口 导入与导出

一、导出 &#xff08;1&#xff09;使用DOS窗口命令 win11后&#xff0c;新增了一个powerShell&#xff0c;但导出时一定要使用命令提示符 输入&#xff1a;mysqldump -hlocalhost -uroot -p123456 staff>D:\mydb.sql 其中 > 很形象&#xff0c;理解为指向哪个位置 &…

ES、MongoDB、HBase的区别和使用场景

ES、MongoDB、HBase的区别和使用场景 技术优点缺点使用场景选型ES 支持全文搜索可以自动建立索引复杂查询性能高 字段类型无法修改不支持mapReduce写入性能较低、硬件资源消耗高没有细致的权限管理各节点数据的一致性问题 日志分析&#xff1a;将日志统一存储&#xff0c;进行…

2023年全国最新安全员精选真题及答案49

百分百题库提供安全员考试试题、建筑安全员考试预测题、建筑安全员ABC考试真题、安全员证考试题库等&#xff0c;提供在线做题刷题&#xff0c;在线模拟考试&#xff0c;助你考试轻松过关。 21.&#xff08;单选题&#xff09;扣件式钢管脚手架的纵向水平杆&#xff0c;其单根钢…

3.1、线程概述

3.1、线程概述1.线程概述2.线程和进程区别3.线程和进程虚拟地址空间4.线程之间共享和非共享资源①共享资源②非共享资源5.线程版本NPTL1.线程概述 与进程&#xff08;process&#xff09;类似&#xff0c;线程&#xff08;thread&#xff09;是允许应用程序并发执行多个任务的…

企业如何利用数据打造新的人才战略?

由于利率上升、能源价格上涨、政治不确定性、全球供应限制以及寻找和资助昂贵人才的问题&#xff0c;企业的优先事项&#xff0c;也就是人们的优先事项&#xff0c;正在以前所未有的速度发生转变。在数据的基础之上迅速做出正确决策&#xff0c;并灵活精准地向他人传达意义。 …

版本控制工具Git的常见命令与使用方法

目录概述基础命令提交代码把代码提交到暂存区把代码提交到版本库同一笔提交想追加修改回退代码对代码进行了修改&#xff0c;想回退工作区的修改执行了add操作&#xff0c;想回退到工作区执行了commit操作&#xff0c;想撤销修改执行了commit操作&#xff0c;想回退到暂存区挑代…