目录
一、程序环境
程序的实现都要经过「编译阶段」、「链接阶段」,这 2 个过程分别对应 2 个不同的环境:「翻译环境」、「执行环境」
翻译环境:将源代码转换为可执行的机器指令
执行环境:将转换后的代码执行
二、编译阶段
编译阶段分为 3 个步骤:「预编译」「编译」「汇编」
1.预编译
对源文件做相应处理并将使用的头文件内容进行包含,生成「.i」后缀名文件
gcc 「文件名」 -E -o 「.i后缀文件名」
//将程序进行预编译并将预编译后的内容写入.i后缀新文件
//-E表示预编译,-o表示写入文件
//若不写入文件将会直接输出到终端,不好观察
查看 .i 文件,发现里面的内容比 .c 文件多很多,其中最后的部分是 做过处理后.c 文件中的代码,前面的部分是所包含头文件中的内容。
在 .i 文件中可找到所包含头文件的路径,打开头文件与 .i 文件对比,证明包含的是头文件。
由于包含头文件在预编译后会产生大量代码,所以要避免重复包含头文件。
2.编译
对 C 代码进行「语法分析」「词法分析」「符号汇总」「语义分析」后,转换为汇编代码
gcc 「.i文件」 -S
//对文件进行编译,执行完毕后出现「.s文件」
符号汇总:将全局名称进行汇总
3.汇编
对汇总的符号标识对应地址,形成符号表,将汇编代码转换为二进制指令
gcc 「.s文件」 -c
//对文件进行汇编,生成「.o文件」
三、链接阶段
合并段表、符号表,生成可执行程序
合并段表
「.o」文件中的数据是以 elf 格式存储的,elf 格式的数据存储模式为分段存储,这时可以将多个「.o」文件中相对应的段进行合并。
合并符号表
将多个「.o」文件生成的符号表进行合并
例:
当注释掉 add 函数再次运行程序,可发现编译器在链接期间报错,因为此时符号表中找不到有效地址来替换 add 的无效地址。
在 Linux 下,生成的可执行程序为「.elf」
四、预编译详解
1.预编译符号
常用于记录日志
__FILE__ //该文件路径
__LINE__ //当前行号
__DATE__ //当前日期
__TIME__ //当前时间
__STDC__ //若该编译器遵循ANSI C,其值为1,否则未定义
//所有下划线由两个组成
VS 未遵循 ANSI C 标准
2.命令行定义
在编译程序时定义符号,常用于在不同机器上做测试
-D 表示定义
3.条件编译
对宏(常量)进行判断,若满足条件则执行包含语句,类似 if 语句
(1)判断表达式
#if 「常量表达式」
//...
#elif 「常量表达式」
//...
#endif //结束
(2)判断是否定义
#if defined (标识符)
//等价于:
#ifdef 「标识符」
#if !defined (标识符)
//等价于:
#ifndef 「标识符」
不满足条件的语句将会在预编译时清除
4.头文件
头文件被包含几次,在预处理时就会写入几次该头文件
避免头文件重复包含
(1)在头文件中添加如下代码
#ifndef __TEST_H__ //判断是否定义
#define __TEST_H__ //若没定义则定义该头文件
int add(int ,int);
#endif //结束
预编译被包含的头文件名会改为:__「文件名大写」_H__,并且定义该文件名
(2)在头文件中声明只包含 1 次
#pragma once //声明只包含1次
int add(int ,int);
头文件的查找
(1)当使用库文件的方式「< >」包含头文件时,编译器直接到库文件路径下查找该头文件
(2)当使用本地的方式「“ ”」包含头文件时,编译器优先去本地路径下查找该头文件,若为找到,则到库文件路径下查找