Linux:gcc编译器 | 动静态库的创建与使用

文章目录

  • gcc/g++
  • gcc编译器
  • 编译链接:
    • file、ldd
  • 动态链接 vs 静态链接
    • 使用静态库
    • 使用共享库
    • 库的搜索路径
  • 参考

全文约 3036 字,预计阅读时长: 9分钟


gcc/g++

  • GCC:GNU Compiler Collection(GUN 编译器集合),它可以编译C、C++、JAV、Fortran、Pascal、Object-C等语言。
    • gcc是GCC中的GUN C Compiler(C 编译器)
    • g++是GCC中的GUN C++ Compiler(C++编译器)
  • 由于编译器是可以更换的,所以gcc不仅仅可以编译C文件。所以,更准确的说法是:gcc调用了C compiler,而g++调用了C++ compiler

gcc和g++的主要区别

  1. 对于 .c和.cpp文件,gcc分别当做c和cpp文件编译(c和cpp的语法强度是不一样的)
  2. 对于 .c和.cpp文件,g++则统一当做cpp文件编译
  3. 使用g++编译文件时,g++会自动链接标准库STL,而gcc不会自动链接STL
  4. gcc在编译C文件时,可使用的预定义宏是比较少的
  5. gcc在编译cpp文件时/g++在编译c文件和cpp文件时(这时候gcc和g++调用的都是cpp文件的编译器),会加入一些额外的宏。
  6. 在用gcc编译c++文件时,为了能够使用STL,需要加参数 –lstdc++ ,但这并不代表 gcc –lstdc++ 和 g++等价,它们的区别不仅仅是这个。

gcc编译器

gcc可以分步骤编译,预处理,编译,汇编,最后链接生成可执行文件。可执行文件在Linux系统上一般表现为ELF目标文件(OBJ文件)。

通过下列方式编译可以得到,每一步处理之后的过程文件:
Linux:gcc编译器 | 动静态库的创建与使用_第1张图片

  也可以一步到位,两种写法:第一种方法编译时需要所有文件重新编译;而第二种方法可以只重新编译修改的文件,未修改的文件不用重新编译
1、多个文件一起编译
2、分别编译各个源文件,之后对编译后输出的目标文件链接。

//源文件 test.c testfun.c
#gcc testfun.c test.c -o test  //多个文件一起编译

//分别编译
#gcc -c testfun.c //将testfun.c编译成testfun.o
#gcc -c test.c   //将test.c编译成test.o
#gcc -o testfun.o test.o -o test //将testfun.o和test.o链接成test

一些选项

  • -static 此选项对生成的文件采用静态链接
  • -shared 此选项将尽量使用动态库,所以生成文件比较小,但是需要系统有动态库.
  • -O0-3 编译器的优化选项的4个级别,-O0表示没有优化,-O1为缺省值,-O3优化级别最高。gcc -O1 test.c -o test
  • -w 不生成任何警告信息。
  • -Wall 生成所有警告信息。
  • -E :仅执行预处理(不要编译、汇编或链接)。
  • -S :只编译(不汇编或链接)。
  • -c :编译和汇编,但不链接。
  • -o :指定输出文件。
  • -pie :创建一个动态链接、位置无关的可执行文件。
  • -I :指定头文件的包含路径。
  • -L :指定链接库的包含路径。

编译链接:

  • 预处理功能主要包括宏替换,文件包含,条件编译,去注释等。
  • 编译(生成汇编):在这个阶段中,gcc 首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,gcc 把代码翻译成汇编语言。
  • 汇编(生成机器可识别代码):汇编阶段是把编译阶段生成的“.s”文件转成“.o”的二进制目标文件
  • 链接:生成可执行文件或库文件
    Linux:gcc编译器 | 动静态库的创建与使用_第2张图片

可执行目标文件格式

