C语言编译过程全面解析

今天是2025年1月26日,农历腊月二十七,一个距离新春佳节仅一步之遥的日子。城市的喧嚣中,年味已悄然弥漫——能在这个时候坚持上班的人,真可称为“牛人”了吧,哈哈。。。。

此刻,我在重新审视那些曾被遗忘的角落——C语言,这门陪伴了编程生涯初期的语言,如今再次拾起,竟有如老友重逢,倍感亲切,又回到了那个最初的起点。

C语言编译过程

四个步骤:

(1)预处理:展开头文件/宏替换/去掉注释/条件编译(test.i )。
(2)编译 :检查语法,生成汇编 ( test.s)。
(3)汇编:汇编代码转换机器码(test.o )。
(4)链接:链接到一起生成可执行程序 a.out/a.exe。

在这里插入图片描述

一、预处理

1、**展开所有的宏(macro):**预处理器会查找源代码中的宏定义(使用#define指令定义),并将所有宏调用替换为相应的宏定义。
例如,源代码#define PI 3.14,则预处理器会将所有出现的PI替换为3.14

2、**处理所有条件编译指令:**如#if#elif#else#endif等,这些指令允许程序员根据条件编译不同的代码段。

3、处理#include指令:预处理器会查找源代码中的#include指令,将被包含文件的内容插入到源文件中的指定位置。
这通常用于包含头文件,以便在多个源文件中共享定义和声明。

4、**删除所有注释:**注释是程序员为代码添加的解释性文字,对程序的运行没有实际作用,因此预处理器会将其删除。

5、 **添加行号和文件名信息:**以便在编译时编译器可以使用这些信息来显示警告或错误信息。

预处理结束后,会产生一个后缀为.i的临时文件,该文件是源代码的修改版,已经删除了注释、展开了宏、包含了头文件等。

示例:
a1.c

#include <stdio.h>
int main(void) {printf("hello world\n");system("pause");return 0;
}

执行命令:
-E 是让编译器在预处理之后就退出,不进行后续编译过程;
-o 指定输出文件名。

[admin@myhost testc]$ gcc  -E  a1.c  -o  a1.i

生成a1.i文件
在这里插入图片描述

.c 中的头文件展开、[宏展开]。生成的文件是 .i 文件,预处理之后的程序还是文本,可以用文本编辑器打开。
预处理后的文件变大

头文件

什么是头文件
头文件(Header Files)是C语言中用来声明函数、宏和数据类型的文件(只是声明,不占用内存空间),通常以**“.h”**作为后缀。使得多个源文件可以共享这些声明和定义,从而提高代码的重用性和可读性。
在这里插入图片描述
头文件的作用

声明函数和变量:头文件可以包含函数和变量的声明,使得不同的源文件可以共享这些声明。
定义宏:头文件可以定义宏,这样在多个源文件中都可以使用相同的宏。
包含其他头文件:头文件可以包含其他头文件,从而形成一个头文件的层次结构。

示例:自定义头文件

  1. 创建头文件:.h 扩展名的文件
    myheader.h的头文件。
#ifndef MYHEADER_H
#define MYHEADER_H// 函数声明
void myFunction();// 宏定义
#define MY_MACRO 100// 类型定义(可选)
typedef struct {int x;int y;
} Point;#endif // MYHEADER_H

预处理器指令#ifndef、#define和#endif来防止头文件被多次包含

  1. 创建源文件:.c文件,并实现头文件中声明的函数
    myfunctions.c的文件。
#include "myheader.h"
#include <stdio.h>void myFunction() {printf("Hello from myFunction!\n");
}
  1. 使用头文件
    main.c的文件
#include "myheader.h"int main() {myFunction();printf("MY_MACRO = %d\n", MY_MACRO);Point p;p.x = 10;p.y = 20;printf("Point p = (%d, %d)\n", p.x, p.y);return 0;
}

预处理命令

C语言的预处理命令是由预处理器在编译之前执行的指令。
这些指令以#字符开头,主要目的是在编译之前对源代码进行文本替换、条件编译、文件包含等操作。

C语言中常见的预处理命令:
在这里插入图片描述

