概述
有关内联函数的一些事,《Thinking in C++》和《Effective C++》的学习笔记。
内联函数是C++用来替换宏而引入的。
C中的宏在省去函数调用的开销的同时引入了不易发现的BUG,主要是由对参数求值引起的。
一、内联函数如何起作用:
对于普通函数,编译器只把函数名称(对于C++来说也包含了参数类型?)和返回值记录在符号表里,对于内联函数除此之外还在符号表里记录其函数体(究竟存放源代码还是编译后的汇编指令就看编译器的实现了)。当遇到内联函数的调用时,编译器首先检查调用是否正确(参数类型检查,返回结果是否被正确使用——对于普通函数也进行这些检查),检查无误后将内联函数的函数体替换掉对它的调用,从而省去调用函数的开销(参数入栈,汇编CALL等)。
二、如何让函数成为内联:
使用关键字inline指定函数为内联。
声明函数为内联是没有意义也没有必要的,要让函数成为内联需要在定义时指定。
对于类的成员函数有一些特殊,在类的声明内定义的成员函数自动成为内联,在类体外定义的函数就需要显式指定了。
由于内联函数的定义必须被包含在每一个使用它的文件里,而定义的不统一又会造成多重定义的error,因此将内联函数的定义放在头文件里是合适的。
将内联函数的定义放在头文件里的根本原因是,大多数建制环境都是在编译期执行内联的,而编译期要将函数调用替代为函数体,就需要指导函数长什么样。
——>扩展:
这点也和function template相同,函数模板也通常放在头文件里。这是因为大多数编译器在编译期进行函数模板的具现化,因此它需要知道函数模板长什么样子。
所以不应该有“函数模板都是inline”的推论。
三、编译器和内联函数:
有几种情况编译器无法执行函数的内联,遇到这种情况,即使你将函数定义为内联,编译器仍然会将其按照普通函数处理——为函数体分配存储空间。如果这必须在多个编译单元之间进行,这很可能导致多重定义错误,若一定要执行的话(取决于编译器),链接器会被告知忽略多重定义。下面列出这些情况:
1)函数过于复杂。这取决于编译器,但大多数编译器都会选择放弃,这时候即使内联你也得不到任何显著的效率提升。一般,函数体内带有循环或者递归,都会被认为过于复杂。
2)函数被取址时。这就迫使编译器不得不为其分配内存从而产生一个地址,然而在不被取址的地方,内联仍有可能被执行。
另外,对virtual函数的调用也做不到inlining,因为virtual意味着“等待,直到运行期才知道调用哪个函数”,因此编译器无法知道调用的具体函数,也就没法进行代码替换。
一个不应存在的顾虑:
由于类的声明中给出函数的定义时,可能类的全部成员函数没有都完成声明,所以可能会有这样的顾虑:
class A{
int i;
public:
A() :i(0){}
int f() const { return g() + 1; }
int g() const { return i; }
};
main()
{
A a;
a.f();
}
f()定义内调用了g(),而此时g()还没有声明。
其实这种顾虑是不必要的,因为语言定义规定在类中所有内联函数的评估都要在类声明完成时进行。
四、何时使用内联:
内联肯定会提高程序的效率,尽管有时这种提高微乎其微。
内联不一定会带来代码膨胀,如果函数体十分短小,甚至比调用函数所生成的代码都小,那就不会代码膨胀,反而会缩小object code。
这给何时使用内联提供了指导。
inline还有另一个需要考虑的成本:
对于程序开发者,升级一个inline的函数比升级一个outline的函数成本更大,因为inline函数定义在头文件中,因此更新后所有使用到此函数的客户文件都需要重新编译,而对于outline函数,则只需要重新链接即可。如果是动态链接,甚至没有成本。
——The End
最后
以上就是微笑荔枝为你收集整理的有关内联函数的一些事的全部内容,希望文章能够帮你解决有关内联函数的一些事所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复