Linux:gcc编译器 | 动静态库的创建与使用_第3张图片
零碎的概念:

  1.符号解析:程序中有定义和引用的符号(包括变量名,函数名),存放在符号表(.symtab)。符号表是一个结构数组,包含符号名、长度和位置等信息。编译器将符号的引用存放在重定位节(.rel.text和.rel.data)。链接器将每个符号的引用都与一个确定的符号定义建立关联。

  2.重定位:将多个代码段和数据段分别合并为一个单独的代码段和数据段,计算每个定义的符号在虚拟地址空间的绝对地址。将可执行文件中的符号引用处修改为重定位后的地址信息。

Linux:gcc编译器 | 动静态库的创建与使用_第4张图片

  • 谈谈程序链接及分段那些事

file、ldd

  • 通过file 可执行文件ldd 可执行文件,可以看到文件的链接方式和动态链接库。
  • Linux操作系统下都是动态链接。
    Linux:gcc编译器 | 动静态库的创建与使用_第5张图片

动态链接 vs 静态链接

函数库:Linux下的大多数函数都默认将头文件放到/usr/include/目录下,而库文件则放到/usr/lib/目录下。

  我们的C程序中,并没有定义“printf”的函数实现,且在预编译中包含的“stdio.h”中也只有该函数的声明,而没有定义函数的实现,那么,是在哪里实“printf”函数的呢?

  最后的答案是:系统把这些函数实现都被做到名为 libc.so.6 的库文件中去了,在没有特别指定时,gcc 会到系统默认的搜索路径“/usr/lib”下进行查找,也就是链接到 libc.so.6 库函数中去,这样就能实现函数“printf”了,而这也就是链接的作用。

静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了。其后缀名一般为“.a”

动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库,这样可以节省系统的开销。动态库一般后缀名为“.so”,如前面所述的 libc.so.6 就是动态库。gcc 在编译时默认使用动态库。完成了链接之后,gcc 就可以生成可执行文件,

  • 静态链接生成的文件虽然可以独立运行,但大小几乎是动态链接的10倍,体积过大。
  • 动态链接运行时需要再加载,省内存,编译效率高,但如果某个库缺失,程序就会挂掉。
    Linux:gcc编译器 | 动静态库的创建与使用_第6张图片

使用静态库

  创建一个 foo.c 文件,内容如下:

#include void foo(void)
 {
     printf("Here is a static library\n");
 }

  ar压缩指令,将生成的目标文件打包成静态链接库。静态链接库的不能随意起名,需遵循如下的命名规则:libxxx.a

ar rcs 静态链接库名称 目标文件1 目标文件2 ...

  将 foo.c 编译成静态库 libfoo.a;

 gcc -c foo.c             //生成 foo.o 目标文件
 ar rcs libfoo.a foo.o    //生成 libfoo.a 静态库

  查看文件描述: file *;接着修改 hello.c 文件,调用 foo 函数

 #include void foo();
 int main()
 {
     printf("Hello, GetIoT\n");
     foo();
     return 0;
 }

  编译 hello.c 并链接静态库 libfoo.a(加上 -static 选项)

 gcc hello.c -static libfoo.a -o hello

  也可以使用 -L 指定库的搜索路径,并使用 -l 指定库名

 gcc hello.c -static -L. -lfoo -o hello

  运行:./hello,查看 hello 文件描述:file hello


使用共享库

