Linux——gcc编译过程详解与ACM时间和进度条的制作

gcc编译过程详解与ACM时间和进度条的制作

文章目录

  • gcc编译过程详解与ACM时间和进度条的制作
    • 1. 编译详细过程与ACM时间
      • 1.1 预处理
      • 1.2 编译
      • 1.3 汇编
      • 1.4 链接
      • 1.5 C语言和C++在函数符号形成上的区别
      • 1.6 编译器与ACM时间的关系
    • 2. 进度条的制作
      • \r
      • C语言标准输出的缓冲区
      • sleep()和usleep()
      • 倒计时
      • 简单的进度条
      • 带颜色的进度条

1. 编译详细过程与ACM时间

我们知道编译分为四大步:预处理、编译、汇编、链接,但是详细的情况并不像我们想的那样,因为编译器会做很多事情,比如:宏替换、头文件展开、语法检查、语义检查、代码优化、生成汇编代码、生成可执行文件等。

1.1 预处理

gcc -E main.c -o main.i

解析:

  • gcc:编译器
  • -E:将源代码文件进行预编译处理,形成.i文件
  • main.c:源代码文件
  • -o:指定目标文件的名称或者存放路径
  • main.i:预处理后的目标文件

预处理主要完成以下工作:

  1. 头文件展开
  • 处理所有#include预处理指令
  • 将头文件的内容复制到当前文件
  • 可以递归展开(头文件中包含的头文件)
  1. 宏定义替换
  • 展开所有#define宏定义
  • 处理条件编译指令(#if、#ifdef、#ifndef等)
  • 展开所有宏调用
#define Max(a, b) ((a) > (b) ? (a) : (b))
int max = Max(3, 4); //展开为:int max = ((3) > (4) ? (3) : (4))
  1. 条件编译处理

    • #if、#ifdef、#ifndef:条件判断
    • #elif、#else:分支处理
    • #endif:结束条件编译
    #ifdef DEBUG
    printf("debug info\n");
    #endif
    
  2. 删除注释

    • 删除所有//和/* */格式的注释
    • 每个注释都替换为一个空格
  3. 添加行号和文件名标识

    • 使用#line指令标记行号和文件名
    • 用于编译器产生调试信息和编译错误提示
  4. 处理特殊预处理指令

    • #pragma:编译器指令
    • #error:产生编译错误
    • #warning:产生编译警告
  5. 保留所有的换行符

    • 确保错误提示的行号正确
    • 方便调试
  6. 字符串常量化

    • 处理#运算符,将宏参数转换为字符串
    #define STR(s) #s
    STR(hello)  // 展开为: "hello"
    
  7. 宏连接操作

    • 处理##运算符,连接两个记号
    #define CONCAT(a,b) a##b
    CONCAT(x,y)  // 展开为: xy
    

示例:

// 源文件 main.c
#include <stdio.h>
#define MAX 100
#define SQUARE(x) ((x)*(x))int main() {int value = SQUARE(MAX);return 0;
}// 预处理后 main.i(简化版)
// ... stdio.h的内容 ...
int main() {int value = ((100)*(100));return 0;
}

预处理的作用:

  1. 将源文件转换为完整的C/C++代码
  2. 处理编译器不关心的细节
  3. 提供代码复用和条件编译的机制
  4. 简化程序的编写和维护

注意事项:

  1. 预处理是编译的第一步,不检查语法
  2. 宏定义要注意括号的使用
  3. 头文件要防止重复包含
  4. 条件编译要注意匹配
  5. 预处理指令必须独占一行
  6. 宏不会检查类型,因此一定要注意类型匹配

1.2 编译

gcc -S main.i -o main.s

解析:

  • gcc:编译器
  • -S:将预处理后的文件进行编译处理,形成.s文件
  • main.i:预处理后的目标文件
  • -o:指定目标文件的名称或者存放路径
  • main.s:编译后的目标文件

