内联函数和模版函数的常见错误、以及其为什么可以将其声明和定义在头文件实现并被多个源文件引用
创始人
2025-05-30 18:48:39
0

内联函数和模版函数的常见错误、以及其为什么可以将其声明和定义在头文件实现并被多个源文件引用

    • 1 编译器对内联函数的处理
      • 1.1 编译阶段
      • 1.2 汇编阶段
      • 1.3 链接阶段(编译器不对一个内联函数进行内联展开时不出现重定义错误的原因)
    • 2 内联函数的声明和定义
      • 2.1 内联函数声明和定义分离的错误
      • 2.2 在不同源文件中定义内联函数的不同代码实现的错误
    • 3 编译器对模版函数的处理
      • 3.1 编译阶段
      • 3.2 汇编阶段
      • 3.3 链接阶段(编译器实例化模版函数不发生重定义问题的原因)
    • 4 模版函数的声明和定义
      • 4.1 声明和定义分离后的链接错误
      • 4.2 在不同源文件中定义模版函数的不同代码实现的错误


1 编译器对内联函数的处理

所有的问题都得在了解了编译器在编译、汇编、链接阶段对内联函数的处理,才能引刃而解,因此我们先了解编译器在各个阶段对内联函数的处理。

1.1 编译阶段

编译器会将 inline 函数体嵌入到函数调用处,以避免函数调用的开销。这个过程就叫做 “内联展开”(Inline Expansion)。如果编译器决定不对一个函数进行内联展开,那么该函数就会被当做普通函数来处理,就好像没有加上 inline 一样。

1.2 汇编阶段

已经进行了内联展开的 inline 函数体将被转换为相应的汇编代码,并与源文件中的其他汇编代码一起生成目标文件。

1.3 链接阶段(编译器不对一个内联函数进行内联展开时不出现重定义错误的原因)

在链接阶段,由于 inline 函数的定义通常是写在头文件中的,如果编译器在编译阶段对函数进行内联展开,就不会形成符号表;如果编译器决定不对一个函数进行内联展开,多个源文件可能包含同一个 inline 函数的实现。为了避免重复定义的问题,在链接阶段必须将这些实现合并成为一个函数。如果出现多个 inline 函数的实现无法合并的情况,链接器就会报重复定义错误。

综上,inline 函数的处理是在编译、汇编和链接的不同阶段进行的,其中编译阶段是最核心的一个阶段,也是 inline 函数实现的关键。


2 内联函数的声明和定义

内联函数建议声明和定义都在头文件中实现,否则会出现链接错误

2.1 内联函数声明和定义分离的错误

声明和定义分离后:

//inline.h
#include 
inline int add(int a, int b);//inline.cpp
#include "inline.h"
inline int add(int a, int b) 
{return a + b;
}//test.cpp
#include "inline.h"
using std::cout;
using std::cin;
using std::endl;
int main()
{int result = add(1, 2);return 0;
}

报错:
在这里插入图片描述
当我们将内联函数的定义放在源文件中时,编译器会将其视为普通函数,并根据需要在目标文件中生成相应的符号。但是如果某个源文件需要调用该内联函数,但它没有包含该函数的实现,则链接器就无法找到该函数的符号,从而报告链接错误。

2.2 在不同源文件中定义内联函数的不同代码实现的错误

//inline.h
#include 
inline int add(int a, int b);//inline.cpp
#include "inline.h"
inline int add(int a, int b) 
{return a + b;
}//test.cpp
#include "inline.h"
using std::cout;
using std::cin;
using std::endl;
inline int add(int a, int b)
{return a - b;
}
int main()
{int result = add(1, 2);cout << result << endl;return 0;
}

在链接期间,由于存在两个不同的 add 函数实现,链接器无法确定要选择哪个实现,从而报告符号重复定义的错误。

在某些编译器(如vs2019)下,我们发现其并没有报错:

在这里插入图片描述
需要注意的是,尽管某些编译器可能支持这种行为,但在 C++ 标准中,多个源文件中包含相同名称但实现不同的内联函数是不被允许的,并且可能会导致不可预测的行为和不可移植性问题。因此,我们应该遵循 C++ 标准并避免在多个源文件中定义相同名称但实现不同的内联函数。


3 编译器对模版函数的处理

C++ 中的模板函数在编译、汇编和链接时的处理与普通函数不同。由于模板函数是一种泛型编程技术,模板参数的具体值在编译期间才能确定。因此,模板函数需要进行两次编译:一次是模板定义的编译,另一次是模板实例化的编译。

3.1 编译阶段

对于模板函数的每个使用,编译器都会对其进行模板实例化,即根据传入的实参具体化出一个函数实例。这个过程就叫做 “模板实例化”(Template Instantiation)。编译器会将每个实例化后的函数大小确定下来,并生成相应的汇编代码。

3.2 汇编阶段

每个被实例化的函数都会被转换为相应的汇编代码,并与源文件中的其他汇编代码一起生成目标文件。

3.3 链接阶段(编译器实例化模版函数不发生重定义问题的原因)

当多个源文件包含相同的模板实例化时,编译器会分别为它们生成独立的实现,并将它们存储在各自的目标文件中。
跟未进行内联展开的内联函数类似:链接时,链接器会将这些目标文件合并成为一个可执行文件或库,并在符号表中保留每个实例化的唯一名称和地址信息,从而避免了符号重定义的问题。