修改 foo.c 文件,内容如下:

 #include void foo(void)
 {
     printf("Here is a shared library\n");
 }

  将其编译为动态库/共享库(由于动态库可以被多个进程共享加载,所以需要使用 -fPIC 选项生成位置无关的代码)。

  位置无关代码:可以直接加载而无需重定位的代码。主要是因为无论内存在何处加载目标模块,数据段和代码段的距离总是保持不变的。因此,代码段中的任意指令与数据段中的任意变量之间的距离在运行时都是一个常量,与代码和数据加载的绝对内存位置无关。

 gcc foo.c -shared -fPIC -o libfoo.so

  hello.c 代码无需修改,编译 hello.c 并链接共享库 libfoo.so;也可以使用 -L 和 -l 选项指定库的路径和名称。

 gcc hello.c libfoo.so -o hello

 gcc hello.c -L. -lfoo -o hello

  但是此时运行 hello 程序失败,原因是找不到 libfoo.so 共享库;这是因为 libfoo.so 并不在 Linux 系统的默认搜索目录中。解决办法是我们主动告诉系统,libfoo.so 共享库在哪里。

  方式一:设置环境变量 LD_LIBRARY_PATH。将 libfoo.so 所在的当前目录添加到 LD_LIBRARY_PATH 变量,再次执行 hello。 LD_LIBRARY_PATH 变量:指定函数库查找路径的,而且这个路径通常是在查找标准的路径之前查找。

 export LD_LIBRARY_PATH=$(pwd)

  方式二:使用 rpath 将共享库位置嵌入到程序。rpath 即 run path,是种可以将共享库位置嵌入程序中的方法,从而不用依赖于默认位置和环境变量。这里在链接时使用 -Wl,-rpath=/path/to/yours 选项,-Wl 会发送以逗号分隔的选项到链接器,注意逗号分隔符后面没有空格哦。

 gcc hello.c -L. -lfoo -Wl,-rpath=`pwd` -o hello

  这种方式要求共享库必须有一个固定的安装路径,欠缺灵活性,不过如果设置了 LD_LIBRARY_PATH,程序加载时也是会到相应路径寻找共享库的。

  方式三:将 libfoo.so 共享库添加到系统路径。如果 hello 程序仍然运行失败,请尝试执行 ldconfig 命令更新共享库的缓存列表。

 sudo cp libfoo.so /usr/lib/

 $ ldd hello
         linux-vdso.so.1 (0x00007ffecfbb1000)
         libfoo.so => /lib/libfoo.so (0x00007f3f3f1ad000)
         libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f3f3efbb000)
         /lib64/ld-linux-x86-64.so.2 (0x00007f3f3f1d6000)
//可以看到 libfoo.so 已经被发现了,其中 /lib 是 /usr/lib 目录的软链接。

库的搜索路径

-I 指定头文件路径;-L 指定库路径;-l(小写L) 指定库名。

/usr/bin/ld 搜索路径顺序。

静态库链接时搜索路径顺序

  1. ld会去找GCC命令中的参数-L
  2. 再找gcc的环境变量LIBRARY_PATH
  3. 再找内定目录 /lib /usr/lib /usr/local/lib 这是当初compile gcc时写在程序内的

动态链接时、执行时搜索路径顺序:

  1. 编译目标代码时指定的动态库搜索路径
  2. 环境变量LD_LIBRARY_PATH指定的动态库搜索路径
  3. 配置文件/etc/ld.so.conf中指定的动态库搜索路径
  4. 默认的动态库搜索路径/lib
  5. 默认的动态库搜索路径/usr/lib

有关环境变量

LIBRARY_PATH环境变量:指定程序静态链接库文件搜索路径

LD_LIBRARY_PATH环境变量:指定程序动态链接库文件搜索路径

配套问题

  为什么不直接把高级语言直接翻译成二进制呢?,,因为高级语言后出现,且再做一遍相关的转换工作,费时费力;所以先处理,在转成汇编就很方便。这是事情叫做站在前人的肩膀上;如果有什么bug,就叫做历史 遗留问题。

  编译器怎么直到我的代码是更新过的呢?修改文件的时候,会修改源文件时间窗,编译器会根据老的程序结合文件修改的时间进行对比,进而确定哪个文件被改过了,再编译特定的文件就可以了。


参考

  • C/C++语言编译链接过程
  • Carbon 漂亮的源代码语法高亮图片:
  • gcc 编译命令详解及最佳实践 - 阿基米东的文章 - 知乎
  • Linux系统-基础IO
  • 理清gcc、libc、libstdc++的关系
  • /usr/bin/ld 搜索路径顺序

你可能感兴趣的:(Linux,linux,服务器,c语言,c++,1024程序员节)