一、预处理

        --- 预处理 #开头

(1)预处理指令

        gcc-E file.c-o hello.i

(2)编译指令

        gcc-S file.c-o hello.s

(3)汇编指令

        gcc-c file.s-o hello.o

二、GCC编译C语言程序的全过程

1.预处理(Preprocessing)

        将C源程序预处理,生成.i文件。

        预处理过程实质上是处理“#”,

        将#include包含的头文件直接拷贝到.c当中;

        将#define定义的宏进行替换;

        将#if #else #endif定义的无用代码过滤掉,同时将代码中没用的注释部分删除等。

        预处理所完成的基本上是对源程序的“替代”工作。

        经过此种替代,生成一个没有宏定义、没有条件编译指令、没有特殊符号的输出文件。

        这个文件的含义同没有经过预处理的源文件是相同的,但内容有所不同。

2.编译(Compilation)

        预处理后的.i文件编译为汇编语言,生成.s文件。

        编译所要作的工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码。

3.汇编(Assembly)

        将.s文件经过汇编,生成.o目标文件。

        汇编过程实际上指把汇编语言代码翻译成目标机器指令的过程。对于被翻译系统处理的每一个C语言源程序,都将最终经过这一处理而得到相应的目标文件。目标文件中所存放的也就是与源程序等效的目标的机器语言代码。

        目标文件由段组成。通常一个目标文件中至少有两个段:

        代码段:该段中所包含的主要是程序的指令。该段一般是可读和可执行的,但一般却不可写。

        数据段:主要存放程序中要用到的各种全局变量或静态的数据。一般数据段都是可读,可写,可执行的。

4.链接(Linking)

        将.o文件链接起来生成一个可执行文件。

        链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体。

        根据库函数不同的链接方式,链接处理可分为静态链接和动态链接两种。

三、宏定义

1.定义

        #define 标识符 字符串

(1)预处理命令

(2)标识符   --- 宏名

(3)字符串    //这个字符串只是预处理阶段的文本信息

        #define 宏名 宏值

eg:#define N 10

2.用途

        1.用一个更有可读性的标识符(可用)提高可读性。

        2.符号常量需要见名知义,代码修改 ---一改全改。

3.语法

        a.不能写分号。

        b.宏名 --- 一般建议大写,区别于变量名。

        c.宏名是区分大小写的。

        d.宏名的作用域从定义开始到后面文件结束

补充:①#undef   宏名   ---取消宏定义②预处理   --- 本质做的是原样替换

四、带参宏

1.定义

        #define 宏名(参数)宏值

        eg:

        相乘

        #define MUL(a,b) a*b;

        MUL(1,2)//使用

注意:

1.宏函数不是函数,只是预处理时的定义

2.宏定义的副作用。

因为宏做的是文本的原样替换导致有些时候替换结果不符合预期,解决方式:加括号。

3.宏的嵌套,是逐层替换的。

4.宏定义:要求必须要在一行,结束标志是换行,换行使用"\"续航符

用途:

实现多行代码的替换

2.文件包含

        #include<文件名>

        #include“文件名”

        作用:

                预处理阶段,用文件中的内容替换include命令

        区别:

                <>   ---表示找包含的文件,到系统默认的路径寻找,系统默认的头文件一般用<>。

                ""   ---先在当前目录下寻找,如果没有再到系统目录下寻找,自己写的头文件一般用""。

3.条件编译

        ①#ifdef 标识符

                程序段1

        #else

                程序段2

        #endif

说明:如果标识符已经定义将程序段1包含到最终的文件中,否则包含文件2包含到最终文件中。

调试

        ②#ifndef 标识符

                程序段1

        #else

                程序段2

        #endif

说明:如果标识符没有定义将程序段1包含到最终的文件中,否则包含文件2包含到最终文件中。

        ③#if 表达式

                程序段1

        #else

                程序段2

        #endif

说明:如果表示为真,将程序段1包含到最终的文件中,否则包含文件2包含到最终文件中。

多文件编程中

4.头文件

        ①预处理命令

        ②函数声明

        ③变量的声明

        ④类型的定义

注:功能函数需要配一个对应的头文件

        eg:

                calc.c calc.h

五、心得体会

        预处理、编译、汇编、链接四阶段 toolchain 的拆解,让我第一次把“写下的.c”与“跑起来的 ELF”之间的黑盒打开:宏在预处理期做纯文本替换,因此必须加括号避免副作用;条件编译 #ifndef/#endif 则是头文件卫士,解决重复包含;带参数的宏与函数相比,省调用开销却失类型检查,提示我要根据场景权衡速度与安全。文件包含时区分 <> 与 "" ,既关乎搜索路径,也体现了系统与用户代码的边界。

        多文件编程中,每个功能模块配 .c/.h 的约定,使接口与实现分离,既符合低耦合设计,又方便并行开发与单元测试。今后编码,我会:1) 用 static 隐藏模块内部符号;2) 用宏封装常量及短小逻辑,同时加括号防副作用;3) 为每个 .h 加卫士宏;4) 关注数据在不同存储层次间的移动,写出缓存友好的代码。这次知识体系化整理,为我深入理解操作系统、编译原理打下了坚实的第一步。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