关于inline函数和普通函数编译的问题?

tjjccnu 2005-04-05 10:52:23
最近研读c++primer,受益匪浅。
遇到一个问题,请大家帮忙看看。

一般为了避免函数的重复定义,我们通常不把函数定义(不是声明)放在头文件中,

然而inline函数却应该放在头文件中,这是为什么呢?

我的理解是inline函数在调用是内联展开,编译时并不开栈,所以不会引起重复定义,普通函数就正好相反,不知道这样理解是否正确?

另外想问问,inline函数和一般函数编译时刻,还有什么区别呢?具体是想问问编译时刻,函数是怎么处理的,(定义就开栈,记录相应入口地址,分配变量存储区等等?)

请大家推荐几本书看看,谢谢了!
...全文
918 33 打赏 收藏 转发到动态 举报
写回复
用AI写文章
33 条回复
切换为时间正序
请发表友善的回复…
发表回复
eric8231 2005-04-12
  • 打赏
  • 举报
回复
补充一点,static函数和inline函数都是内部连接的,但他们的区别是:多个编译单位的static函数拥有多个实体,而多个编译单位的inline函数拥有同一个唯一的实体。

但按标准上对内、外连接的定义,我认为是否拥有同一个实体并不是判断内、外连接的标准。

不论是static函数还是inline函数,对于某个编译单位来说,如果你不直接定义他们或将它们的定义include进来,你就无法引用其他编译单位定义的那个函数实体。
eric8231 2005-04-12
  • 打赏
  • 举报
回复
To whyglinux(山青水秀):

>> 你上面程序中出现的连接错误,是因为你违反的是ODR

我上面的程序是为了证明inline函数是内部连接的。我想强调的是,ODR是规则,而编译器采用的两种不同的连接方式是手段。

我上面的两个测试说明了编译器对待inline函数的方式:编译器没有像对待普通函数那样产生一个共其他编译单位访问的外部符号,而是将inline函数的实体局限在了本编译单位中。

如果你说inline函数是外部连接的,那上面两个程序应该能通过连接才对!可实际不是。




>> 因此,判断一个名字是内部还是外部连接的方法就是:检查在不同编译单位中的这个名字是否为同一事物。
>> 对于函数名或者对象名来说,可以通过比较它们在内存中的地址来实现。

对此我不同意,14882的 3.5 节 给出了外部连接和内部连接的定义:

--when a name has external linkage,the entity it denotes can be refered to by names from scopes of
other translation units or from other scopes of the same translation unit.

--when a name has internal linkage,the entity it denotes can be referred to by names from other scopes in
the same translation unit.

由此,我认为,判断一个名字是外部连接还是内部连接的标准是:看这个名字是否可以被其他编译单位访问到。

一个有外部连接的名字可以被其他编译单位访问到,但你看看我上面两个程序中的inline函数,它们都不能被其他的编译单位调用,因为它们都是内部连接的。

再强调一下,ODR是规则,编译器对待一个名字的两种连接方式是手段。inline函数具有内部连接绝对不和ODR矛盾。

另外,我虽然没有完整的看过14882上关于ODR的说明,但就我了解的情况,ODR实际上给编译器留下了很多灵活性,规则上有一些是不要求做检查的,比如我可以在两个编译单位定义两个签名完全一样但定义不同的inline函数,从而造成“非法”但可以通过编译、连接的混乱局面。


langzi8818 2005-04-12
  • 打赏
  • 举报
回复
学习中,讲的好。
whyglinux 2005-04-12
  • 打赏
  • 举报
回复
>> 首先,inline函数并不一定总能成功的被编译器内联,这要看函数定义的复杂程度以及编译器的“进取度”

C++标准的 7.1.2 Function specifiers 有这么一句话,你可以看看:

An implementation is not required to perform this inline substitution at the point of call;(你上面的话就是这个意思)however, even if this inline substitution is ommited, the other rules for inline functions defined by 7.1.2 shall still be respected.(这是我想告诉你的)
whyglinux 2005-04-12
  • 打赏
  • 举报
回复 1
你上面程序中出现的连接错误,是因为你违反的是ODR。ODR其中的一条就是对inline函数的,再引述如下:

An inline function shall be defined in every translation unit in which it is used.

