gcc编译过程详解 静态和动态链接库

1. gcc介绍
早期 GCC 的全拼为 GNU C Compiler,即 GUN 计划诞生的 C 语言编译器。
GCC 的功能得到了很大的扩展,它不仅可以用来编译 C 语言程序,还可以处理 C++、Go、Objective -C 等多种编译语言编写的程序。
重新定义为 GNU Compiler Collection,即 GNU 编译器套件。

GCC的组成部分
c++ gcc 的一个版木,默认语言设置为 C++,而且在连接的时候自动包含标准 C++ 库。这和 g++ 一样
ccl 实际的C编译程序
cclplus 实际的 C++ 编泽程序
collect2 在不使用 GNU 连接程序的系统上,有必要运行 collect2 来产生特定的全局初始化代码(例如 C++ 的构造函数和析构函数)
configure GCC 源代码树根目录中的一个脚木。用于设置配置值和创建GCC 编译程序必需的 make 程序的描述文件
crt0.o 这个初始化和结束代码是为每个系统定制的,而且也被编译进该文件,该文件然后会被连接到每个可执行文件中来执行必要的启动和终止程序
cygwin1.dll Windows 的共享库提供的 API,模拟 UNIX 系统调用
f77 该驱动程序可用于编译 Fortran
f771 实际的 Fortran 编译程序
g++ gcc 的一个版木,默认语言设置为 C++,而且在连接的时候自动包含标准 C++ 库。这和 c++ 一样
gcc 该驱动程序等同于执行编译程序和连接程序以产生需要的输出
gcj 该驱动程序用于编译 Java
gnat1 实际的 Ada 编译程序
gnatbind 一种工具,用于执行 Ada 语言绑定
gnatlink 一种工具,用于执行 Ada 语言连接
jc1 实际的 Java 编译程序
libgcc 该库包含的例程被作为编泽程序的一部分,是因为它们可被连接到实际的可执行程序中。 它们是特殊的例程,连接到可执行程序,来执行基木的任务,例如浮点运算。这些库中的例程通常都是平台相关的
libgcj 运行时库包含所有的核心 Java 类
libobjc 对所有 Objective-C 程序都必须的运行时库
libstdc++ 运行时库,包括定义为标准语言一部分的所有的 C++ 类和函数

2. 预处理
我们先来看一段代码:

#include <stdio.h>


void test_gcc()
{
	printf("test gcc\n");
}
int main()
{
	test_gcc();
	return 0;
}

我们在Linux命令行中敲:
gcc -o test1.c -E test.c
先来解释一下这个命令:
-o:指定生成文件的文件名
test1.c:预编译产生的文件
-E:指明要进行预编译
test.c:源文件

我们查看产生的文件test1.c,可以发现,test1.c比源文件多了很多定义,这些定义是来自于#include <stdio.h>,也就是说预编译就是把包含的头文件stdio.h的内容原封不动的复制到add.c中的#include的位置,另外还会处理一些预处理命令,比如#define等。

我们来看看预编译有哪些常用的编译选项:
GCC选项 描述
-E(大写) 预处理指定的源文件,不进行编译。
-S(大写) 编译指定的源文件,但是不进行汇编。
-c 编译、汇编指定的源文件,但是不进行链接。
-o 指定生成文件的文件名。
-llibrary(-I library) 其中 library 表示要搜索的库文件的名称。该选项用于手动指定链接环节中程序可以调用的库文件。建议 -l 和库文件名之间不使用空格,比如 -lstdc++。
-ansi 对于 C 语言程序来说,其等价于 -std=c90;对于 C++ 程序来说,其等价于 -std=c++98。
-std= 手动指令编程语言所遵循的标准,例如 c89、c90、c++98、c++11 等。

3. 编译
所谓编译是指对预处理文件进行一系列词法分析,,语法分析和语义分析:
(1)词法分析主要分析关键字,,标示符 标示符,,立即数等是否合法 立即数等是否合法
(2)语法分析主要分析表达式是否遵循语法规则
(3)语义分析在语法分析的基础上进一步分析表达式是否合法
分析结束后进行代码优化生成相应的汇编代码文件
编译命令:
gcc –S test.c –o test.s
先来解释一下这个命令:
-S:指明进行编译处理
-o:指明输出文件
test.s:编译产生的文件
查看test.c可以看到test.c是一个汇编代码文件

4. 汇编
汇编器将汇编代码转变为机器可以执行的指令
每个汇编语句几乎都对应一条机器指令
汇编指令:
gcc –c test.s –o test.o
-c:用于指明进行进行汇编
test.s:源文件
-o:指明输出文件
test.o:输出文件
我们可以看到test.o是一个二进制文件,也就是可执行文件

