链接器如何解决多重定义的全局符号?-IDC帮帮忙

在编译时,编译器将每个全局符号作为强或弱导出到汇编器,并且汇编器隐式地在可重定位目标文件的符号表中对此信息进行编码。函数和初始化的全局变量获得强符号。未初始化的全局变量得到弱符号。
对于以下示例程序,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:在函数'main'中:
/tmp/cca015022.o(.text+0x0):'main'的多重定义
/tmp/cca015021.o(.text+0x0):首先在这里定义

类似地,链接器将为以下模块生成错误消息,因为强符号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标志之类的标志调用链接器,如果遇到多重定义的全局符号,则会触发错误。