:C语言库,第2部分,动态库
编译器: gcc(Ubuntu 4.8.4–2ubuntu1〜14.04.3)4.8.4
环境:适用于Ubuntu的带有Linux 14.04.5 LTS的无所不在的虚拟机
语言: C语言
有关该主题的背景信息,有助于理解编译C语言文件时会发生什么; 有关更多信息,请查看我的另一篇博客文章: 计算机编译器:简介 ,这有助于解释gcc编译器的工作方式。 由于这是我撰写的另一篇博客文章的延续,因此请参阅我关于库的文章: f * Lib.a是什么? 有关静态库的更多信息。 这是该博客的简短摘要:
为什么使用图书馆及其运作方式
库文件通常包含应用程序或程序员最常使用的功能代码,或者属于一组独特的功能,这些功能共同作用以满足可以在各种不同应用程序中使用的更大最终目标。 因此,用C语言编写的库通过链接那些通用函数来帮助程序员节省时间,而不必每次程序员在不同的应用程序和情况下使用它们时都必须对那些相同的函数进行硬编码。 此外,通过将最常用的功能分离到库中,它可以帮助程序员保持井井有条。 程序员可以在一个库中共享一组功能,而无需将任何其他独特代码也添加到该库中。 另一个优点是能够在不影响main()函数或可执行文件的情况下修改库文件。 例如,如果程序员曾经从库中更新代码,则可以进行这些更改而不会影响使用该库功能的可执行文件,而不必将更改集成到使用该库的所有文件中; 这取决于编译程序和链接库的方式。 在gcc的链接阶段中,即在预处理器阶段之后,即头文件实际复制到源代码中之后,库被动态或静态地“链接”。
如何创建动态库(仅Linux)
在下面的代码片段中,您将看到如何创建动态库以及如何使用动态库。 由于我是旧金山霍尔伯顿学校的学生,并且由于他们的课程教会我们学生创建自己的C库,因此我发现C库非常有用,因此我在下面的示例中向他们大喊大叫。
源代码
首先,这里列出了我的’.c’文件和具有唯一头文件’.h’的函数,这些文件将代替C标准库使用。 我还用cat
来显示每个文件中的代码。
$ ls _strncpy.c puts.c holberton.h _putchar.c $ cat holberton.h int _putchar(字符c); 无效_puts(char * s); char * _strncpy(char * dest,char * src,int n); $ cat _putchar.c #包括 int _putchar(字符c) { return(write(1,&c,1)); } $猫_strncpy.c #include“ holberton.h” char * _strncpy(char * dest,char * src,int n) { int j; 对于(j = 0; j <n && src [j]!= 0; j ++) dest [j] = src [j]; 对于(; j <n; j ++) dest [j] = 0; 返回(目的地); } $ cat _puts.c #include“ holberton.h” 无效_puts(char * str) { 我 对于(i = 0; str [i]!= 0; i ++) _putchar(str [i]); _putchar('n'); }
gcc
在下一个示例中,我将使用命令gcc -c -fPIC *.c
创建目标文件并将其存储到名为libholberton.so的库中。 动态库以“ lib”开头,以“ .so”扩展名结尾。 下面我使用-c
标志; gcc
手册对此标志进行了解释:“编译或汇编源文件,但不链接。链接阶段只是没有完成。最终输出是以每个源文件的目标文件的形式。” 如果您阅读了有关GNU编译器集合的文章,您会记住这意味着编译器会经历预处理器,编译器和汇编器。 -fPIC
标志在gcc
手册中进行了解释:“如果目标计算机支持,则发出与位置无关的代码,适合动态链接并避免对全局偏移表的大小进行任何限制。此选项对m68k,PowerPC和SPARC。与位置无关的代码需要特殊的支持,因此仅在某些机器上有效。”
$ gcc -c -fPIC * .c $ ls * .o _strncpy.o puts.o _putchar.o
确认
接下来,我将运行命令nm
。 linux的手册页指出:“ GNU nm列出了目标文件objfile中的符号”。 该命令将为我们提供libholberton.so
文件中一些目标代码文件的libholberton.so
,该文件是库的名称,以验证新库的成功! 静态库文件的后缀通常是“ Achive”库的扩展名“ .a”,而扩展名是“ S hared O bject”库或动态库的扩展名“ .so”。
$ nm libholberton.so | 头 0000000000201040 B __bss_start 0000000000201040 b完成.6973 w __cxa_finalize @@ GLIBC_2.2.5 0000000000000650吨deregister_tm_clones 00000000000006c0 t __do_global_dtors_aux 0000000000200e08 t __do_global_dtors_aux_fini_array_entry 0000000000201038 d __dso_handle 0000000000200e18 d _动态 0000000000201040 D _edata 0000000000201048 B _结束
接下来,我们可以使用ldd
命令继续验证我们的库是否已成功创建:
$ ldd holberton linux-vdso.so.1 =>(0x00007ffd931c6000) libholberton.so => /home/vagrant/github.com/johncoleman83/holbertonschool-low_level_programming/example/libholberton.so(0x00007fa4dd432000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6(0x00007fa4dd06d000) /lib64/ld-linux-x86-64.so.2(0x00007fa4dd634000)
组态
创建库的最后一步是确保正确配置您的系统以使用动态库。 您可以通过更新环境变量LD_LIBRARY_PATH
来做到这一点,以包括刚刚创建的库的存储目录位置(在本例中为当前工作目录)。 为此,我们在bash中使用export
命令,其中包含要修改的环境变量的名称以及要添加的其他目录,在这种情况下,我更喜欢: $PWD
用于扩展环境变量。当前工作目录。 库位置的完整目录路径的位置也恰巧在ldd
的上述输出中; 但是,我喜欢$ PWD,因为它是较短的类型。 这是我用来将新库添加到环境变量列表中的命令:
$ export LD_LIBRARY_PATH = $ PWD:$ LD_LIBRARY_PATH
配置系统以使用动态库的另一种方法是以sudo
打开/etc/ld.so.conf文件,并在末尾添加换行符,并添加包含库的目录的路径。 在这种情况下,我们添加当前工作目录,该目录在上面的命令“ ldd
”的结果中列出,该目录是:
/home/vagrant/github.com/johncoleman83/holbertonschool-low_level_programming/example/libholberton.so。
然后,您需要运行ldconfig
来重建已知库的缓存,然后验证是否已添加该库。 这两个命令是:
$ sudo ldconfig $ ldconfig -p
如何使用动态库(仅Linux)
(4)现在已经创建了库,让我们创建一个使用我的自定义库中的函数的C语言程序! 我将使用main()
函数创建一个名为“ main.c”的文件。 该文件将不包含C标准库 ,而是将自定义库与自定义函数一起使用。 这是一个包含
main()
函数的文件示例,我将使用该函数进行测试:
$ cat main.c #include“ holberton.h” int main(无效) { char string1 [] =“ Holberton”,string2 [10],* p,i; p = _strncpy(string2,string1,10); _puts(“我刚刚复制了一个字符串,请参阅:”); _puts(string2); 返回(0); }
编译
编译文件时,将使用标志(或选项)“-L。”,“-l”和“ -o”来包含函数库,并将输出文件命名为“ holberton”。 通过阅读有关gcc的手册,我们可以知道’-L’标志指示gcc在指定目录中搜索库。 由于添加了“。”,因此表示当前目录。 在gcc手册中,“-l”标志/选项描述为:-l library链接时搜索名为library的库。 因此,我们指定了库libholberton.so;。 但是,此选项会自动在输入代码前加上“ lib”前缀,并在其后加上必要的后缀,因此这些规范不需要包含在使用gcc进行编译的命令中。
$ gcc main.c -L。 -lholberton -o holberton
最后,我们执行该文件以查看动态库工作,它具有强大的C语言美感。
$ ./霍尔伯顿 我只是复制了一个字符串,请参见: 霍尔伯顿
静态库和动态库之间的区别,优点和缺点是什么?
为了解释其中的一些差异,重要的是再次参考我在gcc编译器上的文章,该文章进一步说明了编译过程。 在本文中,我们已经讨论了gcc
如何编译为目标文件,这是在预处理器,编译器和汇编器步骤之后发生的。 编译过程的最后一个阶段是链接,该概念有助于理解静态库和动态库之间的差异。
连接器
静态链接
在编译的链接阶段,对于静态库,将二进制形式的库中的实际代码复制并包含在编译过程中创建的程序的最终输出可执行文件中。 因此,静态库每次与编译一起使用时,都会存在一个新位置。 因此,如果您创建5个使用静态库的程序,则该库的实例总数将为6,实际库为1,可执行文件的二进制代码中包含5个其他副本。 这样做的一个优点是,您无需保留该库的副本即可支持使用该库的程序,因为该程序已包含该库。 但是,缺点是依赖于静态库的程序需要进行更新和重新编译,以解决它们在静态库中的所有更新。
动态链接
使用动态链接时,该库只有1个实例,因此在编译过程中不会复制整个库,而仅引用可执行文件的二进制代码中包含的库在内存中的位置。 因此,到库的链接发生在文件运行时。 如果您有多个程序取决于一个动态库,则每个程序在运行时都引用相同的库; 因此,使用动态库或共享库的文件较小。 这种链接形式的优点是在动态库更新时发生,不需要更改或更新依赖该库的程序,因为它仍可以在运行时链接到该库,并将利用该库中的所有更新。 动态库需要花费更多时间来加载并开始运行,因为链接是在运行时而不是在编译过程中发生的。