5. 链接
连接器的主要作用是把各个模块之间相互引用的部分
处理好,,使得各个模块之间能够正确的衔接 使得各个模块之间能够正确的衔接
gcc test.o -o main
这里是将test.o链接成main可执行程序

注意:当同时编译多个文件时,上述命令同样适用
例如:
将多个 C(C++)源文件加工为汇编文件或者目标文件;
将多个 C(C++)源文件或者预处理文件加工为汇编文件或者目标文件;
将多个 C(C++)源文件、预处理文件或者汇编文件加工为目标文件;
同一项目中,不同的源文件、预处理文件、汇编文件以及目标文件,可以使用一条 gcc 指令,最终生成一个可执行文件。

6. 静态链接和动态链接

以下面这个图来简单说明一下从静态链接到可执行文件的过程,根据在源文件中包含的头文件和程序中使用到的库函数,如stdio.h中定义的printf()函数,在libc.a中找到目标文件printf.o(这里暂且不考虑printf()函数的依赖关系),然后将这个目标文件和我们test.o这个文件进行链接形成我们的可执行文件。

在这里插入图片描述

静态链接的缺点很明显,一是浪费空间,因为每个可执行程序中对所有需要的目标文件都要有一份副本,所以如果多个程序对同一个目标文件都有依赖,如多个程序中都调用了printf()函数,则这多个程序中都含有printf.o,所以同一个目标文件都在内存存在多个副本;另一方面就是更新比较困难,因为每当库函数的代码修改了,这个时候就需要重新进行编译链接形成可执行程序。但是静态链接的优点就是,在可执行程序中已经具备了所有执行程序所需要的任何东西,在执行的时候运行速度快。

动态链接:
动态链接的基本思想是把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件。下面简单介绍动态链接的过程:
假设现在有两个程序program1.o和program2.o,这两者共用同一个库lib.o,假设首先运行程序program1,系统首先加载program1.o,当系统发现program1.o中用到了lib.o,即program1.o依赖于lib.o,那么系统接着加载lib.o,如果program1.o和lib.o还依赖于其他目标文件,则依次全部加载到内存中。当program2运行时,同样的加载program2.o,然后发现program2.o依赖于lib.o,但是此时lib.o已经存在于内存中,这个时候就不再进行重新加载,而是将内存中已经存在的lib.o映射到program2的虚拟地址空间中,从而进行链接(这个链接过程和静态链接类似)形成可执行程序。

下面以一个例子来说明静态链接和动态链接的区别:
还是以test.c为例子:

#include <stdio.h>


void test_gcc()
{
	printf("test gcc\n");
}
int main()
{
	test_gcc();
	return 0;
}

我们先将其编译生成test.o:
gcc -c test.c
再用 ar cr lib_test.a test.o 来生成一个静态库
lib_test.a是我们要用的静态链接库

接下来我们还是以test.c为例编译产生动态链接库:
使用 gcc -fpic -shared -o lib_test.so test.c 来生成一个动态库 lib_test.so
然后使用一个main.c来调用这两个库:

#include "test.h"
 
int main()
{
   test_gcc();
   return 0;
}

#ifndef TEST_H
#define TEST_H
 
void test_gcc();
 
#endif 

使用 gcc -o main main.c ./lib_test.a 来生成可执行程序 main。然后使用 nm main 查看它的符号情况,如图:
在这里插入图片描述
从图中可以看到 test_gcc 符号的类型为 T(说明在 .text 段里),这说明引用的是静态库里的 test_gcc 符号。

第二次,我使用 gcc -o main main.c ./lib_test.so 来生成可执行文件 main, 再次使用 nm main来查看里面符号的情况,如图:
在这里插入图片描述
可以看到 foobar 的符号类型是 U (未定义),说明引用的是 lib_test.so 动态库里的符号。

7. C文件和makefile:

test.c:
#include <stdio.h>


void test_gcc()
{
	printf("test gcc\n");
}

test.h:

#ifndef TEST_H
#define TEST_H
 
void test_gcc();
 
#endif 

main.c:
#include "test.h"
 
int main()
{
   test_gcc();
   return 0;
}
makefile:

main:
	gcc -c test.c
	ar cr lib_test.a test.o
	gcc -fpic -shared -o lib_test.so test.c
	gcc -o main main.c ./lib_test.so
	gcc -o main2 main.c ./lib_test.a

clean:
	rm main main2 lib_test.a test.o lib_test.so
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值