4 模版函数的声明和定义

4.1 声明和定义分离后的链接错误

//template.h 声明
#include 
template
void swap(T& left, T& right);//template.c 定义
#include "template.h"
template
void swap(T& left, T& right)
{int tmp = left;left = right;right = tmp;
}//test.c 调用
#include "template.h"
using std::cout;
using std::cin;
using std::endl;
int main()
{int a = 0;int b = 10;swap(a, b);cout << a << b << endl;return 0;
}

连接错误:具体来说,当编译器编译一个包含模板函数调用的源文件时,它会根据需要对模板进行实例化,并生成相应的代码。如果模板函数的定义没有被包含在当前的编译单元中,那么编译器就无法生成相应的代码,从而导致链接错误。

在这里插入图片描述

4.2 在不同源文件中定义模版函数的不同代码实现的错误

那么如果在test.cpp文件中也写下一个函数定义呢?
这里我选择在test.cpp中故意写一个不同的函数实现

//template.h
#include 
template
void swap(T& left, T& right);//template.cpp
#include "template.h"
template
void swap(T& left, T& right)
{int tmp = left;left = right;right = tmp;
}//test.cpp
#include "template.h"
using std::cout;
using std::cin;
using std::endl;template
void swap(T& left, T& right)
{left = 0;right = 0;
}
int main()
{int a = 0;int b = 10;swap(a, b);cout << a << ' ' << b << endl;return 0;
}

上述情况下:在链接期间,由于存在两个不同实现的 swap 函数模板,链接器无法确定要选择哪个实现,从而报告符号重复定义的错误。

但是在某些编译器(如vs2019)下,可能这种写法也不会报错:
在这里插入图片描述
需要注意的是,尽管一些编译器可能支持这种行为,但在 C++ 标准中,多个源文件中包含相同名称但实现不同的模板函数仍然是未定义的行为,并且可能导致不可预测的结果和不可移植性问题。因此,我们应该遵循 C++ 标准并避免在多个源文件中定义相同名称但实现不同的模板函数。

相关内容

热门资讯

幼儿园送给教师节老师的祝福语 幼儿园送给教师节老师的祝福语(精选55句)  在日常的学习、工作、生活中,许多人都有过写祝福语的经历...
牛年春节新春祝福语 牛年春节新春祝福语  在我们平凡的日常里,说到祝福语,大家肯定都不陌生吧,祝福语的种类很多,可分为吉...
使用premake帮助生成VS... Premake :https://github.com/premake/premake...
画图解释一个汇编小例子 这是对应的C代码 int caller() {int temp1 = 125;int tem...
学习黑客十余年,如何成为一名高...   1. 前言  说实话,一直到现在,我都认为绝大多数看我这篇文章的读者...
春节的拜年祝福语 春节的拜年祝福语精选  春节要登场,鞭炮快乐响,下面是春节的拜年祝福语精选,一起来看一下吧。  1....
爱国的对联 爱国的对联  说起“爱国“,人们首先想到的是那些家喻户晓的爱国英雄,但其实还有豪言壮语的`爱国对联。...
中秋节散文随笔抒情 中秋一词,最早见于《周礼》。根据我国的古代历法,汉服中秋农历八月十五,在一年秋季的八月中旬,故称“中...
【B/S医院区域云LIS系统源... ▶  【云LIS系统源码说明】: ❶云LIS源码,系统完全采用B/S架构...
智慧政务一网通办云平台顶层设计... 本资料来源公开网络,仅供个人学习,请勿商用,如有侵权请联系...
HCIE-Cloud Comp... 我是它的题目,要就来先看看我咯 确认密码是否正确 错误出现场景:键盘大小键输入错误、手抖写错 操作:...
滑步处理 - 让动画脚步和移位... 游戏制作中,通常的做法是让动画播放跑步或者其他移动动画,然后让刚体跟着移...
春节祝福语41条 2021年精选春节祝福语锦集41条  让我高歌一曲海皮呗时特吐有,海皮呗时特吐有,海皮呗时特吐有,海...
“明天”的我们散文随笔 “明天”的我们散文随笔  现在的我们成年了,我只是希望,我在今天的每一丝微笑都比明天要灿烂,我的每一...
春节拜年祝福语短信 2020年精选春节拜年祝福语短信集合38条  愿你是清新的海风,鼓起白色的船帆;愿你是坚固的大船,剪...
大班教育随笔 大班教育随笔(通用20篇)  无论是身处学校还是步入社会,大家都写过随笔吗?随笔是过去社会较为流行的...
二维码及条形码智能检测软件(P... 摘要:二维码及条形码智能检测软件用于检测常用条形码和二维码,对其位置进行...
伤感心情随笔 伤感心情随笔合集15篇  无论是身处学校还是步入社会,大家都写过随笔吗?随笔,或讲述文化知识,或发表...
大班区域活动随笔   开展区域活动的意义,就是培养孩子的发散思维和提高思考能力。那么大班区域活动随笔会写些什么呢?下面...
小学语文教师随笔3篇   导读:随笔,顾名思义:随笔一记,是散文的一个分支,是议论文的一个变体,兼有议论和抒情两种特性,通...