浅记一下,还有很多linux的知识要学,等想到要补充的东西再逐步完善这篇博客
可执行文件形成过程
每个源文件通过编译器生成.obj文件(目标文件)
然后目标文件和链接库通过链接器生成可执行文件。(比如win系统,最后就生成.exe文件)
而编译器做的事又有:预编译、编译、汇编
命令行操作
1. 预编译命令
gcc test.c -E
仅预编译(预处理)便停下,此时屏幕会出现预编译结果
gcc test.c -E -o test.i
-o
:output。输出。将结果输出到test.i的文件中
生成了test.i文件,这是一个中间文件
此时
vim test.i
可以看到里面就是预编译结果
test.i中是C语言
2. 编译命令
gcc test.i -S
仅编译便停下,生成test.s文件,里面是编译后的结果
test.s文件中是汇编语言
3. 汇编命令
gcc test.s -c
会生成test.o文件,其实就是目标文件
在windows系统下,目标文件是xxx.obj。
在linux系统下,目标文件是xxx.o
test.o里面是二进制指令,我们直接看是看不懂的
不过,xxx.o文件是elf格式
在linux系统下有一个工具readelf
可翻译里面写的是什么:
readelf test.o -s
但是我用的mac还不知道mac下有没有什么命令可以查看。
给定程序供分析
test.c
#include <stdio.h>
extern int Add(int, int);
int main() {
int a = 10;
int b = 20;
int c = Add(a, b);
printf("%d\n", c);
return 0;
}
add.c
int Add(int x, int y) {
return x + y;
}
结果如下:
预编译阶段
完成预处理指令,文本操作,此时仍是C语言代码
-
头文件包含
#include – 预处理指令。在test.i前面一大堆代码其实就是把头文件内容复制进去了
-
define定义符号的替换
#define – 预处理指令。在test.i中没有了#define这行,所有define定义的符号都被替换
-
注释删除
test.i中没有注释行
编译阶段
把C语言代码翻译成了汇编代码
- 词法分析
- 语法分析
- 语义分析
- 符号汇总
符号汇总会把全局变量符号都汇总出来
汇编阶段
把汇编指令翻译成了二进制指令
形成符号表
链接阶段
- 合并段表:把相同的东西合在一起
- 符号表的合并和重定位
关于符号表
-
形成符号表:简单来说就是让全局的符号形成一个符号和地址的表:比如
Add
,因为这个符号是要跨文件使用的,test.c
里要用,add.c
里也要用,这些符号是要形成符号表的,但是add.c
里面的临时变量x
,y
这些是不用的,因为它们只在add.c
里面的一个函数里能用 -
合并符号表
合并的意思就是,我们来找相同的符号。
比如Add
符号,链接器找到两个Add
。这时,链接器会发现,有一个Add
的地址是有效的,有一个是无效的,此时链接器会丢弃无效的那个地址,保留有效的地址。
其实简单来说就是把源文件弄在一起了。
如果我们把add.c里的的Add函数名改为ADD
int ADD(int x, int y) {
return x + y;
}
那么就会有如下报错
从2、3行可以看到其实此时目标文件
test-dfdbb1.o
都已经生成了,到了4行ld
阶段就不行了(ld是gcc的链接器)
linker command failed
说明链接阶段出了问题,就是因为符号表中找不到ADD
符号,链接失败了
像这种函数名、变量名写错的情况,都是在链接阶段失败了
程序执行的过程
程序执行的过程:
-
程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序
的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。 -
程序的执行便开始。接着便调用main函数。
-
开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回
地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程
一直保留他们的值。 -
终止程序。正常终止main函数;也有可能是意外终止。