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   %rbp
    movq    %rsp, %rbp
    subq    $16, %rsp
    movl    $5, -4(%rbp)      ; int a = 5;
    movl    $10, -8(%rbp)     ; int b = 10;
    movl    -4(%rbp), %eax    ; eax = a
    addl    -8(%rbp), %eax    ; eax = eax + b
    movl    %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          ; 设置返回值 0
    leave                     ; 清理栈帧
    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、生成可执行文件
链接器会生成最终的可执行文件。这个文件包含了程序的所有代码和数据,并且已经被正确地组织和链接在一起,可以在操作系统上直接运行。

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

在php中怎么打开OpenSSL

&#xff08;点击即可进入聊天助手&#xff09; 背景 在使用php做一些项目时,有用到用户邮箱注册等,需要开启openssl的能力 在php系统中openssl默认是关闭状态的,在一些低版本php系统中,有的甚至需要在服务器终端后台,手动安装 要打开OpenSSL扩展&#xff0c;需要进行以下步骤 …

【数据分享】2014-2025年我国水系数据(免费获取/全国/分省)

水系数据是我们在各项研究中经常使用的数据&#xff01;水系数据虽然很常用&#xff0c;但是却基本没有能下载最近年份水系数据的网站&#xff0c;所以很多人不知道如何获得水系数据。 本次我们给大家分享的是2014-2025年的全国范围的水系数据&#xff0c;包括水系线数据和水系…

Office2021下载与安装保姆级教程【Office Tool Plus】

Office Tool Plus安装Office2021 下载Office Tool Plus安装OfficeⅠ. 清除旧版本Ⅱ. 配置安装参数Ⅲ. 安装许可证Ⅳ. 激发&#xff08;JH&#xff09;Office 本文介绍使用Office Tool Plus工具下载、安装、部署Office 2021全过程。 下载Office Tool Plus OfficeToolPlus是一个…

DeepSeek明确学术研究方向效果如何?

明确学术研究方向 在学术写作中&#xff0c;选择一个出色的研究主题至关重要&#xff0c;因为它直接关系到论文是否能登上高级别的学术期刊。不少学者在这个过程中走入了误区&#xff0c;他们往往将大把的时间花在写作本身&#xff0c;而忽略了对选题的深入思考&#xff0c;这…

Spring MVC 综合案例

目录 一. 加法计算器 1. 准备工作 2. 约定前后端交互接口 需求分析 接口定义 3. 服务器端代码 4. 运行测试 二. 用户登录 1. 准备工作 2. 约定前后端交互接口 需求分析 接口定义 (1) 登录界面接口 (2) 首页接口 3. 服务器端代码 4. 运行测试 三. 留言板 1. 准备…

基于OSAL的嵌入式裸机事件驱动框架——消息队列osal_msg

参考B站up主【架构分析】嵌入式祼机事件驱动框架 感谢大佬分享 消息队列 消息分为hdr和bdy&#xff0c;把消息的头dhr和内容bdy做了一个分离的设计 dhr包括指向下一个消息的指针next&#xff0c;len在创建消息的时候使用&#xff0c;dest_id即目标任务&#xff0c;将消息和任务…

Github 2025-01-25Rust开源项目日报Top10