编译主要完成以下工作:

  1. 词法分析

    • 将源代码分解成记号(token)
    • 识别关键字、标识符、常量、运算符等
    int main() { return 0; }
    // 分解为: int(关键字) main(标识符) ((符号) )(符号) {(符号) 
    //         return(关键字) 0(常量) ;(符号) }(符号)
    
  2. 语法分析

    • 根据语法规则分析记号序列
    • 构建抽象语法树(AST)
    • 检查语法错误
    if (x > 0) { y = 1; }
    // 构建为:
    //   if
    //  /  \
    // >    =
    // / \  / \
    // x 0  y 1
    
  3. 语义分析

    • 类型检查
    • 变量声明检查
    • 类型转换
    • 检查语义错误
    int x;
    float y = x + 1.5;  // 需要将x从int转换为float
    
  4. 中间代码生成

    • 生成平台无关的中间表示(IR)
    • 方便后续优化
    // 源代码
    x = a + b * c;// 中间代码(三地址码形式)
    t1 = b * c
    t2 = a + t1
    x = t2
    
  5. 代码优化

    • 常量折叠
    int x = 3 + 4;  // 优化为: int x = 7;
    
    • 死代码消除
    if (0) {        // 这段代码永远不会执行x = 1;      // 可以被消除
    }
    
    • 循环优化
    // 循环展开
    for (i=0; i<2; i++) { a[i] = i; }
    // 优化为:
    a[0] = 0;
    a[1] = 1;
    
    • 公共子表达式消除
    // 原代码
    x = a + b;
    y = (a + b) * c;
    // 优化后
    t = a + b;
    x = t;
    y = t * c;
    

    编译可以带上优化选项来指定想要优化的级别,如:gcc -O2 main.c -o main,-O2表示中等优化,-O3表示最大优化。

  6. 目标代码生成

    • 生成特定平台的汇编代码
    • 考虑目标机器的特性
    • 寄存器分配
    # x86汇编示例
    movl    $1, %eax    # 将1移动到eax寄存器
    addl    %ebx, %eax  # 将ebx的值加到eax
    

注意事项:

  1. 编译错误必须全部修复才能继续
  2. 警告可以忽略但最好处理
  3. 优化可能改变代码行为
  4. 不同编译器可能有不同结果
  5. Debug版本应该使用-O0

1.3 汇编

gcc -c main.s -o main.o

解析:

  • gcc:编译器
  • -c:将汇编代码转换为目标文件
  • main.s:汇编代码文件
  • -o:指定输出文件
  • main.o:目标文件

汇编阶段主要工作:

  1. 指令转换

    • 将汇编指令转换为机器码
    • 生成目标文件(二进制格式)
    # 汇编代码
    movl $1, %eax
    # 转换为机器码(十六进制)
    # B8 01 00 00 00
    
  2. 符号表生成

    • 记录全局符号
    • 记录未解析的外部符号
    • 记录调试信息
  3. 重定位表生成

    • 标记需要重定位的地址
    • 为链接阶段做准备

1.4 链接

gcc main.o -o main

链接阶段主要工作:

  1. 符号解析

    • 解析所有外部符号引用
    • 检查符号重定义
    • 建立全局符号表
  2. 重定位处理

    • 计算符号的最终地址
    • 修正代码中的地址引用
    • 合并各个段(代码段、数据段等)
  3. 生成可执行文件

    • ELF格式(Linux)
    • PE格式(Windows)
    • Mach-O格式(macOS)

注意事项:

  1. 静态链接和动态链接的选择
  2. 注意符号冲突
  3. 库的链接顺序很重要
  4. 链接错误通常与符号解析相关

1.5 C语言和C++在函数符号形成上的区别

C语言是没有函数重载的,因此不存在重名函数,那么一个函数名就可以表示一个唯一的函数,在建立符号表时,一个函数名就对应一个符号,因此链接时不会出现符号冲突。
C++是有函数重载的,因此存在重名函数,在建立符号表时,多个函数名可以对应一个符号,因此链接时会出现符号冲突。
那么该如何解决呢?
函数重载,它要求一个函数名可以有多个不同的定义,但是这些定义的参数类型不能完全相同,因此C++编译器在链接时会对函数名进行改编,在函数名后加上函数参数类型,这样就可以区分重名函数,从而解决符号冲突。
比如:

void f(int, int);
void f(double, double);
// 编译后
// f_i_i
// f_d_d

各种参数类型的符号:

  • v - void
  • b - bool
  • c - char
  • a - signed char
  • h - unsigned char
  • s - short
  • t - unsigned short
  • i - int
  • j - unsigned int
  • l - long
  • m - unsigned long
  • x - long long
  • y - unsigned long long
  • f - float
  • d - double
  • e - long double
  • z - … (varargs)

复合类型的符号:

  • P - 指针 (pointer)
  • R - 引用 (reference)
  • K - const
  • V - volatile
  • A - 数组 (array)

示例:

