makefile知识点《一》
一、$(@F)
表示"$@"的文件部分,如果"$@"值是"dir/foo.o",那么"$(@F)"就是"foo.o","$(@F)"
相当于函数"$(notdir $@)"。
"$(*D)"
"$(*F)"
和上面所述的同理,也是取文件的目录部分和文件部分。对于上面的那个例子,
"$(*D)"返回"dir",而"$(*F)"返回"foo"
"$(%D)"
"$(%F)"
分别表示了函数包文件成员的目录部分和文件部分。这对于形同"archive(member)"
形式的目标中的"member"中包含了不同的目录很有用。
二、在shell命令前加@
在makefile文件,shell命令前加上@表示不显示该命令;而直接执行命令。
三、-fPIC
fPIC 作用于编译阶段,告诉编译器产生与位置无关代码(Position-Independent Code),
则产生的代码中,没有绝对地址,全部使用相对地址,故而代码可以被加载器加载到内存的任意
位置,都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。
不加fPIC编译出来的so,是要再加载时根据加载到的位置再次重定位的.(因为它里面的代码并不是位置无关代码)
如果被多个应用程序共同使用,那么它们必须每个程序维护一份so的代码副本了.(因为so被每个程序加载的位置都不同,显然这些重定位后的代码也不同,当然不能共享)
我们总是用fPIC来生成so,也从来不用fPIC来生成a.
fPIC与动态链接可以说基本没有关系,libc.so一样可以不用fPIC编译,只是这样的so必须要在加载到用户程序的地址空间时重定向所有表目.
因此,不用fPIC编译so并不总是不好.
如果你满足以下4个需求/条件:
1.该库可能需要经常更新
2.该库需要非常高的效率(尤其是有很多全局量的使用时)
3.该库并不很大.
4.该库基本不需要被多个应用程序共享
如果用没有加这个参数的编译后的共享库,也可以使用的话,可能是两个原因:
1:gcc默认开启-fPIC选项
2:loader使你的代码位置无关
从GCC来看,shared应该是包含fPIC选项的,但似乎不是所以系统都支持,所以最好显式加上fPIC选项。参见如下
`-shared'
Produce a shared object which can then be linked with other
objects to form an executable. Not all systems support this
option. For predictable results, you must also specify the same
set of options that were used to generate code (`-fpic', `-fPIC', or model suboptions) when you specify this option.(1)
-fPIC 的使用,会生成 PIC 代码,.so 要求为 PIC,以达到动态链接的目的,否则,无法实现动态链接。
四、静态库和动态库
-static
此选项将禁止使用动态库,所以,编译出来的东西,一般都很大,也不需要什么
动态连接库,就可以运行.
-share
此选项将尽量使用动态库,所以生成文件比较小,但是需要系统由动态库.
Linux 应用程序因为 Linux 版本的众多与各自独立性,在工程制作与使用中必须熟练掌握如下两点才能有效地工作和理想地运行。
- Linux 下标准库链接的三种方式(全静态 , 半静态 (libgcc,libstdc++), 全动态)及其各自利弊。
- Linux 下如何巧妙构建 achrive(*.a),并且如何设置链接选项来解决 gcc 比较特别的链接库的顺序问题。
为了演示三种不同的标准库链接方式对最终应用程序产生的区别, 这里用了一个经典的示例应用程序 HelloWorld 做演示,见 清单 1 HelloWorld。 整个工程可以在文章末尾下载。
#include <stdio.h> #include <iostream> using std::cout; using std::endl; int main(int argc, char* argv[]) { printf("HelloWorld!(Printed by printf)\n"); cout<<"HelloWorld!(Printed by cout)"<<endl; return 0; } |
三种标准库链接方式的选项及区别见 表 1
标准库连接方式 | 示例连接选项 | 优点 | 缺点 |
---|---|---|---|
全静态 | -static -pthread -lrt -ldl | 不会发生应用程序在 不同 Linux 版本下的标准库不兼容问题。 | 生成的文件比较大, 应用程序功能受限(不能调用动态库等) |
全动态 | -pthread -lrt -ldl | 生成文件是三者中最小的 | 比较容易发生应用程序在 不同 Linux 版本下标准库依赖不兼容问题。 |
半静态 (libgcc,libstdc++) | -static-libgcc -L. -pthread -lrt -ldl | 灵活度大,能够针对不同的标准库采取不同的链接策略, 从而避免不兼容问题发生。 结合了全静态与全动态两种链接方式的优点。 | 比较难识别哪些库容易发生不兼容问题, 目前只有依靠经验积累。 某些功能会因选择的标准库版本而丧失。 |
上述三种标准库链接方式中,比较特殊的是 半静态链接方式,主要在于其还需要在链接前增加额外的一个步骤: ln -s `g++ -print-file-name=libstdc++.a`,作用是将 libstdc++.a(libstdc++ 的静态库)符号链接到本地工程链接目录。 -print-file-name 在 gcc 中的解释如下: -print-file-name=<lib> Display the full path to library <lib>
为了区分三种不同的标准库链接方式对最终生成的可执行文件的影响,本文从两个不同的维度进行分析比较:
维度一:最终生成的可执行文件对标准库的依赖方式(使用 ldd 命令进行分析)
ldd 简介:该命令用于打印出某个应用程序或者动态库所依赖的动态库 涉及语法:ldd [OPTION]... FILE... 其他详细说明请参阅 man 说明。
三种标准库链接方式最终产生的应用程序的可执行文件对于标准库的依赖方式具体差异见 图 1、图 2、图 3所示:
图 1. 全静态标准库链接方式
图 2. 全动态标准库链接方式
图 3. 半静态(libgcc,libstdc++) 标准库链接方式
通过上述三图,可以清楚的看到,当用 全静态标准库的链接方式时,所生成的可执行文件最终不依赖任何的动态标准库, 而 全动态标准库的链接方式会导致最终应用程序可执行文件依赖于所有用到的标准动态库。 区别于上述两种方式的 半静态链接方式则有针对性的将 libgcc 和 libstdc++ 两个标准库非动态链接。 (对比 图 2与 图 3,可见在 图 3中这两个标准库的动态依赖不见了)
从实际应用当中发现,最理想的标准库链接方式就是半静态链接,通常会选择将 libgcc 与 libstdc++ 这两个标准库静态链接, 从而避免应用程序在不同 Linux 版本间标准库依赖不兼容的问题发生。
维度二 : 最终生成的可执行文件大小(使用 size 命令进行分析)
size 简介:该命令用于显示出可执行文件的大小 涉及语法:size objfile... 其他详细说明请参阅 man 说明。
三种标准库链接方式最终产生的应用程序的可执行文件的大小具体差异见 图 4、图 5、图 6所示:
图 4. 全静态标准库链接方式
图 5. 全动态标准库链接方式
图 6. 半静态(libgcc,libstdc++) 标准库链接方式
通过上述三图可以看出,最终可执行文件的大小随最终所依赖的标准动态库的数量增加而减小。 从实际应用当中发现,最理想的是 半静态链接方式,因为该方式能够在避免应用程序于 不同 Linux 版本间标准库依赖不兼容的问题发生的同时,使最终生成的可执行文件大小最小化。
-llibrary -l library:指定所需要的额外库 -Ldir:指定库搜索路径 -static:静态链接所有库 -static-libgcc:静态链接 gcc 库 -static-libstdc++:静态链接 c++ 库 关于上述命令的详细说明,请参阅 GCC 技术手册
ar 简介:处理创建、修改、提取静态库的操作
涉及选项: t - 显示静态库的内容 r[ab][f][u] - 更新或增加新文件到静态库中 [s] - 创建文档索引 ar -M [<mri-script] - 使用 ar 脚本处理 其他详细说明请参阅 man 说明。
假设现有如 图 7所示两个库文件
从 图 7中可以得知,CdtLog.a 只包含 CdtLog.o 一个对象文件 , 而 xml.a 包含 TXmlParser.o 和 xmlparser.o 两个对象文件 现将 CdtLog.o 提取出来,然后通过 图 8方式创建一个新的静态库 demo.a,可以看出,demo.a 包含的是 CdtLog.o 以及 xml.a, 而不是我们所预期的 CdtLog.o,TXmlParser.o 和 xmlparser.o。这正是区别于 Windows 下静态库的制作。
这样的 demo.a 当被链接入某个工程时,所有在 TXmlParser.o 和 xmlparser.o 定义的符号都不会被发现,从而会导致链接错误, 提示无法找到对应的符号。显然,通过图 8 方式创建 Linux 静态库是不正确的。
正确的方式有两种:
- 将所有静态库中包含的对象文件提取出来然后重新打包成新的静态库文件。
- 用一种更加灵活的方式创建新的静态库文件:ar 脚本。
显然,方式 1 是比较麻烦的,因为涉及到太多的文件处理,可能还要通过不断创建临时目录用于保存中间文件。 推荐使用如 清单 2 createlib.sh所示的 ar 脚本方式进行创建:
rm demo.a rm ar.mac echo CREATE demo.a > ar.mac echo SAVE >> ar.mac echo END >> ar.mac ar -M < ar.mac ar -q demo.a CdtLog.o echo OPEN demo.a > ar.mac echo ADDLIB xml.a >> ar.mac echo SAVE >> ar.mac echo END >> ar.mac ar -M < ar.mac rm ar.mac |
如果想在 Linux makefile 中使用 ar 脚本方式进行静态库的创建,可以编写如 清单 3 BUILD_LIBRARY所示的代码:
define BUILD_LIBRARY $(if $(wildcard $@),@$(RM) $@) $(if $(wildcard ar.mac),@$(RM) ar.mac) $(if $(filter %.a, $^), @echo CREATE $@ > ar.mac @echo SAVE >> ar.mac @echo END >> ar.mac @$(AR) -M < ar.mac ) $(if $(filter %.o,$^),@$(AR) -q $@ $(filter %.o, $^)) $(if $(filter %.a, $^), @echo OPEN $@ > ar.mac $(foreach LIB, $(filter %.a, $^), @echo ADDLIB $(LIB) >> ar.mac ) @echo SAVE >> ar.mac @echo END >> ar.mac @$(AR) -M < ar.mac @$(RM) ar.mac ) endef $(TargetDir)/$(TargetFileName):$(OBJS) $(BUILD_LIBRARY) |
通过 图 9,我们可以看到,用这种方式产生的 demo.a 才是我们想要的结果。
正如 GCC 手册中提到的那样: It makes a difference where in the command you write this option; the linker searches and processes libraries and object files in the order they are specified. Thus, ‘ foo.o -lz bar.o ’ searches library ‘ z ’ after file ‘ foo.o ’ but before ‘ bar.o ’ . If ‘ bar.o ’ refers to functions in ‘ z ’ , those functions may not be loaded.
为了解决这种库链接顺序问题,我们需要增加一些链接选项 :
$(CXX) $(LINKFLAGS) $(OBJS) -Xlinker "-(" $(LIBS) -Xlinker "-)" -o $@
通过将所有需要被链接的静态库放入 -Xlinker "-(" 与 -Xlinker "-)" 之间,可以是 g++ 链接过程中, 自动循环链接所有静态库,从而解决了原本的链接顺序问题。
-Xlinker option Pass option as an option to the linker. You can use this to supply system-specific linker options which GCC does not know how to recognize.
本文介绍了 Linux 下三种标准库链接的方式及各自利弊,同时还介绍了 Linux 下静态库的制作及使用方法,相信能够给 大多数需要部署 Linux 应用程序和编写 Linux Makefile 的工程师提供有用的帮助。