本章解释了如何生成您自己的库。
库具有两点好处。首先,它们提供了在多个应用程序间共享代码的方法。如果您有要共享的代码,则可以创建一个具有该代码的库,并将该库链接到需要这些代码的应用程序。其次,库提供了降低大型应用程序复杂性的方法。这类应用程序可以将相对独立的部分生成为库并进行维护,因此减轻程序员在其他部分工作的负担。
生成库只不过是创建 .o 文件(使用 -c 选项编译代码)并使用 CC 命令将 .o 文件并入库中。可以生成两种库:静态(归档)库和动态(共享)库。
对于静态(归档)库,库中的对象在链接时链接到程序的可执行文件中。只有库中属于应用程序所需的那些 .o 文件链接到可执行文件。静态(归档)库名称通常以 .a 后缀结尾。
对于动态(共享)库,库中的对象并不链接到程序的可执行文件,而是链接程序在可执行文件中注明程序依赖于库。执行该程序时,系统会装入程序所需的动态库。如果使用同一动态库的两个程序同时执行,那么操作系统在程序间共享这个动态库。动态(共享)库名称以 .so 后缀结尾。
动态链接共享库较静态链接归档库有多个优势:
可执行文件较小。
在运行时,代码的有效部分可在程序间共享,这样就可以降低内存使用量。
库可以在运行时替换,无需重新链接应用程序。(动态链接共享库的主要机制是使程序能够利用 Solaris 操作系统的多项改进的功能,而无需重新链接和分发程序。)
但动态库也具有一些缺点:
运行时链接有执行时间成本。
使用动态库进行程序的分发可能会要求同时分发该程序所使用的库。
将共享库移动到一个不同的位置就可以阻止系统查找该库并执行程序。(环境变量 LD_LIBRARY_PATH 可以帮助克服此问题。)
生成静态(归档)库的机制与生成可执行文件相似。可以使用 CC 的 – xar 选项将目标 (.o) 文件集合并入单个库中。
可以使用 CC -xar 而非直接使用 ar 命令来生成静态(归档)库。C++ 语言通常要求编译器维护的信息比传统 .o 文件提供的信息多,尤其是模板实例。– xar 选项可确保所有必要信息(包括模板实例)都包括在库中。在通常的编程环境下,可能无法完成该操作,因为 make 无法确定实际创建和引用了哪些模板文件。如果没有 CC -xar,引用的模板实例可能没有按照需要包括在库中。例如:
% CC -c foo.cc # Compile main file, templates objects are created. % CC -xar -o foo.a foo.o # Gather all objects into a library. |
–xar 标志会使 CC 创建静态(归档)库。要为新建的库命名,需要使用 – o 指令。编译器检查命令行上的目标文件,交叉引用这些目标文件与模板系统信息库中的目标文件,并将用户的目标文件所需的模板(以及主目标文件本身)添加到归档文件中。
仅可将 -xar 标志用于创建或更新现有归档文件。不要用它来维护归档。-xar 选项与 ar -cr 等效。
最好每个 .o 文件中只有一个函数。如果要链接归档文件,则在需要该归档文件中的特定 .o 文件中的符号时,整个 .o 文件都链接到应用程序中。每个 .o 文件中有一个函数可以确保将只从归档文件链接应用程序所需的那些符号。
动态(共享)库的生成方式与静态(归档)库的生成方式基本相同,除了在命令行上使用 –G 而不是 –xar。
不应直接使用 ld。与静态库一样,CC 命令可以确保使用模板时,模板系统信息库中所有必要的模板实例都包括在库中。在执行 main() 之前会调用与应用程序链接的动态库中所有静态构造函数,在 main() 退出之后会调用所有静态析构函数。如果使用 dlopen() 打开共享库,所有静态构造函数都在执行 dlopen() 时执行,所有静态析构函数都在执行 dlclose() 时执行。
应该使用 CC -G 来生成动态库。使用 ld(链接编辑器)或 cc(C 编译器)生成动态库时,异常可能无法生效,且库中定义的全局变量未初始化。
要生成动态(共享)库,必须使用 CC 的 –Kpic 或 –KPIC 选项编译每个对象来创建可重定位的目标文件。然后您就可以生成一个具有这些可重定位目标文件的动态库。如果遇到异常的链接失败,可能是忘记了使用 –Kpic 或 –KPIC 编译某些对象。
要生成名为 libfoo.so 的 C++ 动态库(该库包含源文件 lsrc1.cc 和 lsrc2.cc 中的对象),请键入:
% CC -G -o libfoo.so -h libfoo.so -Kpic lsrc1.cc lsrc2.cc |
-G 选项指定动态库的构造。-o 选项指定库的文件名。-h 选项指定共享库的内部名称。-Kpic 选项指定目标文件与位置无关。
CC -G 命令不会将任何 -l 选项传递给链接程序 ld。为了确保正确的初始化顺序,共享库对其所需的每个其他共享库必须具有显式的依赖性。要创建依赖性,请对每个此类库使用 -l 选项。典型的 C++ 共享库将使用以下几组选项之一:
-lCstd -lCrun -lc -library=stlport4 -lCrun -lc |
为了确保列出了需要的所有依赖性,请使用 -zdefs 选项生成库。对于缺少的每个符号定义,链接程序都会发出错误消息。要提供缺少的定义,请针对这些库添加 -l 选项。
要确定是否包含了不需要的依赖性,请使用以下命令
ldd -u -r mylib.so ldd -U -r mylib.so |
然后可以重新生成没有不需要的依赖性的 mylib.so。
对于包含 C++ 代码的程序,切勿使用 -Bsymbolic,而应使用链接程序映射文件。如果使用 -Bsymbolic,不同模块中的引用会绑定到应是一个全局对象内容的不同副本。
异常机制依赖对地址的比较。如果您具有某项内容的两个副本,它们的地址就不等同且异常机制可能失败,这是由于异常机制依赖对假设为唯一地址内容的比较。
在组织生成一个仅供内部使用的库时,可以使用不建议在一般情况下使用的选项来生成这个库。具体来说,库不需要符合系统的应用程序二进制接口 (application binary interface, ABI)。例如,可以使用 -fast 选项编译库,以提高其在某已知体系结构上的性能。同样,可以使用 -xregs=float 选项编译库以提高性能。
在组织生成一个供其他公司使用的库时,库的管理、平台的一般性以及其他问题就变得尤为重要。一个用于检验库是否为公用的简单测试就是询问应用程序程序员是否可以轻松地重新编译该库。生成公用库时应该符合系统的应用程序二进制接口 (application binary interface, ABI)。通常,这意味着应该避免任何特定于处理器的选项。(例如,不使用 –fast 或 –xtarget。)
SPARC ABI 为应用程序保留了一些专用寄存器。对于 V7 和 V8,这些寄存器是 %g2、%g3 和 %g4。对于 V9,这些寄存器是 %g2 和 %g3。由于多数编译用于应用程序,所以在缺省情况下,为了提高程序的性能,C++ 编译器将这些寄存器作为临时寄存器使用。但是,对公用库中寄存器的使用通常不兼容于 SPARC ABI。生成公用库时,请使用 -xregs=no%appl 选项编译所有对象,以确保不会使用应用程序寄存器。
如果要生成以 C++ 编写但可用于 C 程序的库,必须创建 C API(application programming interface,应用程序编程接口)。为此,应先使所有导出的函数为 extern "C"。注意,只有在全局函数中才能够完成该操作,在成员函数中不行。
如果 C 接口库需要 C++ 运行时支持,且要使用 cc 进行链接,则在使用 C 接口库时,还必须用 libC(兼容模式)或 libCrun(标准模式)链接应用程序。(如果 C 接口库不需要 C++ 运行时支持,就不必用 libC 或 libCrun 进行链接。)归档库与共享库的链接步骤是不同的。
提供归档的 C 接口库时,必须提供如何使用该库的说明。
如果 C 接口库是在标准模式(缺省模式)下使用 CC 生成的,那么在使用该 C 接口库时,将 -lCrun 添加到 cc 命令行。
如果 C 接口库是在兼容模式 (-compat) 下使用 CC 生成的,那么在使用该 C 接口库时,将 -lC 添加到 cc 命令行。
提供共享的 C 接口库时,必须在生成库时创建对 libC 或 libCrun 的依赖性。如果共享库具有正确的依赖性,就不必在使用该库时将 -lC 或 -lCrun 添加到命令行。
如果要在兼容模式 (-compat) 下生成 C 接口库,应在生成库时将 -lC 添加到 CC 命令行。
如果要在标准模式(缺省模式)下生成 C 接口库,应在生成库时将 -lCrun 添加到 CC 命令行。
如果要删除对 C++ 运行时库的任何依赖性 ,应该在库源文件中强制应用下列代码规则:
不要使用任何形式的 new 或 delete,除非提供了自己的相应版本。
不要使用异常。
不要使用运行时类型信息 (RTTI)。
如果要使用 dlopen() 从 C 程序打开 C++ 共享库,应确保共享库依赖于适当的 C++ 运行时(对于 -compat=4,为 libC.so.5;对于 -compat=5,为 libCrun.so.1)。
为此,应在生成共享库时,将 -lC(对于 -compat=4)或 lCrun(对于 -compat=5)添加到命令行。例如:
example% CC -G -compat=4... -lC example% CC -G -compat=5... -lCrun |
如果共享库使用了异常且不具有对 C++ 运行库的依赖性,则 C 程序可能会出现无规律的行为。