void func(int);           // _Z4funci
void func(int*);          // _Z4funcPi
void func(const int);     // _Z4funcKi
void func(int&);          // _Z4funcRi
void func(const int*);    // _Z4funcPKi
void func(int* const);    // _Z4funcKPi
//_Z是C++编译器改编符号的标志,4表示函数名长度

1.6 编译器与ACM时间的关系

当我们使用makefile管理项目时,makefile中会有一个all目标,它通常会调用所有的编译命令,根据依赖文件,然后逐步的将源代码文件预编译、编译、汇编形成.o文件,最后链接生成可执行文件。
但是这样就会引申出一个问题,我们在链接阶段,实际上就是将.o文件链接到一起,可以每次我们修改代码,并不一定所有的源代码文件都会修改,那么那些没有修改过的源文件最终预编译编译汇编形成的.o文件和之前完全一样,这样链接时就会造成很多不必要的开销。
那么该如何解决呢?
这时我们就要了解一个概念——ACM(Access Time、Modify Time、Create Time)。
对于任何一个文件来说,它都有三个时间戳:

  • 创建时间(Create Time):文件被创建的时间
  • 修改时间(Modify Time):文件内容被修改的时间
  • 访问时间(Access Time):文件被访问的时间

创建时间,顾名思义,就是文件被创建的时间,这个时间戳在文件创建时被设置,并且在文件的整个生命周期中保持不变。
修改时间,这个时间戳在文件内容被修改时被设置,比如我们使用vim编辑一个文件时,每敲一个键,这个时间戳就会被更新。
访问时间,这个时间戳在文件被访问时被设置,比如我们使用ls查看一个文件时,这个时间戳就会被更新。

由于.o文件是由源代码文件编译形成的,因此.o文件的创建时间总是比源代码文件的修改时间要早,且修改时间一开始就是自己的创建时间。
而当我们修改源代码文件时,源代码文件的修改时间就会被更新,而.o文件的修改时间不会被更新,此时.c的修改时间就要比.o的创建时间要早,这就表示.o文件需要重新编译。
所以,当编译器在进行编译时,如果检查到源代码文件的修改时间比.o文件的修改时间要早,那么就会重新编译源代码文件形成新的.o文件,反之则不会重新编译,使用修改过的.o文件和没修改的.o文件链接生成可执行文件,大大节省了时间。

2. 进度条的制作

在学习制作进度条之前,我们需要认识一些概念:

\r

在C语言中,我们用的最多的转义字符大概就是\n了,\n叫做换行符,作用是将光标移动到下一行,而\r是回车符,作用是将光标移动到当前行的行首。(但是终端在解释的时候会让\n同时拥有回车换行的功能)

C语言标准输出的缓冲区

当我们使用printf输出时,我们认为调用完函数就立即输出到终端,但实际上是先输出到一个语言层面的缓冲区(非系统层面),然后缓冲区再刷新到终端。
一般来说,有两种缓冲区模式:行缓冲和全缓冲
对于行缓冲来说,当遇到\n时,缓冲区就会刷新;
而对于全缓冲来说,当缓冲区满时,缓冲区才会刷新;
而标准输出默认是行缓冲。
因此,如果我们想立即看到输出,可以使用\n刷新缓冲区,或者使用fflush(stdout)刷新缓冲区。
由于我们想在终端的同一行看到输出,所以我们采用fflush(stdout)刷新缓冲区。
而对于\r来说,虽然它不能刷新缓冲区,但是我们可以让光标回到行首,然后输出新的内容,这样就实现了终端上覆盖的效果。

sleep()和usleep()

这两个函数是C语言标准库提供的,用于使程序休眠。

  1. sleep(int n):使系统休眠n秒。
  2. usleep(int n):使系统休眠n微秒

如果我们不想让进度条刷新的太快,就可以使用这两个函数进行控制。

倒计时

#include<stdio.h>
#include<unisted.h>int main()
{int n = 10;while(n){printf("%-2d\r", n);n--;fflush(stdout);sleep(1);}return 0;
}  

简单的进度条

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#define bar '#'#define MaxSize 101
int main()
{printf("简单的进度条:\n");char arr[MaxSize];char spindle[] = {'|', '/', '-', '\\'};memset(arr, '\0', MaxSize);int i = 0;while (i < MaxSize){printf("[%-100s][%-3d%%][%c]\r", arr, i, spindle[i % 4]);arr[i++] = bar;fflush(stdout);usleep(100000);}printf("\n");return 0;
}       

