1.翻译环境和运行环境
在ANSI C中,存在两个不同的环境:翻译环境和运行环境
- 翻译环境 :我们在编译器编写出的代码都是文本信息,机器是看不懂的,翻译环境主要是源代码转化为可执行的机器指令的地方
- 运行环境:实际执行代码的地方
2 .翻译环境
翻译环境主要是源代码转化为机器可执行指令的地方
这个过程又包含了:编译和链接两个组要过程
而编译又可以分为:预编译(预处理),编译,汇编三个阶段
2.1预编译(预处理)
gcc -E 文件名.c -o 文件名.i
1.宏替换
将所有的 #define 删除,并展开所有的宏定义
预处理器通常会找源码中的宏定义,将他们替换成相应的值或表达式。这包含使用#difine定义的常量,宏函数
#define PI 3.14159 #define MAX(a, b) ((a) > (b) ? (a) : (b)) double a = 5.0; double b = PI * b * bs; int x = 10, y = 20; int max_val = MAX(x, y);
PI会被替换成:3.14159,Max(x,y)会被替换成:((x) > (y) ? (x) : (y))
2.条件编译
处理所有的条件编译指令,如: #if、#ifdef、#elif、#else、#endif 。
3.文件包含
预处理器会将#include指令指向的文件内容插入到源代码中,这个过程是递归进行的,也就是说被包含的头⽂件也可能包含其他⽂件
#include <stdio.h> #include "myheader.h"
<stdio.h>是标准库头文件,而"myheader.h"是用户自定义的头文件。预处理器会查找这些文件,并将它们的内容复制到#include指令的位置。
4.删除所有注释
预处理器会删除源代码中的所有注释,无论是单行注释(//)还是多行注释(/* ... */)
5.添加⾏号和⽂件名标识
预处理器可以处理#line指令,它用于改变当前行号和文件名,这在某些情况下对于错误处理和调试是有用的。
例如:程序语法错误的时候,错误列表不仅输出了错误信息还输出了行号
6.处理其他预处理指令
还有一些其他的预处理的指令也会被一起处理
例如:#undef(用于取消宏定义) ,#pragma(用于提供特定的编译器指令)
2.2编译
编译阶段就是将预处理后的文件经过“词法分析,语法分析,语义分析,优化,代码生成”,一系列的操作生成相应的汇编文件
2.2.1词法分析
例如: array [index] = (index+ 4 )*( 2 + 6 );
上⾯程序进行词法分析后得到了16个记号 :
2.2.2语法分析
2.2.3语义分析
2.3汇编
汇编器就是将汇编码进一步转化成机器指令的地方,几乎每一条汇编码都对应一条机器指令。
汇编器根据据汇编指令和机器指令的对照表⼀⼀的进⾏翻译,也不做指令优化。
gcc -c test.s -o test.o
2.4链接
符号解析:链接器需要找到目标文件中引用的所有函数和变量的定义。如果定义在当前目标文件中,链接器将其内部链接;如果定义在其他目标文件或库中,链接器需要找到相应的定义,并确定其在最终程序中的地址。
重定位:链接器需要确定每个目标文件在内存中的位置,并修正目标文件中所有绝对地址和相对地址的引用,使得它们在最终的执行文件中是正确的。
3.运行环境
- 程序必须载⼊内存中。在有操作系统的环境中:⼀般这个由操作系统完成。在独⽴的环境中,程序 的载⼊必须由⼿⼯安排,也可能是通过可执⾏代码置⼊只读内存来完成。
- 程序的执⾏便开始。接着便调⽤main函数。
- 开始执⾏程序代码。这个时候程序将使⽤⼀个运⾏时堆栈(stack),存储函数的局部变量和返回 地址。程序同时也可以使⽤静态(static)内存,存储于静态内存中的变量在程序的整个执⾏过程 ⼀直保留他们的值。
- 终⽌程序。正常终⽌main函数;也有可能是意外终⽌。