比如,在你给的第一个程序中,f()是inline函数,那么为了符合ODR,除了在文件test1.cpp中给出这个函数的定义之外,还应该在main.cpp中给出它的定义,而不仅仅是声明了事(因为在main.cpp中要使用这个函数)。这就是上面的规则告诉我们的:“inline函数应该在使用它的每一个编译单位中定义”。

再重复一遍:这是inline函数和非inline函数在定义方面的不同,这个区别说明不了连接属性的问题。

inline函数和非inline函数在定义方面为什么不一样呢?因为inline函数是在编译时需要函数的定义实现,以便将将函数代码插入到函数调用的地方,所以需要在每一个编译单位中包含它的实现;而非inline函数则不同:它是在编译之后的连接阶段才实现将函数调用和函数的定义进行连接(函数的连接属性其实就决定了程序连接时定位函数定义的搜索范围:是只在本编译单位寻找(内部连接)还是在整个程序范围内寻找(外部连接)),以便在执行函数的时候能找到函数的执行代码。

连接(Linkage)还说明了什么问题呢?如果一个名字具有外部连接,则说明了在 整个程序中 这个名字代表的是同一个事物(对象、函数、类型等)。如果一个名字具有内部连接属性,则它只能在它所在的 编译单位内 有效,即使在其它编译单位中出现了与之相同的名字,也代表的不是同一个事物。

因此,判断一个名字是内部还是外部连接的方法就是:检查在不同编译单位中的这个名字是否为同一事物。对于函数名或者对象名来说,可以通过比较它们在内存中的地址来实现。
eric8231 2005-04-11
  • 打赏
  • 举报
回复
>> 由此可知,inline函数和非inline函数在函数的定义方面的区别是:在一个程序中,
>> 非inline函数有且只能有一个定义,而inline函数可以有多个定义、可以被定义多次
>> (只要每一个定义位于不同的编译单位内),它们都符合One definition rule (ODR)。

呵呵,知道为什么ODR能够正常工作吗?因为类定义、模板定义、inline函数定义都具有内部连接。

编译器要实现ODR,就要采取某种手段。编译器要做的工作之一就是,区别对待具有两种截然不同的连接属性的定义。
当然,正如你上面说的,还可以算上一种“无连接”的定义,但从“无连接”表现出的种种特征来看,似乎也可以将其归为“内部连接”一类。

好了,为了证明inline函数具有内部连接,下面分别就两种inline函数举例,
编译器用的是g++

1. 证明 inline全局函数 的内部连接性

test1.cpp:

inline int f(int i) { return 1; } // 内部连接,其他的.cpp文件无法使用这个函数

-------------------------------------------------------------
main.cpp:

int f(int); // 声明

int main()
{
f(0); // 连接时出错,无法引用test1.cpp中的inline定义。

return 0;
}
-------------------------------------------------------------

这个程序在连接时会出错,我的g++给出的错误信息是:

[Linker error] undefined reference to `f(int)'

很显然,一切都正如所预料的那样,由于inline函数定义具有内部连接,所以在main.cpp中无法调用那个f()




2. 证明 inline成员函数 的内部连接性

test.h:

class A {
public:
inline void foo(); // inline成员函数的声明
};
-------------------------------------------------------------

test.cpp:

#include "test.h"

inline void A::foo() { } // inline成员函数的定义
-------------------------------------------------------------

main.cpp:

#include "test.h"

int main()
{
A a;
a.foo(); // 这里出错! 在main.cpp中无法调用一个在其他编译单位中定义的inline函数

return 0;
}
-------------------------------------------------------------

上面的程序同样在连接时出错,出错信息与上一个类似:

[Linker error] undefined reference to `A::foo()'

因为在main.cpp这个编译单位中,无法调用其他编译单位(test.cpp)中定义的具有内部连接的定义。


从上面两个测试可以看出,inline函数定义具有内部连接,从其他的编译单位无法访问。

要想让上面两个程序通过连接,很简单,只需要将程序中的所有“inline”都去掉。这时,
函数变成了具有外部连接的全局函数或是具有外部连接成员函数。

其实,我所说的“inline声明影响函数的连接性”的说法并不准确。首先,inline函数并不一定总能成功的被编译器内联,这要看函数定义的复杂程度以及编译器的“进取度”; 其次,当人们想把某个定义局部于本编译单位的时候,第一想到的应该是static而不是inline