带颜色的进度条

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#define bar '#'#define MaxSize 101
int main()
{srand((unsigned)time(NULL));printf("简单的进度条:\n");char arr[MaxSize];char spindle[] = {'|', '/', '-', '\\'};memset(arr, '\0', MaxSize);int i = 0;while (i < MaxSize){printf("\033[%d;%dm[%-100s]\033[0m\033[40;%dm[%-3d%%]\033[0m\033[40;%dm[%c]\033[0m\r", rand() % 10 + 40, rand() % 10 + 30, arr, rand() % 10 + 30, i, rand() % 10 + 30, spindle[i % 4]);arr[i++] = bar;fflush(stdout);usleep(100000);}printf("\n");return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/469777.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

准确--FastDFS快速单节点部署

FastDFS单节点部署 1. 系统准备 # 查看操作系统信息&#xff08;适用于麒麟V10&#xff09; cat /etc/os-release# 安装必要的软件包 yum -y install gcc gcc-c perl zlib-devel unzip2. 准备 FastDFS 环境 cd /usr/local/fastdfs# 下载必要包&#xff08;若有访问限制&…

国标GB28181视频平台EasyCVR私有化部署视频平台对接监控录像机NVR时,录像机“资源不足”是什么原因?

EasyCVR视频融合云平台&#xff0c;是TSINGSEE青犀视频“云边端”架构体系中的“云平台”系列之一&#xff0c;是一款针对大中型项目设计的跨区域、网络化、视频监控综合管理系统平台&#xff0c;通过接入视频监控设备及视频平台&#xff0c;实现视频数据的集中汇聚、融合管理、…

【智谱开放平台-注册/登录安全分析报告】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 1. 暴力破解密码&#xff0c;造成用户信息泄露 2. 短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉 3. 带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造…

计算机新手练级攻略——如何搜索问题

目录 计算机学生新手练级攻略——如何搜索问题1.明确搜索意图2.使用精确关键词3.使用专业引擎搜索4.利用好技术社区1. Stack Overflow2. GitHub3. IEEE Xplore4. DBLP 5.使用代码搜索工具1. GitHub 代码搜索2. Stack Overflow 代码搜索3. Papers with Code4. IEEE Xplore 6.查阅…

【C++】详细介绍模版进阶,细节满满

目录 一、非类型模版参数&#xff1a; 1、介绍&#xff1a; 2、使用&#xff1a; 3、注意&#xff1a; 4、应用 二、模版特化 &#xff08;一&#xff09;、概念 &#xff08;二&#xff09;、函数模版特化 1、步骤&#xff1a; 2、举例&#xff1a; 3、不建议使用函…

动态规划---解决多段图问题

ok 小伙伴们&#xff0c;我现在有点小小的红温&#xff0c;有点毛躁。 怎么解决多段图问题呢&#xff1f;求取最短路径有多种方法可取。 家人们&#xff0c;毫无思绪可言……………………………… 要实现动态规划&#xff0c;条件&#xff1a;子问题重叠度较高&#xff0c;并…

JMeter基础篇

目录 总目录&#xff1a; 一、JMeter简介&#xff1a; -用途&#xff1a; -优缺点&#xff1a; 二、JMeter安装&#xff1a; 三、项目简介&#xff1a; -学生管理系统&#xff1a; -API接口清单&#xff1a; 查询&#xff1a; 新增&#xff1a; 更新&#xff1a; 删…

Elasticsearch中什么是倒排索引?

倒排索引&#xff08;Inverted Index&#xff09;是一种索引数据结构&#xff0c;它在信息检索系统中被广泛使用&#xff0c;特别是在全文搜索引擎中。倒排索引允许系统快速检索包含给定单词的文档列表。它是文档内容&#xff08;如文本&#xff09;与其存储位置之间的映射&…

【Python特征工程系列】利用SHAP进行特征重要性分析-XGB模型为例(案例+源码)

这是我的第374篇原创文章。 一、引言 SHAP有多种实现方式&#xff0c;每种方式都适用于特定的模型类型&#xff0c;可以实现更快的逼近。 TreeExplainer :TreeExplainer专为树集合方法开发&#xff0c;如XGBoost&#xff0c;LightGBM或CatBoost。 DeepExplainer :DeepExplain…

C++数据结构算法学习

C ,orient(面向) object , object entity(实体) Visible(可见的) or invisible(不可见) 变量用来保存数据 objects attribute(属性) services(服务) C STL 容器 vector, list&#xff08;&#xff09; vector底层是数组&#xff0c;类似双向链表和list底层 map/s…

ELK-Logstash配置

文章目录 一、什么是Logstash、有什么用&#xff1f;什么是 Logstash&#xff1f;Logstash 的主要特点&#xff1a;Logstash 的用途&#xff1a; 二、Logstash的安装与基本配置事先要安装Java的环境&#xff1f;Logstash 安装Debian/UbuntuRed Hat/CentOSmacOS&#xff08;使用…

R语言机器学习与临床预测模型69--机器学习模型解释利器:SHAP

R小盐准备介绍R语言机器学习与预测模型的学习笔记&#xff0c; 快来收藏关注【科研私家菜】 01 机器学习的可解释性 对于集成学习方法&#xff0c;效果虽好&#xff0c;但一直无法解决可解释性的问题。我们知道一个xgboost或lightgbm模型&#xff0c;是由N棵树组成&#xff0c;…

Vue自定义指令详解——以若依框架中封装指令为例分析

自定义指令 在Vue.js中&#xff0c;自定义指令提供了一种非常灵活的方式来扩展Vue的功能。以下是对Vue中自定义指令的详细解释&#xff1a; 一、自定义指令的基本概念 自定义指令允许开发者直接对DOM元素进行低层次操作&#xff0c;而无需编写大量的模板或者JavaScript代码。…

sql server启用远程连接与修改默认端口

一&#xff0c;数据库右键属性 二&#xff0c;sa账号状态属性启用 三&#xff0c;SQL Server配置管理器, 点击SQL Server 服务选项&#xff0c;确定SQL Server是正在运行的。 四&#xff0c;手动修改数据库的连接端口 1&#xff09;确保启用 2)修改默认端口 3)客户端IP改为一…

吴恩达机器学习笔记(3)

吴恩达机器学习&#xff08;3&#xff09; tensorflow实现 用 TensorFlow 实现神经网络 以下是一个完整的代码示例&#xff0c;展示如何使用 TensorFlow 和 Keras 构建和训练一个简单的神经网络来处理 MNIST 数据集&#xff1a; import tensorflow as tf from tensorflow.k…

【入门篇】A+B Problem——多语言版

AB Problem 跳转 题目分析&#xff1a; 这个题目要求输入两个整数 a 和 b&#xff0c;然后输出它们的和。需要注意的是 a 和 b 的绝对值都不超过 10^9。此外&#xff0c;题目中提到了 Pascal 使用 integer 类型可能会爆掉&#xff0c;说明需要使用更大范围的数据类型来处理这…

Matlab实现鹈鹕优化算法(POA)求解路径规划问题

目录 1.内容介绍 2.部分代码 3.实验结果 4.内容获取 1内容介绍 鹈鹕优化算法&#xff08;POA&#xff09;是一种受自然界鹈鹕捕食行为启发的优化算法。该算法通过模拟鹈鹕群体在寻找食物时的协作行为&#xff0c;如群飞、潜水和捕鱼等&#xff0c;来探索问题的最优解。POA因其…

LED和QLED的区别

文章目录 1. 基础背光技术2. 量子点技术的引入3. 色彩表现4. 亮度和对比度5. 能效6. 寿命7. 价格总结 LED和 QLED都是基于液晶显示&#xff08;LCD&#xff09;技术的电视类型&#xff0c;但它们在显示技术、色彩表现和亮度方面有一些关键区别。以下是两者的详细区别&#xff…

《JavaEE进阶》----20.<基于Spring图书管理系统①(登录+添加图书)>

PS&#xff1a;关于接口定义 接口定义&#xff0c;通常由服务器提供方来定义。 1.路径&#xff1a;自己定义 2.参数&#xff1a;根据需求考虑&#xff0c;我们这个接口功能完成需要哪些信息。 3.返回结果&#xff1a;考虑我们能为对方提供什么。站在对方角度考虑。 我们使用到的…

OpenEuler 下 Docker 安装、配置与测试实例

文章目录 前言1. 环境准备2. 下载 Docker3.配置服务文件4.配置加速器加速下载docker镜像5. 验证 Docker 安装 前言 Docker 安装大致分为包管理器安装、脚本安装、离线手动安装、容器编排工具安装、桌面版安装等&#xff0c;每种安装各有特点&#xff0c;但涉及知识面不少&…