制作微信网站模板下载,企业管理系统排名,邯郸建设网,windows优化大师有用吗今天是2025年1月26日#xff0c;农历腊月二十七#xff0c;一个距离新春佳节仅一步之遥的日子。城市的喧嚣中#xff0c;年味已悄然弥漫——能在这个时候坚持上班的人#xff0c;真可称为“牛人”了吧#xff0c;哈哈。。。。
此刻#xff0c;我在重新审视那些曾被遗忘的…今天是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 指定输出文件名。
[adminmyhost testc]$ gcc -E a1.c -o a1.i生成a1.i文件
将 .c 中的头文件展开、[宏展开]。生成的文件是 .i 文件预处理之后的程序还是文本可以用文本编辑器打开。 预处理后的文件变大
头文件
什么是头文件 头文件Header Files是C语言中用来声明函数、宏和数据类型的文件(只是声明不占用内存空间)通常以**“.h”**作为后缀。使得多个源文件可以共享这些声明和定义从而提高代码的重用性和可读性。 头文件的作用
声明函数和变量头文件可以包含函数和变量的声明使得不同的源文件可以共享这些声明。 定义宏头文件可以定义宏这样在多个源文件中都可以使用相同的宏。 包含其他头文件头文件可以包含其他头文件从而形成一个头文件的层次结构。
示例自定义头文件
创建头文件.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来防止头文件被多次包含
创建源文件.c文件并实现头文件中声明的函数 myfunctions.c的文件。
#include myheader.h
#include stdio.hvoid myFunction() {printf(Hello from myFunction!\n);
}使用头文件 main.c的文件
#include myheader.hint 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、#elifelse 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.hint 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、处理静态库和动态库 链接阶段还需要处理静态库和动态库。 静态库是一组预编译的目标文件的集合它们在链接时被复制到最终的可执行文件中。 动态库则是在程序运行时动态加载的库文件链接器会在可执行文件中记录动态库的依赖关系并在程序运行时加载这些库。 使用动态库可以减小可执行文件的大小并且当库文件更新时无需重新编译整个程序。 静态库 静态库是一组已经被编译和链接成二进制代码的程序模块这些模块在编译时被合并到最终的可执行文件中。 特点 在编译时将库的代码嵌入到可执行文件中因此可执行文件独立于库的存在。 每次程序编译时静态库的代码都被复制到生成的可执行文件中。 生成的可执行文件包含了库的所有必要代码因此文件通常较大。 可执行文件不依赖于外部库文件可以在没有库文件的机器上独立运行。 文件扩展名通常以.aUnix/Linux或.libWindows为文件扩展名。
使用场景适用于对执行文件大小没有严格限制、需要在没有库文件的机器上运行或需要避免动态链接带来的依赖性的场景。
动态库 动态库是一组已经被编译和链接成二进制代码的程序模块但它们在运行时被加载到内存中而不是在编译时被合并到可执行文件中。 特点 可执行文件在运行时需要动态库的支持。 动态库可以被多个程序共享从而减小可执行文件的大小。 需要确保目标系统上存在相应的动态库否则程序将无法正常运行。 易于更新和维护因为只需替换相应的动态库文件即可无需重新编译整个程序。 文件扩展名通常以.soUnix/Linux或.dllWindows为文件扩展名。 隐式调用程序在编译时指定依赖的动态库链接器会在程序运行时自动加载这些库。 显式调用程序在运行时通过特定的API来加载和调用动态库中的函数这种方式提高了程序的灵活性。 使用场景适用于需要多个程序共享库代码、希望节省内存和磁盘空间或需要方便地进行库代码升级和维护的场景。
4、生成可执行文件 链接器会生成最终的可执行文件。这个文件包含了程序的所有代码和数据并且已经被正确地组织和链接在一起可以在操作系统上直接运行。
借用网络一张图总结