根据Github Trendings的统计,今日(2025-01-25统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Rust项目10Python项目1Vue项目1JavaScript项目1Deno: 现代JavaScript和TypeScript运行时 创建周期:2118 天开发语言:Rust, JavaScript协议类型…

【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】1.8 随机数奥秘:生成符合现实分布的虚拟数据

1.8 随机数奥秘&#xff1a;生成符合现实分布的虚拟数据 目录 #mermaid-svg-wHqPAE3mMd8HNYmi {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-wHqPAE3mMd8HNYmi .error-icon{fill:#552222;}#mermaid-svg-wHqPAE3mM…

使用vitepress搭建自己的博客项目

一、介绍can-vitepress-blog 什么是CAN BLOG CAN BLOG是基于vitepress二开的个人博客系统&#xff0c;他能够方便使用者快速构建自己的博客文章&#xff0c;无需繁琐的配置和复杂的代码编写。 CAN BLOG以antdv为UI设计基础&#xff0c;简洁大方&#xff0c;界面友好&#xf…

STranslate 中文绿色版即时翻译/ OCR 工具 v1.3.1.120

STranslate 是一款功能强大且用户友好的翻译工具&#xff0c;它支持多种语言的即时翻译&#xff0c;提供丰富的翻译功能和便捷的使用体验。STranslate 特别适合需要频繁进行多语言交流的个人用户、商务人士和翻译工作者。 软件功能 1. 即时翻译&#xff1a; 文本翻译&#xff…

【数据结构】_链表经典算法OJ:合并两个有序数组

目录 1. 题目描述及链接 2. 解题思路 3. 程序 3.1 第一版 3.2 第二版 1. 题目描述及链接 题目链接&#xff1a;21. 合并两个有序链表 - 力扣&#xff08;LeetCode&#xff09; 题目描述&#xff1a; 将两个升序链表合并为一个新的 升序 链表并返回。 新链表是通过拼接给…

51单片机开发:动态数码管

实验目标&#xff1a;使8个数码管依次显示01234567。 动态数码管的原理图如下图所示&#xff1a;两个四位一体的数码管&#xff0c;其段选接在P0端口上。下表以数字0为例&#xff0c;描述端口输出值与数码管显示的对照关系。 dpgfedcbaP07P06P05P04P03P02P01P00001111110x3f&…

84,【8】BUUCTF WEB [羊城杯 2020]Blackcat

进入靶场 音乐硬控我3分钟 回去看源码 <?php // 检查 POST 请求中是否包含 Black-Cat-Sheriff 和 One-ear 字段 // 如果任意一个字段为空&#xff0c;则输出错误信息并终止脚本执行 if(empty($_POST[Black-Cat-Sheriff]) || empty($_POST[One-ear])){die(请提供 Black-C…

挂载mount

文章目录 1.挂载的概念(1)挂载命令&#xff1a;mount -t nfs(2)-t 选项&#xff1a;指定要挂载的文件系统类型(3)-o选项 2.挂载的目的和作用(1)跨操作系统访问&#xff1a;将Windows系统内容挂载到Linux系统下(2)访问外部存储设备(3)整合不同的存储设备 3.文件系统挂载要做的事…

数据分箱 baggingboosting onehot独热编码 woe编码 sklearn的ensemble(集成学习)

目录 数据分箱就是将连续变量离散化。 bagging&boosting onehot独热编码 独热编码的结果如下&#xff1a; woe编码 WOE编码的基本原理 步骤一&#xff1a;计算WOE 步骤二&#xff1a;应用WOE WOE编码的优点 示例 数据示例 步骤一&#xff1a;计算每个类别的违约…

Jetson Xavier NX (ARM) 使用 PyTorch 安装 Open3D-ML 指南

由于 Jetson 为 ARM64 (aarch64) 的系统架构&#xff0c;所以不能用 pip install 直接安装&#xff0c;需要通过源码编译。 升级系统 JetPack 由于 Open3D-ML 目前只支持 CUDA 10.0 以及 CUDA 11.*&#xff0c;并且 JetPack 的 CUDA 开发环境只有10.2、11.4以及12.2&#xff0…

【Linux系统】进程间通信:进程池

进程池的结构图示如下&#xff1a; 一、初始化进程池 1、创建多个子进程 通过 main 命令行参数获取需要创建子进程的个数 判断 argc 个数&#xff0c;使用 usage 提示 在编程和命令行工具中&#xff0c;“usage” 通常指的是命令或程序的使用说明&#xff0c;即如何正确使用该…

【问题解决】el-upload数据上传成功后不显示成功icon

el-upload数据上传成功后不显示成功icon 原因 由于后端返回数据与要求形式不符&#xff0c;使用el-upload默认方法调用onSuccess钩子失败&#xff0c;上传文件的状态并未发生改变&#xff0c;因此数据上传成功后并未显示成功的icon标志。 解决方法 点击按钮&#xff0c;调用…

怎样在PPT中启用演讲者视图功能?

怎样在PPT中启用演讲者视图功能&#xff1f; 如果你曾经参加过重要的会议或者演讲&#xff0c;你就会知道&#xff0c;演讲者视图&#xff08;Presenter View&#xff09;对PPT展示至关重要。它不仅能帮助演讲者更好地掌控演讲节奏&#xff0c;还能提供额外的提示和支持&#…

【2025年数学建模美赛F题】(顶刊论文绘图)模型代码+论文

全球网络犯罪与网络安全政策的多维度分析及效能评估 摘要1 Introduction1.1 Problem Background1.2Restatement of the Problem1.3 Literature Review1.4 Our Work 2 Assumptions and Justifications数据完整性与可靠性假设&#xff1a;法律政策独立性假设&#xff1a;人口统计…