目录
前言
在ANSI C的任何一种实现中,存在两个不同的环境
第一种是翻译环境,把源代码转变成可执行的机器码
第二种是执行环境,负责执行代码
一、详解编译和链接
1.编译和链接的关系
我们用一张图来表示翻译的大概过程
每一个源文件都会单独通过编译器(cl.exe)生成目标文件,即后缀为obj的文件,然后链接器(link.exe)会把目标文件和链接库链接在一起,生成可执行文件,即.exe文件。这就是编译和链接的大概过程。链接库就是提供你使用的一些库函数的信息。现在的集成开发环境(IDE)可以包括编辑,编译,链接,调试等一系列功能。
2.编译和链接过程
编译过程分为预编译,编译和汇编三个过程,由于现在的很多编译器功能很完备了,想要更仔细了解,我们可以在Linux环境下面演示,这里用的是gcc编译器集。
2.1预编译过程
用命令(-E)让程序停在预编译刚结束的时候,这时候我们可以看见预编译过程会生成的一个文件
我们可以看见,除了自己写的main函数外,该文件还多了很多代码,其实这些代码就是包含的头文件里面的信息,但是我们发现注释都不见了,宏也会被替换。所以我们就可以知道预编译的功能—处理#include,即把头文件的信息包含进来;去除注释;替换宏。
我们可以发现预编译完成的都是一些文本操作。
2.2编译过程
用命令(-S)可以让代码在编译后停下来
这就是编译后生成的文件里面的信息,所以编译就是吧代码转换成汇编代码,不过这个过程包含了词法分析;语法分析;语义分析和符号汇总,这里讲解一下符号汇总,其他的可以到汇编原理课程学习。
那么什么是符号汇总呢?
我们知道,翻译过程会生成一个可执行程序(a.out),这是一个二进制文件,并且是elf格式,这个文件里有一个符号表,我们在Linux下面可以用一个readelf的工具来来查看这些符号。
#include<stdio.h>
int g_val=345;
int Add(int x,int y)
{
return x+y;
)
int main()
{
int a=4;
int b=6;
int c=Add(a,b);
printf("%d",c);
return 0;
}
查找上面这个程序的符号表,可以发现一些符号
江江,函数和全局变量的符号都被包含起来了,神奇吧。这就是符号汇总,为什么需要符号汇总呢?这和链接有很大关系。
2.3 汇编过程
汇编可不是生成汇编代码,而是把汇编代码转换成二进制文件,这就是可执行程序,它也是elf格式的。这个过程还会生成符号表,就是符号名+符号地址,说了这么久,符号表到底是干嘛的?我们接着往下看。
2.4 链接过程
链接主要实现两个功能:合并段表;合并符号表和重定位
在翻译结束后,生成了多个可执行程序,这些程序都是elf格式的,这种文件不同的数据是放在不同的段的,合并段表就是把相同的段合并在一起,生成新的可执行程序。
合并符号表就是把不同文件的符号合并在一起,如果有相同的符号,只要一个(重定位),且地址是有意义的(比如函数声明的符号名,函数定义的符号名,后者才是有意义的)。有时候程序调试会报出无法解析的外部符号,一般就是链接除了问题。
二.运行环境
程序执行的过程:
1.程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
2.程序的执行便开始。接着便调用main函数。
3.开始执行程序代码。这个时候程序将使用一个运行时堆栈(函数栈帧),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
4.终止程序。正常终止main函数;也有可能是意外终止。