用户工具


  • main.c
#include "a.h"

#define abc 5

int main() {
    add(abc, 5);
}
  • a.h
int add(int a, int b);
  • a.c
#include "a.h"
int add(int a, int b){
    return a+b;
}

预处理

  • 将宏出现的地方进行替换。main.c预处理后
  • 命令gcc -E main.c
int main() {
    add(5, 5); // 宏abc替换成了5
}

编译

  • 将预处理后的代码编译成汇编
  • 命令gcc -S main.c

汇编

  • 将汇编代码转成机器码(.o文件),.o文件有如下特点
    • 一个功能的机器码
    • 自己并不能被执行,常用于链接成可执行文件
  • 命令gcc -c main.c a.c

静态链接库(.a文件)就是一堆.o文件的集合

链接

  • 命令 ld main.o a.o
  • 汇编之后生成了main.o和c.o 两个文件(这2个文件并不能执行,因为main.o中并不知道add的逻辑在哪里。add的逻辑在a.o中)
  • 链接的作用是将main.o 和c.o中内容合并成一个可执行文件。步骤大致如下(省略了无关的步骤)
    • 创建一个新的文件(默认是a.out)写入main.o中的内容
    • 发现main.o依赖add方法
    • 链接器去其他.o文件中找add方法,找到后将add方法的字节码放入a.out中并记录偏移地址
    • 将所有调用add方法的地方,改成a.out中add方法的实际地址
    • 如果引用外部的变量也是上面这个过程

链接后就能定位到add方法的实现了

可执行文件结构

可执行文件。上面的.o 文件 a.out等都是分段存储的

  • ELF Header 文件头
    • 包含系统类型,程序入口地址等信息
  • .text 代码段
  • .data 数据段
  • .bss 段
    • 为全局未定义,静态未定义的变量保留空间
  • .rodata 只读数据段(对应编程中const 修饰符)