GNU linker的undefined reference问题

GNU linker的undefined reference问题

一个链接的小实验

假设有两个源文件,一个main.c, 一个helloworld.c,两个文件中分别定义了:

  1. main.c中定义了main函数,调用了helloworld.c中的hello 函数
int main()
{
    hello();
    return 0;
}
  1. helloworld.c中定义了hello 和world两个函数,但是world函数中调用了一个未定义的函数ufunction();
#include <stdio.h>
void hello()
{
	printf("hello()"\n);
}
void world()
{
	ufunction();
	printf("world()\n");
}
  1. 现在对这两个文件进行编译链接
gcc -o main.o -c main.c
gcc -o helloworld.o -c helloworld.c
gcc -o main main.o helloworld.o
  1. 上面的编译链接结果是,执行第三条命令时,一定会报undefined reference symbol ufuntion错误
  2. 这个小实验说明,链接的时候,会解析整个.o文件中的符号,如果找不到该符号的定义,则会报undefined reference错误。

使用静态链接库(.a)文件时的链接逻辑

一个静态链接库中,通常存在多个.o文件,链接时并不是将.a文件中的所有.o文件都进行链接,而是按需进行,这里有一个简单的问题是什么交按需?笔者的实验结论是:如果需要.o中的某个符号(可能是函数也可能是全局变量),那么就需要这个.o文件。下面同样通过一个简单的小实验来验证这个结论。

  1. 定义三个源文件,main.c和hello.c以及world.c,其中hello.c和world.c用来生成一个静态链接库libme.a,为了说明我们的结论,做两个小实验。
  2. main.c中仍然定义如下
extern void hello();
int main()
{
	hello();
	return 0;
}
  1. 第一个小实验的libme.a我们这样设计,称为libmev1.a,hello.c和world.c定义如下
//以下是hello.c中的定义
#include <stdio.h>
int globalcnt = 1000;
void hello()
{
	printf("hello %d\n",globalcnt++);
}
//以下是world.c中的定义
#include <stdio.h>
extern int globalcnt;
extern void ufunction();
void world()
{
	ufunction();
	printf("world %d\n",globalcnt++);
}
/* 实用如下命令生成libmev1.a
 * gcc -o hello.o -c hello.c
 * gcc -o world.o -c world.c
 * ar -rcs libmev1.a hello.o world.o
 */
  1. 第二个小实验的libme.a的实现,仅仅是将globalcnt的定义位置从hello.c中移动到world.c中,称为libmev2.a,hello.c和world.c定义如下
//以下是hello.c中的定义
#include <stdio.h>
extern int globalcnt;
void hello()
{
	printf("hello %d\n",globalcnt++);
}
//以下是world.c中的定义
#include <stdio.h>
int globalcnt = 1000;
extern void ufunction();
void world()
{
	ufunction();
	printf("world %d\n",globalcnt++);
}

/* 实用如下命令生成libmev2.a
 * gcc -o hello.o -c hello.c
 * gcc -o world.o -c world.c
 * ar -rcs libmev1.a hello.o world.o
 */
  1. 首先生成libmev1.a和libmev2.a,过程如下
    生成libmev1.a
    生成libmev2.a
  2. 下面通过链接来验证之前的结论,使用libmev2.a必然会产生undefined reference symbol ufunction错误,而使用libmev1.a则可以正确链接。实验结果如下:
    链接libme.a的结果

通过map文件验证链接过程

上面的两个小实验说明,所需要的符号所在的.o是链接所必须的,而链接就会解析.o中的所有符号,在解析这些符号的过程时,可能引入新的依赖.o文件。就上面的小实验,我们尝试说明一下对libmev1和libmev2的链接过程。

  1. 对 libmev1.a的链接过程
    mian.o中需要符号hello,符号hello在hello.o文件中,hello.o中不存在其依赖于其它.o的符号,因此只链接hello.o一个文件。我们使用Wl,-Map参数生成map文件来检验,显然完全符合推导过程:
    对libmev1.a的链接过程
  2. 对libmev2.a的链接过程
    main.o中需要符号hello,符号hello在hello.o文件中,hello.o中需要符号globalcnt,符号globalcnt在world.o中,因此需要链接hello.o和world.o两个文件。由于直接链接libmev2.a会失败,而无法产生map文件,在main.c中定义ufunction符号来满足链接需要(这并不影响链接过程)。
extern void hello();
void ufunction()
{
}
int main()
{
    hello();
    return 0;
}

修改main.c后链接libmev2.a对libmev2.a的链接过程
从结果图可以看到,链接hello.o是因为main.o中需要hello,而链接world.o是因为hello.o中需要globalcnt.

对.a文件的链接逻辑的结论

通过上面的两组小实验,可以得到gnu linker的链接逻辑如下:

  1. 以第一个.o文件为基准,查找所需要的符号;
  2. 从.a文件中找到所需符号所在的.o,将这个.o中的所有符号进行解析(无论是否被引用)
  3. 将所有的这样的.o全部找出,进行链接。

一个潜在的undefined reference问题及其解决方法

基于这样的链接逻辑,会导致一个奇怪的问题,即我们自己的代码中明明没有引用某些符号,而使用静态链接库时,总是报错说存在未定义的符号引用(如上面的libmev2.a)。这是由于一个.a文件中定义了多个符号,而这些符号并不一定是我们都需要的,且都实现了的(详见上面的libmev2.a)。这种问题往往不容易发现,遇到奇怪的未定义问题时,可以往这方面思考。

为了解决这一问题,有几种方法可以使用:

  1. 将报错的undefined reference符号手动定义,但是这些符号往往时lib.a中的某个.o需要的,我们并不一定知道该如何定义;
  2. 如果报错的符号是在其它lib.a中,那么还要链接所需的.a文件。
  3. 如果确定自己的代码中一定不会引用到报错的符号,可以使用链接参数-Wl,–gc-sections,这会告诉链接器,在链接过程中不要去解析未引用的符号。
  4. 对于lib.a的提供者,可以采用一个源文件中只定义一个函数,全局变量不要和函数定义放在一起。

重要补充

  1. 连接过程并不是以整个.o文件为单位,而是以.o中的section为单位,即section中的任何一个符号被需要,则整个section都会被链接。
  2. -Wl,–gc-sections移除的是.o文件中没有被用到的section. 如果不加此选项,gnu linker也会移除一些明确不用的section.
  3. 生成的map文件中的“Archive member included to satisfy reference by file (symbol)” 给出了链接库中的.o的一个原因,并不是全部原因,也就是说,如果引用.o中的多个符号也只给出一条。
  4. 可以使用-ffunction-sections将文件中的所有函数都独立放入一个section.
  5. 可以使用-fdata-sections将文件中的所有全局或静态变量都独立放入一个section.
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值