1. 主页
  2. 文档
  3. C语言教程
  4. C语言变量
  5. 链接器如何解析在多个位置定义的全局符号?

链接器如何解析在多个位置定义的全局符号?

在编译时,编译器将每个全局符号作为强符号或弱符号导出到汇编器,汇编器将此信息隐式编码到可重定位目标文件的符号表中。函数和初始化的全局变量得到强符号。未初始化的全局变量获得弱符号。
对于以下示例程序,buf、bufp0、main 和 swap 是强符号;bufp1 是一个弱符号。

/* main.c */
void swap();
int buf[2] = {1, 2};
int main()
{
swap();
return 0;
}

/* swap.c */
extern int buf[];

int *bufp0 = &buf[0];
int *bufp1;

void swap()
{
int temp;

bufp1 = &buf[1];
temp = *bufp0;
*bufp0 = *bufp1;
*bufp1 = temp;
}

鉴于这种强符号和弱符号的概念,Unix 链接器使用以下规则来处理多个定义的符号:
规则 1:不允许使用多个强符号(具有相同的变量名)。
规则 2:给定一个强符号和多个弱符号,选择强符号。
规则 3:给定多个弱符号,选择任何一个弱符号。
例如,假设我们尝试编译和链接以下两个 C 模块:

/* foo1.c */ 
int main() 
{ 
return 0; 
} 

/* bar1.c */
int main()
{
return 0;
}

在这种情况下,链接器将生成错误消息,因为强符号 main 被定义了多次(规则 1):

$ gcc foo1.c bar1.c
/tmp/cca015022.o: In function ‘main’:
/tmp/cca015022.o(.text+0x0): multiple definition of ‘main’
/tmp/cca015021.o(.text+0x0): first defined here

同样,链接器将为以下模块生成错误消息,因为强符号 x 被定义了两次(规则 1):

/* foo2.c */
int x = 15213;
int main()
{
return 0;
}

/* bar2.c */
int x = 15213;
void f()
{
}

但是,如果 x 在一个模块中未初始化,则链接器将悄悄地选择另一个模块中定义的强符号(规则 2),如下面的程序所示:

/* foo3.c */
#include <stdio.h>
void f(void);
int x = 15213;
int main()
{
f();
printf("x = %d\n", x);
return 0;
}

/* bar3.c */
int x;
void f()
{
x = 15212;
}

在运行时,函数 f() 将 x 的值从 15213 更改为 15212,这对函数 main 的作者来说可能是一个不受欢迎的惊喜!请注意,链接器通常不会表明它已检测到 x 的多个定义。

$ gcc -o gfg foo3.c bar3.c
$ ./gfg
x = 15212

如果 x 有两个弱定义(规则 3),也会发生同样的事情:

/*a.c*/
#include <stdio.h>
void b(void);

int x;
int main()
{
x = 2016;
b();
printf("x = %d ",x);
return 0;
}
/*b.c*/
#include <stdio.h>

int x;

void b()
{
x = 2017;

}

规则 2 和 3应用可能会引入一些隐蔽的运行时错误,这些错误对于粗心的程序员来说是无法理解的,尤其是在重复的符号定义具有不同类型的情况下。
示例:“x”在一个模块中定义为 int,在另一个模块中定义为 double。

/*a.c*/
#include <stdio.h>
void b(void); 

int x = 2016;
int y = 2017;
int main()
{
b();
printf("x = 0x%x y = 0x%x \n", x, y);
return 0;
}
/*b.c*/
double x;

void b()
{
x = -0.0;
}

执行:

$ gcc ac bc -o geeksforgeeks
$ ./geeksforgeeks
x = 0x0 y = 0x80000000

这是一个微妙而令人讨厌的错误,特别是因为它是静默发生的,编译系统没有发出警告,而且它通常在程序执行的更晚才出现,远离错误发生的地方。在具有数百个模块的大型系统中,此类错误极难修复,尤其是因为许多程序员不了解链接器的工作原理。如有疑问,请使用 gcc -fno-common 标志等标志调用链接器,如果遇到多个已定义的全局符号,则会触发错误。

 

这篇文章对您有用吗?