但不论怎么说,一个真正被施加了内联操作的inline函数具有内部连接,这一点是非常确定的。


whyglinux 2005-04-11
  • 打赏
  • 举报
回复
To eric8231(小诗)

>> 这里的l()之所以具有外部连接,是因为在将其声明为inline的之后又重复的声明了一次,声明成了普通的全局函数。 我想这才是文档中举这个例子的用意吧。

上面的情况,只能是前面的声明对后面的声明或者定义造成影响;后面的声明不能影响前面的。

>> 如果你认为 inline函数 也有外部连接的,那么请回答一个问题,为什么大家都建议将inline函数的定义放在.h文件中呢?

这就是inline函数和非inline函数在定义方面的区别了。C++的“One definition rule”(C++标准 3.2 One definition rule)是这么告诉我们的:

No translation unit shall contain more than one definition of any variable, funtion, class type, enumeration type or template.

Every program shall contain exactly one definition of every non-inline function or object that is used in that program.

An inline function shall be defined in every translation unit in which it is used.

由此可知,inline函数和非inline函数在函数的定义方面的区别是:在一个程序中,非inline函数有且只能有一个定义,而inline函数可以有多个定义、可以被定义多次(只要每一个定义位于不同的编译单位内),它们都符合One definition rule (ODR)。不能用定义方面的这种差别来说明连接属性的差别。

另外,对于连接(linkage),建议你先找一些有关的资料看看。
sloriver 2005-04-08
  • 打赏
  • 举报
回复
一般为了避免函数的重复定义,我们通常不把函数定义(不是声明)放在头文件中,
然而inline函数却应该放在头文件中,这是为什么呢?
---------------------------
可放可不放,也可以像非inline函数那样在头文件中声明,在.cpp文件中定义inline函数
eric8231 2005-04-08
  • 打赏
  • 举报
回复
再说明白点吧,如果一个.h文件里有一个具有外部连接的定义,而且这个.h文件又被包含到了两个以上的.c文件中(这是多数情况),那么整个程序无法通过连接,原因是重复定义了那个具有外部连接的东西。
eric8231 2005-04-08
  • 打赏
  • 举报
回复
To whyglinux(山青水秀):

这里的l()之所以具有外部连接,是因为在将其声明为inline的之后又重复的声明了一次,声明成了普通的全局函数。 我想这才是文档中举这个例子的用意吧。



如果你认为 inline函数 也有外部连接的,那么请回答一个问题,为什么大家都建议将inline函数的定义放在.h文件中呢?

试问,假如存在有外部连接的inline函数,那么我们怎么敢将它放在.h文件中呢?

问题就在于: 放在.h中的函数一定要具有内部连接!
whyglinux 2005-04-08
  • 打赏
  • 举报
回复
>> 你真的让我无话可说了,为什么总是断章取义的看C++标准呢?

C++标准既然是作为一个标准,它的语句都是经过了仔细推敲的。每一句话都非常严密,上下文相关极小,基本上你从中摘取一句话(以句号结束),它都是成立的,这样做的目的就是方便人们引用标准条例。所以对于 C++标准,你不但可以“断章取义”,人们通常更喜欢的是“断句取义”。

>> 其中有一个例子:
>> inline void l();
>> void l(); // external linkage

难道你不能从中看出问题来吗?用inline修饰函数的声明或者定义即把函数指定为inline函数。按照你的说法:inline函数是内部连接,那连接属性不是跟下面标明为外部连接的同一个函数的声明冲突了吗?可是这里没有冲突,即 void l();是外部连接,inline void l();也是外部连接。
du51 2005-04-08
  • 打赏
  • 举报
回复
以空间换时间.
eric8231 2005-04-08
  • 打赏
  • 举报
回复
To whyglinux(山青水秀):

你真的让我无话可说了,为什么总是断章取义的看C++标准呢?7.1.1节举的那些例子是在说明连续的声明Specifiers时的情况,这与以上一直在讨论的关于inline函数的连接性质有什么关系?

其中有一个例子:
inline void l();
void l(); // external linkage

这个例子并不是 说在有些情况下inline函数居于外部连接性!