1、宏定义 (#define)
宏可以是简单的常量、带参数的宏(类似于函数)或者更复杂的结构。
宏定义是预处理命令中最常见的一种。

(1). 定义常量宏
常量宏是最简单的宏类型,它们用于定义常量值。例如:

#define PI 3.14159
#define MAX_SIZE 100

在代码中,每当预处理器遇到PI或MAX_SIZE时,它们都会被替换为3.14159和100。

(2). 定义带参数的宏(宏函数)
宏也可以像函数一样接受参数,并在展开时替换这些参数。例如:

#define SQUARE(x) ((x) * (x))

这个宏接受一个参数x,并返回它的平方。注意,由于宏是文本替换,所以它们不执行类型检查,也不会导致函数调用的开销。

(3). 条件编译宏
宏还可以用于条件编译,根据宏的定义与否来决定是否包含某段代码。例如:

#define DEBUG#ifdef DEBUG// 这段代码在定义了DEBUG宏时会被编译printf("Debug mode is on.\n");
#else// 这段代码在没有定义DEBUG宏时会被编译// printf("Debug mode is off.\n");
#endif

如果定义了DEBUG宏,则编译器会包含printf(“Debug mode is on.\n”);这行代码;否则,它会忽略它。

2、文件包含 (#include)
用于在当前文件中包含(插入)另一个文件的内容。
例如:#include <stdio.h> 包含了标准输入输出库的头文件。
也可以包含用户自定义的头文件:#include “myheader.h”。
3、条件编译
根据宏的定义与否来决定是否编译某段代码。
#if、#ifdef(如果定义了某个宏)
#ifndef(如果没有定义某个宏)
#else、#elif(else if的缩写)
#endif指令。
例如:#ifdef DEBUG … #endif 用于在定义了DEBUG宏时编译包含的代码。
4、宏取消定义 (#undef)
用于取消之前定义的宏。
例如:#undef PI 会取消PI宏的定义。
5、行控制 (#line)
用于改变当前行号和文件名,通常用于由其他程序生成的源代码中。
例如:#line 100 “newfile.c” 会将接下来的代码行视为位于名为newfile.c的文件的第100行。
6、错误和警告 (#error 和 #warning)
用于生成编译时的错误和警告信息。
例如:#error “This is an error message” 会导致编译器显示错误消息并停止编译。
#warning “This is a warning message” 会导致编译器显示警告消息但继续编译。
7、预定义的宏
C预处理器定义了一些预定义的宏,如:
LINE(当前行号)
FILE(当前文件名)
DATE(编译日期)
TIME(编译时间)等。

二、编译

将前面预编译后的文件进行编写,命令:

gcc -S a1.i -o a1.s

编译阶段的主要任务是将预处理后的C代码转换为汇编代码。这一转换过程涉及多个步骤,包括词法分析、语法分析、语义分析和代码生成(生成汇编代码)。

1、词法分析
任务:将源代码分解成一个个基本的元素,如变量名、常量、关键字、运算符和分隔符等。
输出:这些基本元素通常被称为“词法单元”或“标记”。
2、语法分析
任务:检查源代码的结构或语法是否正确,并构建所谓的抽象语法树(AST)。
AST:是源代码逻辑结构的一个层级模型,它表示了源代码中各个元素之间的关系。
输出:如果源代码语法正确,则生成抽象语法树;如果语法错误,则编译器会报错并停止编译。
3、语义分析
任务:在语法分析的基础上,进一步检查源代码是否有语义错误,例如变量类型不匹配、使用了未声明的变量或函数等。
输出:如果源代码语义正确,则继续后续的编译过程;如果语义错误,则编译器会报错并停止编译。
4、代码生成(生成汇编代码)
任务:将经过词法分析、语法分析和语义分析后的源代码转换为汇编语言代码。
汇编代码:是一种低层次的编程语言,更接近于机器语言,但比机器语言更易于人类阅读和理解。
输出:生成的汇编代码文件通常具有.s扩展名。

举例:
example.c:

#include <stdio.h>int main() {int a = 5;int b = 10;int sum = a + b;printf("Sum: %d\n", sum);return 0;
}

预处理:
使用预处理器处理example.c,将头文件stdio.h的内容包含进来,并处理宏定义等。
输出预处理后的文件example.i
编译:
词法分析:编译器读取预处理后的代码,将其分解成词法单元,如关键字int、return,标识符a、b、sum、main,运算符+、=,以及分隔符等。
语法分析:编译器根据C语言的语法规则,检查这些词法单元是否构成了有效的语法结构,并构建抽象语法树(AST)。例如,它会识别出int a = 5;是一个变量声明和初始化的语句。
语义分析:编译器进一步检查这些语法结构是否有意义。例如,它会检查变量a、b、sum在使用前是否已被声明,以及它们的类型是否匹配。此外,它还会检查函数调用printf是否合法,即是否提供了正确类型和数量的参数。
代码生成:如果语义分析通过,编译器将抽象语法树转换为汇编代码。

.section .data
sum_fmt: .asciz "Sum: %d\n".section .text
.globl main
main:pushq   %rbpmovq    %rsp, %rbpsubq    $16, %rspmovl    $5, -4(%rbp)      ; int a = 5;movl    $10, -8(%rbp)     ; int b = 10;movl    -4(%rbp), %eax    ; eax = aaddl    -8(%rbp), %eax    ; eax = eax + bmovl    %eax, -12(%rbp)   ; int sum = eax (即 a + b 的结果)leaq    sum_fmt(%rip), %rdi ; 设置第一个参数为格式字符串movl    -12(%rbp), %eax    ; 设置第二个参数为 sum 的值movl    %eax, %esi         ; esi = eax (即 sum 的值)xorl    %eax, %eax         ; 清零 eax,作为 printf 的返回值占位符call    printf             ; 调用 printf 函数movl    $0, %eax          ; 设置返回值 0leave                     ; 清理栈帧ret                       ; 返回

三、汇编

将前面编译后的文件进行汇编,命令:

gcc -c a1.s -o a1.o

汇编阶段的主要任务是将汇编代码转换为机器代码(也称为目标代码或二进制代码)。这一转换过程是由汇编器(Assembler)完成的。

汇编指令解析
汇编器逐条读取汇编代码中的指令,并根据汇编指令和机器指令的对照表将其转换为对应的机器指令。
每条汇编指令通常都对应一条或多条机器指令。

地址和符号处理
在汇编过程中,汇编器需要处理汇编代码中的地址和符号。
例如,对于变量和函数的引用,汇编器会将其转换为相应的内存地址。
此外,汇编器还会处理标签(labels)和跳转指令(如goto、if等),确保它们能够正确地跳转到目标位置。

生成目标文件
经过汇编器处理后的代码被转换为机器代码,并存储在目标文件(通常具有.o或.obj扩展名)中。目标文件是二进制格式的,包含了机器可以直接执行的指令和数据。

在这里插入图片描述

四、链接

命令:gcc a1.o -o a1.exe

链接阶段是将多个目标文件(.o或.obj文件)和库文件合并成一个可执行文件的过程。

这个过程涉及多个步骤,包括符号解析、重定位以及处理静态库和动态库等。
1、符号解析
链接器会解析目标文件中的符号信息。
符号通常包括变量名、函数名等,它们代表了程序中的不同实体。
链接器会检查每个目标文件中的符号定义和引用,确保所有引用的符号都有相应的定义。
如果某个符号在多个目标文件中都有定义,链接器会根据链接规则(如C语言的“one definition rule”)来决定使用哪个定义。
2、重定位
在编译和汇编阶段,目标文件中的代码和数据被放置在相对地址中。
然而,在链接阶段,这些相对地址需要被转换为绝对地址,以便程序在运行时能够正确地访问内存中的代码和数据。
链接器会根据目标文件中的重定位信息,调整代码和数据的位置,确保它们能够被正确地加载和执行。

3、处理静态库和动态库
链接阶段还需要处理静态库和动态库。
静态库是一组预编译的目标文件的集合,它们在链接时被复制到最终的可执行文件中。
动态库则是在程序运行时动态加载的库文件,链接器会在可执行文件中记录动态库的依赖关系,并在程序运行时加载这些库。
使用动态库可以减小可执行文件的大小,并且当库文件更新时,无需重新编译整个程序。
静态库:
静态库是一组已经被编译和链接成二进制代码的程序模块,这些模块在编译时被合并到最终的可执行文件中。
特点:
在编译时将库的代码嵌入到可执行文件中,因此可执行文件独立于库的存在。
每次程序编译时,静态库的代码都被复制到生成的可执行文件中。
生成的可执行文件包含了库的所有必要代码,因此文件通常较大。
可执行文件不依赖于外部库文件,可以在没有库文件的机器上独立运行。
文件扩展名:通常以.a(Unix/Linux)或.lib(Windows)为文件扩展名。

使用场景:适用于对执行文件大小没有严格限制、需要在没有库文件的机器上运行或需要避免动态链接带来的依赖性的场景。

动态库:
动态库是一组已经被编译和链接成二进制代码的程序模块,但它们在运行时被加载到内存中,而不是在编译时被合并到可执行文件中。
特点:
可执行文件在运行时需要动态库的支持。
动态库可以被多个程序共享,从而减小可执行文件的大小。
需要确保目标系统上存在相应的动态库,否则程序将无法正常运行。
易于更新和维护,因为只需替换相应的动态库文件即可,无需重新编译整个程序。
文件扩展名:通常以.so(Unix/Linux)或.dll(Windows)为文件扩展名。
隐式调用:程序在编译时指定依赖的动态库,链接器会在程序运行时自动加载这些库。
显式调用:程序在运行时通过特定的API来加载和调用动态库中的函数,这种方式提高了程序的灵活性。
使用场景:适用于需要多个程序共享库代码、希望节省内存和磁盘空间或需要方便地进行库代码升级和维护的场景。

4、生成可执行文件
链接器会生成最终的可执行文件。这个文件包含了程序的所有代码和数据,并且已经被正确地组织和链接在一起,可以在操作系统上直接运行。

借用网络一张图,总结:
在这里插入图片描述

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

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

相关文章

乐优商城项目总结

文章目录 项目简介微服务集群1.enreka注册中心2. zuul网关3. 公共工具类4. 商品微服务5. 文件上传微服务6. 搜索微服务7. 页面静态化微服务8. 用户微服务9. 短信微服务10. 认证微服务11. 购物车微服务12. 订单微服务项目最大的收获项目遇到的问题 项目简介 乐优商城是一个全品…

Yolo11 + OCR 营业执照识别+信息抽取(预期后续改用其他ocr更简单,推理预计使用onnxruntim加速,分c++和python两种方式部署)

目录 一 数据集制作 1 labelimg的安装与使用 2 标注方式 3 数据集制作 二 模型训练 三 使用Yolo11 + OCR 实现“营业执照”信息解析完整方案 1 cutLinesforcode.py 2 getBusinessLicenseContentPart.py 3 getPartWords.py 4 pdfTojpg.py 5 main.py 本项目可用于毕业…

18.Word:数据库培训课程❗【34】

目录 题目 NO1.2.3.4 NO5设置文档内容的格式与样式 NO6 NO7 NO8.9 NO10.11标签邮件合并 题目 NO1.2.3.4 FnF12&#xff1a;打开"Word素材.docx”文件,将其另存为"Word.docx”在考生文件夹下之后到任务9的所有操作均基于此文件&#xff1a;"Word.docx”…

SSM开发(七) MyBatis解决实体类(model)的字段名和数据库表的列名不一致方法总结(四种方法)

目录 方法一: 使用@Results和@Result注解(注解方式) 方法二:修改 SQL 查询语句中的别名(注解方式) 方法三: 全局配置别名或结果映射(resultMap,XML配置方式) 方法四:使用@Column注解 在MyBatis中,如果你希望使用注解的方式来操作数据库,但又遇到实体类中的…

AboutDialog组件的功能和用法

文章目录 1 概念介绍2 使用方法3 示例代码 我们在上一章回中介绍了AlertDialog Widget相关的内容,本章回中将介绍AboutDialog Widget.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1 概念介绍 我们在这里说的AboutDialog是一种弹出式窗口&#xff0c;和上一章回中介绍的Al…

设计模式的艺术-策略模式

行为型模式的名称、定义、学习难度和使用频率如下表所示&#xff1a; 1.如何理解策略模式 在策略模式中&#xff0c;可以定义一些独立的类来封装不同的算法&#xff0c;每个类封装一种具体的算法。在这里&#xff0c;每个封装算法的类都可以称之为一种策略&#xff08;Strategy…

软件架构的演变:从大型机和整体式应用到分布式计算

注&#xff1a;本文为 “软件架构演变” 相关文章合辑。 英文引文机翻&#xff0c;未校。 Evolution of Software Architecture: From Mainframes and Monoliths to Distributed Computing Liv Wong Technical Writer August 06, 2024 Software architecture—the blueprint…

SET alter system reload

目录标题 alter system 只是 写 auto 文件SET & alter system1. **会话级别参数&#xff08;Session-level parameters&#xff09;**2. **系统级别参数&#xff08;System-level parameters&#xff09;**3. **某些特定的超级用户参数**4. **修改时生效的参数**总结&#…

Java教程练习:学生信息管理系统

文章目录 学生管理系统1、需求文档需求分析 2、新建学生实体类3、实现基本菜单和退出功能4、查询和添加4.1 查询学生信息4.2 添加学生信息 5、修改和删除5.1 删除功能实现5.2 修改功能实现 完整代码下载 学生管理系统 1、需求文档 需求 采取控制台的方式书写学生管理系统。 …

【Docker】Docker入门了解

文章目录 Docker 的核心概念Docker 常用命令示例&#xff1a;构建一个简单的 C 应用容器1. 创建 C 应用2. 创建 Dockerfile3. 构建镜像4. 运行容器 Docker 优势学习 Docker 的下一步 **一、Docker 是什么&#xff1f;****为什么 C 开发者需要 Docker&#xff1f;** **二、核心概…

新项目上传gitlab

Git global setup git config --global user.name “FUFANGYU” git config --global user.email “fyfucnic.cn” Create a new repository git clone gitgit.dev.arp.cn:casDs/sawrd.git cd sawrd touch README.md git add README.md git commit -m “add README” git push…

崇州市街子古镇正月初一繁华剪影

今天是蛇年正月初一&#xff0c;下午笔者步出家门&#xff0c;逛到了崇州市街子古镇井水街&#xff0c;想看看景象如何。结果看到的是车水马龙、人流如织&#xff0c;繁花似锦&#xff0c;热闹非凡&#xff0c;原来今天开始预订此地摆下的长街宴。心里高兴&#xff0c;便用手机…

Java设计模式:结构型模式→组合模式

Java 组合模式详解 1. 定义 组合模式&#xff08;Composite Pattern&#xff09;是一种结构型设计模式&#xff0c;它允许将对象组合成树形结构以表示“部分-整体”的层次。组合模式使得客户端能够以统一的方式对待单个对象和对象集合的一致性&#xff0c;有助于处理树形结构…

多线程-线程池的使用

1. 线程池 1.1 线程状态介绍 当线程被创建并启动以后&#xff0c;它既不是一启动就进入了执行状态&#xff0c;也不是一直处于执行状态。线程对象在不同的时期有不同的状态。那么 Java 中的线程存在哪几种状态呢&#xff1f;Java 中的线程 状态被定义在了 java.lang.Thread.…

JavaSE第十一天——集合框架Collection

一、List接口 List接口是一个有序的集合&#xff0c;允许元素有重复&#xff0c;它继承了Collection接口&#xff0c;提供了许多额外的功能&#xff0c;比如基于索引的插入、删除和访问元素等。 常见的List接口的实现类有ArrayList、LinkedList和Vector。 List接口的实现类 …

Flutter_学习记录_导航和其他

Flutter 的导航页面跳转&#xff0c;是通过组件Navigator 和 组件MaterialPageRoute来实现的&#xff0c;Navigator提供了很多个方法&#xff0c;但是目前&#xff0c;我只记录我学习过程中接触到的方法&#xff1a; Navigator.push(), 跳转下一个页面Navigator.pop(), 返回上一…

基于 AWS SageMaker 对 DeepSeek-R1-Distilled-Llama-8B 模型的精调与实践

在当今人工智能蓬勃发展的时代&#xff0c;语言模型的性能优化和定制化成为研究与应用的关键方向。本文聚焦于 AWS SageMaker 平台上对 DeepSeek-R1-Distilled-Llama-8B 模型的精调实践&#xff0c;详细探讨这一过程中的技术细节、操作步骤以及实践价值。 一、实验背景与目标 …

arkui-x跨平台与android java联合开发

华为鸿蒙系统采用的是arkts&#xff0c;支持跨平台crossplatform 即前端为arkts&#xff0c;arkui-x框架&#xff0c;后端为其他的语言框架。 本篇示例后端采用的是java&#xff0c;android studio工程。 主要方式是前端鸿蒙完成界面元素、布局等效果&#xff0c;后面androi…

机器人基础深度学习基础

参考&#xff1a; &#xff08;1&#xff09;【具身抓取课程-1】机器人基础 &#xff08;2&#xff09;【具身抓取课程-2】深度学习基础 1 机器人基础 从平面二连杆理解机器人学 正运动学&#xff1a;从关节角度到末端执行器位置的一个映射 逆运动学&#xff1a;已知末端位置…

k8s支持自定义field-selector spec.hostNetwork过滤

好久没写博客啦&#xff0c;年前写一个博客就算混过去啦&#x1f602; 写一个小功能&#xff0c;对于 Pod&#xff0c;在没有 label 的情况下&#xff0c;支持 --field-selector spec.hostNetwork 查询 Pod 是否为 hostNetwork 类型&#xff0c;只为了熟悉 APIServer 是如何构…