目录
一、程序的翻译环境和执行环境
1.C语言代码运行需要存在两个环境
- 第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。
- 第2种是执行环境,它用于实际执行代码。
2.一个C程序的编译运行过程
- 组成一个程序的每个源文件通过编译过程分别转换成目标代码(object code)。
- 每个目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序。
- 链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人的程序库,将其需要的函数也链接到程序中
二、编译
编译分为预处理、编译和汇编三个过程
下面这个程序包含两个文件,我将详细讲解这个程序的编译过程
test.c
int Add(int a, int b)
{
return a + b;
}
function.c
#define A 20
#include<stdio.h>
int b = 10;
int main()
{
int a = A;
int c = Add(a, b);
printf("%d", c);
//加法运算
return 0;
}
1.预处理
详解预处理指令详见:(9条消息) 预处理指令_聪明的骑士的博客-CSDN博客
预处理会对代码先进行一系列操作方便后续编译,生成.i文件
程序在预处理阶段主要完成三个任务:头文件的包含、删除所有#define并替换所有的宏和标识符常量、删除所有条件编译指令、删除所有注释、添加行号和文件标识、保留#pragma指令
效果如下:
test.c
int Add(int a, int b)
{
return a + b;
}
function.c
…… 此处为stdio.h的内容
#define被删除
int b = 10;
int main()
{
int a = 20;A被替换
int c = Add(a, b);
printf("%d", c);
下面的注释被删除
return 0;
}
2.编译
将预处理后的代码进行词法分析、语法分析、语义分析、符号汇总以及后续优化产生相应的汇编语言,生成.s文件。
这里主要讲一下符号汇总,符号汇总主要就是把预处理代码中的函数名、全局变量统计出来做成一个表格。
test.o | function.o |
b(全局变量) | Add(函数名) |
main(函数名) | |
Add(函数名) |
等到一会儿链接的时候就可以通过这个表格检查函数是否正确定义
3.汇编
将汇编语言转化为二进制指令,此时生成的.o文件只有计算机可以看懂
最终生成test.o和function.o
三、链接
1.链接
链接阶段会进行合并段表和符号汇总两个操作从而形成一个可执行程序。
合并段表:编译器会把在汇编阶段生成的多个目标文件中相同格式的数据合并在一起,最终形成一个可执行程序。
在编译链接的过程中,文件后缀的变化是不一样的:
- 在windows系统中,文件的变化:.c -> .obj -> .exe(编译的三步一般一步到位)
- 在linux系统中,文件的变化:.c -> .i ->.s -> .o -> .out
2.符号汇总
在连个表中都存在Add函数名,链接器会自己确认哪一个位置是函数的定义,如果都不是,这个程序的编译就会出错。所以,链接是机器可以检查出函数是否没有定义的一步。
test.o | function.o |
b(全局变量) | Add(函数名)(经过检查这个是定义) |
main(函数名) | |
Add(函数名) |
四、程序的执行环境
程序执行的过程:
- 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
- 程序的执行便开始。接着便调用main函数。
- 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。
- 程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
- 终止程序。正常终止main函数;也有可能是意外终止。