文章目录
一、gcc的使用
(一)gcc中的命令
(1) ld:主要用于链接
(2) ar:主要用于创建静态库。
(3) ldd:可以用于查看一个可执行程序依赖的共享库。
(4)objcopy:将一种对象文件翻译成另一种格式,譬如将.bin 转换成.elf、或
者将.elf 转换成.bin 等。
(5) objdump:主要的作用是反汇编。有关反汇编的详细介绍,请参见后文。
(6) readelf:显示有关 ELF 文件的信息,请参见后文了解更多信息。
(7) size:列出可执行文件每个部分的尺寸和总尺寸,代码段、数据段、总大小等,请参见后文了解使用 size
(二)使用gcc的编译过程
1.预处理
预处理的过程主要包括以下过程:
(1) 将所有的#define 删除,并且展开所有的宏定义,并且处理所有的条件预编
译指令,比如#if #ifdef #elif #else #endif 等。
(2) 处理#include 预编译指令,将被包含的文件插入到该预编译指令的位置。
(3) 删除所有注释“//”和“/* */”。
(4) 添加行号和文件标识,以便编译时产生调试用的行号及编译错误警告行号。
(5) 保留所有的#pragma 编译器指令,后续编译过程需要使用它们。
使用 gcc 进行预处理的命令如下:
gcc -E hello.c -o hello.i
// 将源文件 hello.c 文件预处理生成 hello.i
// GCC 的选项-E 使 GCC 在进行完预处理后即停止
2.编译
编译过程就是对预处理完的文件进行一系列的词法分析,语法分析,语义分析及
优化后生成相应的汇编代码。
使用 gcc 进行编译的命令如下:
$ gcc -S hello.i -o hello.s
// 将预处理生成的 hello.i 文件编译生成汇编程序 hello.s
// GCC 的选项-S 使 GCC 在执行完编译后停止,生成汇编程序
3.汇编
汇编过程调用对汇编代码进行处理,生成处理器能识别的指令,保存在后缀为.o的目标文件中。由于每一个汇编语句几乎都对应一条处理器指令,因此,汇编相对于编译过程比较简单,通过调用 Binutils 中的汇编器 as 根据汇编指令和处理器指令的对照表一一翻译即可。
当程序由多个源代码文件构成时,每个文件都要先完成汇编工作,生成.o 目标
文件后,才能进入下一步的链接工作。注意:目标文件已经是最终程序的某一部分了,但是在链接之前还不能执行。
使用 gcc 进行汇编的命令如下:
gcc -c hello.s -o hello.o
// 将编译生成的 hello.s 文件汇编生成目标文件 hello.o
// GCC 的选项-c 使 GCC 在执行完汇编后停止,生成目标文件
//或者直接调用 as 进行汇编
as -c hello.s -o hello.o
//使用 Binutils 中的 as 将 hello.s 文件汇编生成目标文件
注意:hello.o 目标文件为 ELF(Executable and Linkable Format)格式的可重定向文件
4.链接
链接也分为静态链接和动态链接,其要点如下:
(1) 静态链接是指在编译阶段直接把静态库加入到可执行文件中去,这样可执行文件会比较大。链接器将函数的代码从其所在地(不同的目标文件或静态链接库中)拷贝到最终的可执行程序中。为创建可执行文件,链接器必须要完成的主要任务是:符号解析(把目标文件中符号的定义和引用联系起来)和重定位(把符号定义和内存地址对应起来然后修改所有对符号的引用)。
(2) 动态链接则是指链接阶段仅仅只加入一些描述信息,而程序执行时再从系统中把相应动态库加载到内存中去。
① 在 Linux 系 统中,gcc 编译链接时的动态库搜索路径的顺序 通常为:首先从 gcc 命令的参数-L 指定的路径寻找 ;再从环境变量LIBRARY_PATH 指 定的路径寻址;再从默认路径 /lib、/usr/lib、/usr/local/lib 寻找 。
② 在 Linux 系 统中,执行二 进制 文件时的动态库搜索路径的顺序通常为:首 先搜 索编译目标代码时指定的动态库搜索路径;再从环境变量 LD_LIBRARY_PATH 指定的路径寻址;再从配置 文件/etc/ld.so.conf 中指定 的动态库搜索路径 ;再从默 认路径/lib、/usr/lib寻找 。
③ 在 Linux 系统 中,可以 用 ldd 命令查看 一个可执行程序依赖的共享库。由于链接动态库和静态库的路径可能有重合,所以如果在路径中有同名的静态库文件和动态库文件,比如 libtest.a 和 libtest.so,gcc 链接时默认优先选择动态库,会链接libtest.so,如果要让 gcc 选择链接 libtest.a 则可以指定 gcc 选项-static,该选项会强制使用静态库进行链接。
二、使用gcc生成静态库与动态库
(一)静态库
1.使用vim新建hello.h、hello.c以及main.文件
①hello.h
#ifndef HELLO_H
#define HELLO_H
void hello(const char*name);
#endif //HELLO_H
②hello.c
#include<stdio.h>
void hello(const char*name)
{
printf("Hello%s\n",name);
}
③main.c
#include "hello.h"
int main()
{
hello("everyone");
return 0;
}
2.使用gcc编译hello.c文件
3.根据编译后的.o文件创建静态库
静态库文件名的命名规范是以 lib 为前缀,紧接着跟静态库名,扩展名为.a。例如:我们将创建的静态库名为 myhello,则静态库文件名就是libmyhello.a。在创建和使用静态库时,需要注意这点。创建静态库用 ar 命令。在系统提示符下键入以下命令将创建静态库文件
ar -crv libmyhello.a hello.o
4. 静态库的使用
在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后在用 gcc 命令生成目标文件时指明静态库名,gcc 将会从静态库中将公用函数连接到目标文件中。注意,gcc 会在静态库名前加上前缀 lib,然后追加扩展名.a 得到的静态库文件名来查找静态库文件
方法一:
gcc -o hello main.c -L. –lmyhello
自定义的库时,main.c 还可放在-L.和 –lmyhello 之间,但是不能放在它俩之后,否则会提示 myhello 没定义,但是是系统的库时,如 g++ -o main(-L/usr/lib) -lpthread main.cpp就不出错。
方法二:
gcc main.c libmyhello.a -o hello
运行hello程序试一下
我们删除静态库文件试试公用函数 hello 是否真的连接到目标文件 hello 中了。
rm libmyhello.a
静态库中公用函数已连接到目标文件中
(二)动态库
1. 由.o文件生成动态库
动态库文件名命名规范和静态库文件名命名规范类似,也是在动态库名增加前缀 lib,但其文件扩展名为.so。例如:我们将创建的动态库名为 myhello,则动态库文件名就是libmyhello.so用 gcc 来创建动态库。在系统提示符下键入以下命令得到动态库文件 libmyhello.so
gcc -shared -fPIC -o libmyhello.so hello.o
2. 动态库的使用
在程序中使用动态库和使用静态库完全一样,也是在使用到这些公用函数的源程序中包含
这些公用函数的原型声明,然后在用 gcc 命令生成目标文件时指明动态库名进行编译。我
们先运行 gcc 命令生成目标文件,再运行它看看结果。
gcc -o hello main.c -L. -lmyhello
程序在运行时,会在/usr/lib 和/lib 等目录中查找需要的动态库文件。若找到,则载入动态库,否则将终止程序运行。我们需要将文件 libmyhello.so 复制到目录/usr/lib 中
sudo cp libmyhello.so /usr/lib
运行成功
(三)静态库与动态库同名时,gcc优先使用哪个文件
1. 删除多余文件
将文件删除只剩.c与.h文件
sudo rm -f hello hello.o /usr/lib/libmyhello.so
再来创建静态库文件 libmyhello.a 和动态库文件 libmyhello.so。
gcc -c hello.c
ar -cr libmyhello.a hello.o
gcc -shared -fPIC -o libmyhello.so hello.o
通过上述最后一条 ls 命令,可以发现静态库文件 libmyhello.a 和动态库文件 libmyhello.so 都已经生成,并都在当前目录中。然后,我们运行 gcc 命令来使用函数库 myhello 生成目标文件 hello,并运行程序 hello
gcc -o hello main.c -L. -lmyhello
当静态库和动态库在同路径、同名的情况下,gcc 命令将优先使用动态库,默认去连/usr/lib 和/lib 等目录中的动态库。
如果要让 gcc 选择链接 libtest.a 则可以指定 gcc 选项-static,该选项会强制使用静态库进行链接。
补充 1:编译参数解析
最主要的是 GCC 命令行的一个选项:
-shared 该选项指定生成动态连接库(让连接器生成 T 类型的导出符号表,有时候也生成弱连接 W 类型的导出符号),不用该标志外部程序无法连接。相当于一个可执行文件。
-fPIC 表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。
-L. 表示要连接的库在当前目录中;(多个库:在编译命令行中,将使用的静态库文件放在源文件后面就可以了。比如:
gcc -L/usr/lib myprop.c libtest.a libX11.a libpthread.a -o myprop其中-L/usr/lib 指定库文件的查找路径。编译器默认在当前目录下先查找指定的库文件,如前面的“法二 #gcc main.c libmyhello.a -o hello”)
-lmyhello 编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上 lib,后面加上.so 或.a 来确定库的名称 libmyhello.so 或 libmyhello.a。
LD_LIBRARY_PATH 这个环境变量指示动态连接器可以装载动态库的路径。
当然如果有 root 权限的话,可以修改/etc/ld.so.conf 文件,然后调用 /sbin/ldconfig 来达到同样的目的,不过如果没有 root 权限,那么只能采用输出LD_LIBRARY_PATH 的方法了。
调用动态库的时候有几个问题会经常碰到,有时,明明已经将库的头文件所在目录 通过 “-I”include 进来了,库所在文件通过 “-L”参数引导,并指定了“-l”的库名,但通过 ldd 命令察看时,就是死活找不到你指定链接的 so 文件,这时你要作的就是通过修改LD_LIBRARY_PATH 或者/etc/ld.so.conf 文件来指定动态库的目录。通常这样做就可以解决库无法链接的问题了。
补充 2:
从上述可知,如何找到生成的动态库有 3 种方式:
(1)把库拷贝到/usr/lib 和/lib 目录下。
(2)在 LD_LIBRARY_PATH 环境变量中加上库所在路径。
例如动态库 libhello.so 在/home/example/lib 目录下:
$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/example/lib
(3) 修改/etc/ld.so.conf 文件,把库所在的路径加到文件末尾,并执行 ldconfig 刷新。这样,加入的目录下的所有库文件都可见。
附:像下面这样指定路径去连接系统的静态库,会报错说要连接的库找不到:
g++ -o main main.cpp -L/usr/lib libpthread.a
必须这样 g++ -o main main.cpp -L/usr/lib -lpthread 才正确 。
自定义的库考到/usr/lib 下时,
g++ -o main main.cpp -L/usr/lib libpthread.a libthread.a libclass.a 会出错,但是这样 g++ -o main main.cpp -L/usr/lib -lpthread -lthread -lclass 就正确了。