separate compilation
)"的程序自动执行的// main.c
int sum(int *a, int *n);
int array[2] = {1, 2};
int main()
{
int val = sum(array, 2);
return val;
}
// sum.c
int sum(int *a, int n)
{
int i, s = 0;
for (i = 0; i < n; i++) {
s += a[i];
}
return s;
}
C
预处理器(cpp
),将源程序翻译成一个ASCII
码的中间文件main.i
C
编译器(ccl
),将main.i
翻译成一个ASCII
汇编语言文件main.s
as
,将main.s
翻译成一个“可重定位目标文件(relocatable object file)
” main.o
sum.o
ld
,将main.o
和sum.o
以及一些必要的系统目标文件组合起来,创建一个可执行目标文件executable object file
prog
ld -o prog [system object files and args] main.o sum.o
reference to Page 467 to understand
可重定位目标文件里的内存内容,比如ELF
,.text, .rodata, .data, .bss, .symtab, .rel.text, .rel.data, .debug
等内容
ps: 初始化的全局和静态C变量存在 .data里。
局部 C 变量在运行时被保存在栈中;
未初始化的全局和静态C变量,以及所有被初始化为0的全局或静态变量保存在 .bss中,且不占据实际空间,仅仅是一个占位符。
区分初始化和非初始化是为了空间效率: 在目标文件中,未初始化变量不需要占据任何实际的磁盘空间。运行时,在内存中分配这些变量,初始值为0
.debug
: 一个调试符号表,只有以 -g
选项调用编译器驱动程序时,才会得到这个表
C++ and JAVA
都允许重载方法,这些方法在源代码中有相同的名字,却有不通的参数列表。这是编译器将每个唯一的方法和参数列表组成编码成一个对链接器来说唯一的名字。相关函数被编译为独立的目标模块,然后封装成一个单独的静态库文件。
linux> gcc main.c xxx.a bbb.a
,在链接时,链接器将只赋值被程序引用的目标模块(函数),这就减少了可执行文件在磁盘和内存中的大小。
编译静态库示例
linux> gcc -c func1.c func2.c
linux> ar rcs libfunc.a func1.o func2.o
// 编写一个头文件func.h,里面时对两个.c文件中函数的声明, 并在 main.c 中包含这个头文件
linux> gcc -c main.c
linux> gcc -static -o prog main.o -L. -lfunc
or
linux> gcc -static -o prog main.o ./libfunc.a
-static
高速编译器驱动程序,链接器应该构建一个完全链接的可执行目标文件, 它可以加载到内存并运行,在加载时无需更进一步的链接
-lfunc
参数是libfunc.a
的缩写
-L.
参数高速链接器在当前目录下(.
)查找libfunc.a
reference to page 477
链接器的主要任务是将程序的不同部分(如编译后的代码模块、库文件、目标文件等)合并成一个单一的可执行文件或库文件。这个过程涉及到几个关键步骤,包括解析头文件中的引用和库文件或目标文件中的定义。以下是链接器如何工作的详细说明:
解析符号引用:
头文件中的声明(如函数原型和变量声明)在编译时被转换为符号引用,这些引用在目标文件中被保留。
如果定义一个符号的库,出现在引用这个符号的目标文件之前,引用就不能被解析,链接会失败
是一个目标模块,在运行或加载时,可以加载到任意的内存地址,并和一个在内存中的程序链接起来。这个过程称为动态链接,由一个叫动态链接器的程序执行。
一个库只有一个.so
文件。所有引用该库的可执行目标文件共享这个 .so
文件中的代码和数据,而不是像静态库的内容那样被复制和嵌入到引用他们的可执行文件中。其次,在内存中,一个共享库的.text
节的一个副本,可以被不通的正在运行的进程共享。(reference chatper 9)
linux> gcc -shared -fpic -o libfunc.so func1.c func2.c
-fpic
指示编译器生成与位置无关的代码
-shared
指示链接器创建一个共享的目标文件。
linux> gcc -o prog main.c ./libfunc.so
每一个Linux
程序都有一个运行时内存影响。从地址
2
48
2^{48}
248开始(示例),是为内存中的代码和数据保留的,也就是所谓的内核。