链接程序和库指南

未定义符号

在读取所有输入文件并完成所有符号解析后,链接编辑器将搜索内部符号表,以查找尚未绑定到符号定义的任何符号引用。这些符号引用称为未定义符号。这些未定义符号在链接编辑过程中的效果根据要生成的输出文件类型而不同,也可能根据符号类型而不同。

生成可执行的输出文件

生成可执行的输出文件时,如果有任何未定义符号,则链接编辑器的缺省行为是终止并生成相应的错误消息。如果可重定位目标文件中的符号引用从未与符号定义匹配,则表示此符号未定义:


$ cat main.c

extern int foo();



main()

{

        return (foo());

}

$ cc -o prog main.c

Undefined           first referenced

 symbol                 in file

foo                     main.o

ld: fatal: Symbol referencing errors. No output written to prog

同样,如果使用共享库创建动态可执行文件,并且该库中的符号引用始终未解析,则会产生未定义符号错误。


$ cat foo.c

extern int bar;

foo()

{

        return (bar);

}



$ cc -o libfoo.so -G -K pic foo.c

$ cc -o prog main.c -L. -lfoo

Undefined           first referenced

 symbol                 in file

bar                     ./libfoo.so

ld: fatal: Symbol referencing errors. No output written to prog

要允许未定义符号(与上一个示例一样),可使用链接编辑器的 -z nodefs 选项抑制出现缺省错误状态。


注 –

使用 -z nodefs 选项时应谨慎。如果在执行进程期间需要不可用的符号引用,则会发生致命的运行时重定位错误。在初始执行和测试应用程序期间可能会检测到此错误。然而,执行路径越复杂,检测此错误状态需要的时间就越长,这将非常耗时且开销很大。


将可重定位目标文件中的符号引用绑定到隐式定义的共享库中的符号定义时,符号也可以保持未定义。例如,继续使用上一个示例中使用的文件 main.cfoo.c


$ cat bar.c

int bar = 1;



$ cc -o libbar.so -R. -G -K pic bar.c -L. -lfoo

$ ldd libbar.so

        libfoo.so =>     ./libfoo.so



$ cc -o prog main.c -L. -lbar

Undefined           first referenced

 symbol                 in file

foo                     main.o  (symbol belongs to implicit \

                        dependency ./libfoo.so)

ld: fatal: Symbol referencing errors. No output written to prog

prog 是使用对 libbar.so显式引用生成的。libbar.so 依赖于 libfoo.so。因此,将从 prog 建立对 libfoo.so 的隐式引用。

因为 main.clibfoo.so 提供的接口进行特定引用,所以 prog 依赖于 libfoo.so。但是,在要生成的输出文件中将仅记录显式共享库的依赖项。因此,如果开发一种不再依赖于 libfoo.so 的新版本 libbar.so,则 prog 将无法运行。

因此,此类型的绑定被认为是致命错误。必须通过在链接编辑 prog 期间直接引用库来使隐式引用变为显式引用。前面示例中显示的致命错误消息中会提示需要的引用。

生成共享库输出文件

链接编辑器生成共享库输出文件时,允许在链接编辑结束时仍存在未定义符号。此缺省行为允许共享库从将其定义为依赖性的动态可执行文件导入符号。

可以使用链接编辑器的 -z defs 选项强制在存在任何未定义符号的情况下生成致命错误。建议在创建任何共享库时使用此选项。引用应用程序中的符号的共享库可以使用 -z defs 选项,并可以使用 extern mapfile 指令定义符号。 请参见定义其他符号

自包含的共享库(通过指定的依赖性来满足对外部符号的所有引用)可提供最大的灵活性。此共享库可由许多用户使用,并且这些用户无需确定和建立依赖性来满足共享库的要求。

弱符号

无论要生成哪种类型的输出文件,未解析的弱符号引用不会产生致命错误状态。

如果要生成静态可执行文件,则可将此符号转换为绝对符号,并指定值零。

如果要生成动态可执行文件或共享库,则可将此符号保留为未定义弱引用,并指定值零。在进程执行期间,运行时链接程序将搜索此符号。如果运行时链接程序未找到匹配项,则将此引用绑定到地址零,而不是生成致命重定位错误。

以前,这些未定义弱引用符号被用作一种机制,用于测试功能是否存在。例如,在共享库 libfoo.so.1 中可能使用了以下 C 代码段:


#pragma weak    foo



extern  void    foo(char *);



void bar(char * path)

{

        void (* fptr)(char *);



        if ((fptr = foo) != 0)

                (* fptr)(path);

}

生成引用 libfoo.so.1 的应用程序时,无论是否找到符号 foo 的定义,链接编辑都将成功完成。如果在执行此应用程序时函数地址测试为非零,则将调用此函数。但是,如果未找到符号定义,则函数地址测试将为零,因此不调用此函数。

编译系统将此地址比较方法视为未定义语义,这将导致在优化时删除测试语句。此外,运行时符号绑定机制会对使用此方法设定其他限制。这些限制防止所有动态库使用一致的模型。


注 –

建议不要按照此方式使用未定义弱引用。相反,应将 dlsym(3C)RTLD_DEFAULTRTLD_PROBE 句柄配合使用,以测试符号是否存在。 请参见测试功能