前言
最近编译C++代码时出现链接失败信息,类似下图:

初见这个错误有些令人费解,不过经过一番分析,发现原因还是清晰的,和大家一起分享一下。
图一中使用的tpl.h代码如下:
#pragma once
template <typename T>
int compare(const T& a, const T& b);
tpl.cpp代码如下:
#include "tpl.h"
#include <iostream>
template <typename T>
int compare(const T& a, const T& b) {
if (a == b) {
return 0;
}
return (a > b) ? 1 : -1;
}
main.cpp代码如下:
#include <iostream>
#include "tpl.h"
int main() {
int res = compare<int> (1, 2);
std::cout << res << std::endl;
}
我一直习惯把模板实现写在头文件中,因此从未遇见这种错误。这次偶然将模板声明和实现分离,出现了链接错误。这引发了我一些思考,为什么模板不支持分离式编译?
模板的编译
为了搞清楚模板是怎么编译的,这里以上述tpl.cpp中的compare模板函数为例。tpl.h中声明了一个模板函数并且在tpl.cpp中实现这个模板函数。现在我们编译tpl.cpp,生成的汇编代码如下:

可以看出,一个模板如果没有被调用的时候,编译器不会对这部分代码做任何处理,一行指令都没有。实际上编译器也不知道要怎么处理这个模板。在没有调用之前,模板的参数类型是不确定的。
假如我们在tpl.cpp中增加调用compare的函数的test函数,如下:
int test() {
int res = compare<int> (1, 2);
std::cout << res << std::endl;
return res;
}
再编译tpl.cpp就会发现,生成的汇编文件里面已经有了以int为参数的compare函数了,如下图:

链接错误问题分析
那么图一中的链接错误是怎么发生的?我们执行了g++ tpl.cpp main.cpp
这个命令,大致会经过下面这三个步骤:
- 编译并且汇编tpl.cpp,生成tpl.o目标文件。
- 编译并且汇编main.cpp,生成main.o目标文件。
- 链接tpl.o和main.o生成可执行文件。
编译tpl.cpp已经分析过了,由于没有调用模板函数,因此编译器不会对模板做任何处理。实际上编译器根本不知道要怎么处理,因为模板里面的类型是不确定的。只有在调用的时候才会确定下来,这个时候编译器才知道这个函数长什么样,才会执行编译操作。
编译main.cpp的时候,由于模板compare已经声明但未实现,因此这里也不会生成具体代码,只会生成一个call指令。很显然这个call指令中的函数地址现在肯定是错误的,头文件tpl.h并没有去实现这个函数,需要依赖链接器将这个地址修改成正确的地址。
问题就出在编译tpl.cpp的时候模板没有实例化,编译器并没有编译这个函数,因此找在符号表中找不到这个函数的地址,链接器不知道要怎么处理,因此会出现链接错误。
如何改正
比较好的方式是在头文件中定义模板而不仅仅只是声明。
比如上面的tpl.h代码改成这样
#pragma once
template <typename T>
int compare(const T& a, const T& b) {
if (a == b) {
return 0;
}
return (a > b) ? 1 : -1;
}
就不会有链接问题了。
你可能还喜欢下面这些文章
当程序崩溃的时候,会产生一个core文件。我们可以称它为进程死亡现场。排查进程死亡就和破案一样,找到案发现场,仔细排查每个细节,抽丝剥茧,最终定位原因。很幸运我们有一个强大的工具调查现场信息。这个工具就是GDB。下面我们就来看看如何用GDB排查问题。首先以一个越界访问数组的程序为例,如下:#include #include void core() { std::vector<int> a; std::cout << a[0];}int main() { core(); return 0;}执行上面的代码将会产生一个core文件。假设我们的core
shell中的if语法是最让我头疼的语法之一,它的判断就向使用USB插头一样——拿起来插入不行,翻转再插入还不行,再翻转插入行了!为了搞清楚这部分语言,我收集了一些文章关于if条件判断的用法,希望对你也有些帮助。一、基本语法if [ command ]; then 符合该条件执行的语句fiif [ command ];then 符合该条件执行的语句elif [ command ];then 符合该条件执行的语句else 符合该条件执行的语句fi语法说明bash shell会按顺序执行if语句,如果command执行后且它的返回状态是0,则会执行符合该条件执行的语
最近在写hadoop的streaming任务,在输出的时候用了std::endl,就像下面这样:os << “content” << std::endl运行后发现程序跑的比python还慢,令人费解。我入门C++的时候,输出hello world也是这样写的,有什么问题?于是查了一下std::endl,发现问题挺大。std::endl解释如下:Inserts a new-line character and flushes the stream.Its behavior is equivalent to calling os.put(‘\n’) (or os.put(o
一、基本位操作|或&与~取反^异或<<左移>>右移二、位操作的常见用法1.获取某位的值#define BitGet(Number,pos) ((Number)|= 1<<(pos)) //把某位置1#define BitGet(Number,pos) ((Number) &= ~(1<<(pos)) //把某位置0#define BitGet(Number,pos) ((Number) >> (pos)&1)) //用宏得到某数的某位#define BitGet(Number,pos) ((Number) ^=
赞赏微信赞赏
支付宝赞赏