如果你对一直讨论的主题感兴趣,我真心的希望你认真的加入讨论,而不是随隋便便的抄几句标准上的话断章取义的搬过来。



唉……也许是我自己太较真了吧
whyglinux 2005-04-08
  • 打赏
  • 举报
回复
To eric8231(小诗)

请看C++标准 7.1.1 Storage class specifiers。就不一一反驳了,不是几句话就能说清楚的。
kobefly 2005-04-07
  • 打赏
  • 举报
回复
讲的不错

学习
不过,inline也只是一个建议,声明为inline的函数,并不一定最终都会成为inline函数的
whyglinux 2005-04-07
  • 打赏
  • 举报
回复
To eric8231(小诗)

对于函数来说,改变它的连接属性的关键字是extern和static,而不是inline。

函数可分为全局函数和类的成员函数两大类。全局函数默认的连接属性是外部连接;成员函数的连接属性受到其所在的类的连接属性的限制。简单起见,下面的讨论不妨以全局inline函数为例说明。

一个名字具有外部连接属性,其含义是:这个名字除了在其定义所在的编译单位内使用之外,还可以在其它编译单位使用。

位于不同编译单位的一个inline函数如果具有外部连接属性,那就代表这些函数实际上是同一个函数。如果是同一个函数,那么位于不同编译单位的这个函数的地址应该是相同的。

反之,如果一个inline函数具有内部连接属性,由于不同编译单位的函数不会相互产生影响,所以尽管函数类型和函数名一致,它们也属于不同的函数,其特征是:位于不同编译单位的函数的地址是不相等的。

你可以根据这个特征写一个程序来验证inline外部函数中的inline对于连接属性是否有影响。

我在VC++ .NET中的验证结果是:inline关键字不会影响外部函数的连接属性;影响外部函数连接属性的只有static和extern关键字:一个函数(无论是inline与否)默认是extern的(外部连接),用static修饰后成为内部连接。

由此可以得出结论:inline不影响函数的连接属性。
FlyWithJo 2005-04-07
  • 打赏
  • 举报
回复
两位大侠的讨论看得我头都晕了~~~~~
eric8231 2005-04-07
  • 打赏
  • 举报
回复
To whyglinux(山青水秀) :

>> 对于函数来说,改变它的连接属性的关键字是extern和static,而不是inline。

我认为这句话是错的,extern不能改变函数的连接属性!

extern对于一个变量的作用是明显的,它“声明一个其他编译单位的名字”。
而extern对于一个函数的作用就不那么明显了,如果用extern修饰一个函数声明,
例如“extern int f();” 那么实际上和不用extern 的情况差不多。
换言之,“extern int f();” 几乎等于 “int f();” ,至少他们的差别不在于“连接性”上。



>> 位于不同编译单位的一个inline函数如果具有外部连接属性,那就代表这些函数实际上是同一个函数。

不存在有外部连接性的inline函数


>> 你可以根据这个特征写一个程序来验证inline外部函数中的inline对于连接属性是否有影响。

你是怎么验证的?把源码复制下来讨论一下吧。
我上面(2005-04-05 12:19:00)验证的那个例子不知你看了没有,

我的结论是:关键字inline确实改变的函数的连接性,如果去掉inline,我那个三个文件的程序根本无法通过连接。


>> 一个函数(无论是inline与否)默认是extern的(外部连接),用static修饰后成为内部连接。

我想,必须澄清一个概念,关键字extern并不代表“外部连接” ,尽管它很容易让人们这么认为。

而且不能简单的认为用static修饰过的函数就一定具有内部连接,举个例子,非内联的member函数,不论是否是static的,都具有外部连接性。





由于是否使用了关键字inline,会直接导致函数连接性的不同,所以我说:“inline影响函数的连接性”

欢迎讨论
whyglinux 2005-04-05
  • 打赏
  • 举报
回复
To eric8231(小诗)

1、注意inline只对于类中的成员函数有效;inline不应该用在外部函数上。
2、
>> 实际上程序中出现的任何一个名字要么是内部连接的,要么是外部连接的。
一个名字实际上可以有三种情况:内部连接、外部连接和无连接。
eric8231 2005-04-05
  • 打赏
  • 举报
回复
建议把inline函数放在.h文件中,这与inline的内联展开没有关系
加载更多回复(13)

64,637

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