一. 程序的翻译和执行环境
1 翻译环境 在环境中源代码被转换为可执行的机器指令
2 执行环境 它用于实际执行代码
运行的结果大概是这样
1.1 翻译环境
编译器是一个集成开发环境 它会生成目标文件
编译之后通过连接器生成可执行目标文件
这里我们详细介绍下编译的过程
1.1.1 预处理
我们要观察预处理的过程就要首先执行这一条指令
gcc test.c -E -o test.i • 1
这样子我们前面一条指令的结果就放到test.i 里面去了
打开之后我们发现竟然有八百多行的数据
这里我们可以发现 其实
头文件的包含是在预处理这一步完成的(#include)
宏的替换是在预处理这一步完成的
注释的消除也是在预处理这一步完成的
1.1.2 编译
想要在编译这一步停止 我们要使用这样的linux命令
gcc test.i test.s
这里让我们生成了一个test.s文件
我们打开来看看
我们可以发现 这里面其实就是汇编代码
所以说在汇编这一步
将所有的代码 变成了汇编代码 并且进行
1 语法分析
2 词法分析
3 语义分析
4 符号汇总
1.1.3 汇编
同样的 我们执行下面的命令 让代码进行汇编
gcc test.s -C • 1
我们可以发现能得到一个test.o文件
我们打开这个文件看看
我们会发现 一堆乱码
这是因为
在汇编阶段
所有程序变成了二进制语言
在liunx环境下
test.o 就是一个可执行文件
我们可以使用readelf来读这个文件
readelf test.o -s
我们可以发现会出现上面的一些符号 这些都是符号表
1.1.4 连接
连接之后会生成一个可执行程序
它做的操作实际上是
1 合并段表
2 符号表的合并和重定位
1.2 运行环境
程序执行的过程
1 程序必须载入内存中 在有操作系统的环境中 一般由操作系统来完成
2 程序执行便开始 接着调用main函数
3 开始执行 使用运行时堆栈 存储函数的局部变量和返回地址 程序同时也可以使用静态内存 储存静态内存中变量在程序整个执行过程中一直保留它们的值
4 终止程序 正常终止main函数 也可能意外终止
二. 预处理详解
2.1 预定义符号
FILE //进行编译的源文件
LINE //文件当前的行号
DATE //文件被编译的日期
TIME //文件被编译的时间
FUNCTION //文件当前所在的函数
STDC //如果编译器遵循ANSI C标准,其值为1,否则未定义
这些预定义符号都是语言内置的
我们来使用下上面的语句
int main() { int i = 0; for ( i = 0; i < 10; i++) { printf("File: %s Linr: %d date:%s time:%s i=%d\n", __FILE__, __LINE__, __DATE__, __TIME__,i); } return 0; }
这样我们就可以知道 函数在什么时间 什么地点 发生了什么
2.2 #define 定义标识符号
define可以定义符号
例如
#define NUM 100+200 #define STR "abcdef" int main() { int num = NUM; char* str = STR; return 0; }
在预处理其实NUM就变成了 100+200
STR就变成了“abcdef”
这里注意的是define是不能加分号的 否则替换到源文件里面会出现各种问题
2.3 #define 定义宏
我们举个例子
#define Max(x,y) (x>y?x:y) int main() { int a = 10; int b = 20; int c =Max(a, b); printf("%d", c); return 0; }
这里 它就是一个宏 它允许把参数替换到文本中去 这种实现通常称为宏
这里我们要计算11的平方 计算出来的结果确是21 这是怎么回事呢?
原来啊 在宏定义的时候 数据替换并不会经过计算 实际上我们的表达式是这样子的
10 + 1 * 10 + 1; • 1
当然最后的结果就是21啦
这件事告诉我们在定义宏的时候一定要注意括号
所以说 正确的定义方式应该是这样子
#define S(x) ((x)*(x))
这里还有要注意的一点 注释中的宏是不会被替换的
2.4 #define 的副作用
我们写出以下代码
#define Max(x,y)((x)>(y))?(x):(y) int main() { int a = 5; int b = 8; int c= Max(a++, b++); printf("%d\n", a); printf("%d\n", b); printf("%d\n", c); return 0; }
这里输出结果如下
我们可以发现 其实b是++了两次的
这里就是宏的一个小缺陷
2.5 宏和函数对比
宏通常用于比较简单的运算
例如求两个数中的较大值
为什么这样子呢?
因为
1 宏在规模和速度方面更胜一筹
2 函数必须要声明类型 而宏不用
当然宏也有缺点
每次使用宏的时候一份宏的代码将会被插入到程序中 除非宏比较短 可能大幅度增加程序的长度
2 宏无法调试
3 宏无类型定义 不够眼睛
4 宏可能会带来操作符优先级的问题 导致程序出错
以上就是本篇博客的全部内容啦 由于博主才疏学浅 所以难免会出现纰漏 希望大佬们看到错误之后能够不吝赐教 在评论区或者私信指正 博主一定及时修正
那么大家下期再见咯