✅作者简介:大家好,我是橘橙黄又青,一个想要与大家共同进步的男人😉😉
🍎个人主页:橘橙黄又青-CSDN博客
今天学习:浅学编译和链接内部实现原理
前提:本文是在gcc编译环境下学习,目前只是浅学习
1. 翻译环境和运⾏环境
在ANSI C的任何⼀种实现中,存在两个不同的环境。
2. 翻译环境
如图:
.c文件生成可执行文件过程:
• 多个.c⽂件单独经过编译出编译处理⽣产对应的⽬标⽂件。• 注: 在Windows环境下的⽬标⽂件的后缀是 .obj ,Linux环境下⽬标⽂件的后缀是 .o• 多个⽬标⽂件和链接库⼀起经过链接器处理⽣成最终的可执⾏程序。• 链接库是指运⾏时库(它是⽀持程序运⾏的基本函数集合)或者第三⽅库。
2.1 预处理(预编译)
gcc -E test.c -o test.i
• 将所有的 #define 删除,并展开所有的宏定义。• 处理所有的条件编译指令,如: #if 、 #ifdef 、 #elif 、 #else 、 #endif 。• 处理#include 预编译指令,将包含的头⽂件的内容插⼊到该预编译指令的位置。这个过程是递归进 ⾏的, 也就是说被包含的头⽂件也可能包含其他⽂件 。• 删除所有的注释• 添加⾏号和⽂件名标识,⽅便后续编译器⽣成调试信息等。• 或保留所有的#pragma的编译器指令,编译器后续会使⽤。
来我们看看.i文件是怎么生成的呢?
这里我们发现有700多行代码,没错这里面的都是#include<stdio.h>里面的全部函数 等定义
输入指令生成test.i文件,文件名可以随便改.i文件为文件后缀就行,那我们举上面的两个例子
案例1:
外部函数包含例子
代码:
test.c文件
#include<stdio.h>
#include "test.h"
int g_val = 2024;
int main()
{
printf("hehe\n");
printf("%d\n", g_val);
return 0;
}
test.h 文件
int Add(int x, int y) {
return x + y;
}
生成de.i文件里面的内容:
案例2:
#denfinen的例子
预处理完之后MAX就变成1000,这都是在test.i文件里面完成的,所以说使用#define到后面是不利于调试的,因为调试是在.exe文件里面完成的,.i文件只是一个过渡阶段。使用后就删除了 。
2.2 编译
gcc -S test.i -o test.s
输入指令后。生成.s文件,.s文件是汇编指令。
2.2.1 词法分析:
array[index] = (index+4)*(2+6);
记号 | 类型 |
---|---|
array | 标识符 |
[ | 左方括号 |
index | 标识符 |
] | 右方括号 |
= | 赋值 |
( | 左圆括号 |
index | 标识符 |
+ | 加号 |
4 | 数字 |
) | 左圆括号 |
* | 乘号 |
( | 右圆括号 |
2 | 数字 |
+ | 加号 |
6 | 数字 |
) | 右圆括号 |
2.2.2 语法分析
2.2.3 语义分析
由语义分析器来完成语义分析,即对表达式的语法层⾯分析。编译器所能做的分析是语义的静态分 析。静态语义分析通常包括声明和类型的匹配,类型的转换等。这个阶段会报告错误的语法信息。
2.3 汇编
gcc -c test.s -o test.o
2.4 链接
#include <stdio.h>
//test.c
//声明外部函数
extern int Add(int x, int y);
//声明外部的全局变量
int main()
{
int a = 10;
int b = 20;
int sum = Add(a, b);
printf("%d\n", sum);
return 0;
}
Add.c代码:
int g_val = 2022;
int Add(int x, int y)
{
return x+y;
}
如图:
第2点的全部知识点图解: