C/C++编程:C语言中的内联函数

1060 篇文章 299 订阅
C++中的内联函数可以减少函数调用开销,常用于优化频繁调用的小型函数。内联不是强制的,而是对编译器的建议,编译器会考虑代码大小和复杂性来决定是否内联。内联函数应避免用于大型、循环、虚函数、构造函数和析构函数。内联函数通常定义在头文件中,以方便编译器进行内联处理。过度使用内联可能导致代码膨胀,影响程序性能。正确的内联使用策略是谨慎选择并限制在必要的小函数上。
摘要由CSDN通过智能技术生成

在C语言中,如果一些函数被频繁调用,不断地有函数入栈,即函数栈,会造成栈空间或栈内存的大量消耗。

  • 栈空间就是指放置程式的局部数据也就是函数内数据的内存空间
  • 在系统下,栈空间是有限的,假如频繁大量的使用就会造成因栈空间不足所造成的程式出错的问题,函数的死循环递归调用的最终结果就是导致栈内存空间枯竭。

为了解决这个问题,特别的引入了inline修饰符,表示为内联函数。

inline函数的优点:

  • 功能像函数,但是免除了”函数调用的成本“
  • 编译器最优化机制常常被设计用于那些"不含函数调用"的代码,所以inline某个函数,编译器可能对此执行相关语境的最优化

inline函数的缺点:

  • inline函数背后的整体观念是:将"对此函数的每一个调用"都以函数本体替换之。这可能增加你的目标码的大小,造成函数体积太大。即使有虚拟内存,inline造成的代码膨胀也会导致额外的换页行为,降低指令高速缓存装置的击中率,从而导致效率降低。
  • 换个角度说,如果inline函数的本体很小,编译器针对"函数本体"所产出的码可能比针对"函数调用"所产生的码小。也就是说,将函数inline可能导致较小的目标码和较高的指令高速缓存装置击中率。

怎么做

记住,inline只是对编译器的一个申请,不是强制命名。这项申请可能隐喻提出,也可以明确提出。

  • 隐喻方式是将函数定义在class定义式内。这样的函数通常是成员函数或者友元函数(定义于类内的友元函数)
class Person{
public:
	int age() const {return theAge; }  //  隐式inline
private:
	int theAge;
};
  • 明确声明inline函数的做法是在其定义式前加上关键字inline
template<typename T>
inline const T& std::max(const T&a, const T&b){
	return a < b ? b : a;
}

注意

1、关键字inline 必须与函数定义体放在一起才能使函数成为内联,仅将inline 放在函数声明前面不起任何作用。

如下风格的函数Foo 不能成为内联函数:

inline void Foo(int x, int y); // inline 仅与函数声明放在一起 void Foo(int x, int y) { }

而如下风格的函数Foo 则成为内联函数:

void Foo(int x, int y); inline void Foo(int x, int y) // inline 与函数定义体放在一起 { }

尽管在大多数教科书中内联函数的声明、定义体前面都加了inline 关键字,但inline 不应该出现在函数的声明中。这个细节虽然不会影响函数的功能,但是体现了高质量C++/C 程序设计风格的一个基本原则:声明与定义不可混为一谈,用户没有必要、也不应该知道函数是否需要内联。

2、关于头文件

  • inline函数一定被放在头文件内,因为大多数build environment是在编译过程冲进行inlining,而为了将一个"函数调用"替换为"被调用函数的本体",编译器必须知道那个函数长什么样子。inlining在大多数C++中是编译行为
  • template通常也被放置在头文件内,因为它一旦被使用,编译器为了将它具现化,需要知道它长什么样子。模板的具现化与inlining无关。如果你正在写一个模板而你认为所有根据此模板具现出来的函数都应该inlined,就将此目标inline;如果你写的模板没有理由要求它所具现的每一个函数都是inlined,就应该避免将这个函数声明为inline(无论是隐式还是显示)。因为inlining需要成本

3、inline的使用是有所限制的。inline只是对编译器的一个申请,编译器可以加以忽略。

  • inline只适合函数体内代码简单的函数数使用,大部分编译器都拒绝将大部分复杂(比如带有while、switch、递归、虚函数)的函数inlining。一个较为合理的经验准则是, 不要内联超过 10 行的函数
  • 虚函数、递归函数、构造函数、析构函数都不应该被内联
    • 所有对虚函数的调用也都会使得inlining落空。因为virtual意味着”等待,直到运行期才确定调用哪个函数“,而inline意味着"执行前,先将调用动作替换为被调用函数的本体"。如果编译器不知道该调用哪个函数就不会inlining
  • 类内部的函数一般会自动内联。所以某函数一旦不需要内联,其定义就不要再放在头文件里,而是放到对应的 .cc 文件里。这样可以保持头文件的类相当精炼,也很好地贯彻了声明与定义分离的原则。
  • 有时候虽然编译器有意愿inlining某个函数,还是可能为该函数生成一个函数本体。举个例子,如果程序要取某个inline函数的地址,编译器通常必须为此函数生成一个outline函数本体,因为不可能将一个指针指向并不存在的函数。也就是说编译器通常不对”通过函数指针而进行的调用“实施inline
  • 对inline函数的调用有可能被inlined,也有可能不被inlined,取决于该调用的实施方式:
inline void f() { };  // 假设编译器有意愿inlining"对f的调用"
voiud (*pf)();  

f();  // 这个调用将被inlined,因为它是一个正常调用
pf(); 	// 版本inlined,因为它通过函数指针达成
  • 请避免inlining构造函数和析构函数
class Base{
	...
private:
	std::string bm1, bm2;
};

class Derived : public Base{
public:
	Derived(){}  //它真的为空吗?
	...
private:
	std::string dm1, dm2, dm3;	
};

C++对于”对象被创建和被销毁时发生什么事“做了各式各样的保证:

  • 当你使用new,动态创建的对象将被其构造函数自动初始化
  • 当你使用delete,对应的析构函数会被调用
  • 当你创建一个对象,其每一个基类以及每一个成员变量都会被自动构造
  • 当你销毁一个对象,反向程序的析构行为也会自动发生

也就是说Derived构造函数不一定为空:

Derived::Derived(){
	Base::Base();
	try{ dm1.std::string::string()}
	catch(){
		Base::~Base();
		throw;
	}
	try{ dm2.std::string::string()}
	catch(){
		dm1.std::string::~string()
		Base::~Base();
		throw;
	}
	// ...
}

析构函数也是如此。

慎用内联

内联能提高函数的执行效率,为什么不把所有的函数都定义成内联函数?如果所有的函数都是内联函数,还用得着“内联”这个关键字吗?

  • 内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。
  • 另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。

以下情况不宜使用内联:

(1)如果函数体内的代码比较长,使用内联将导致内存消耗代价较高。只有当函数只有 10 行甚至更少时才将其定义为内联函数.

(2)如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大。

一个好的编译器将会根据函数的定义体,自动地取消不值得的内联(这进一步说明了inline 不应该出现在函数的声明中)。

因此, 慎用内联。当你决定哪些函数应该被声明为inline时,策略如下:一开始先不要将任何函数声明为inline,或者事少将inlining施行范围内的那些”一定成为内联“或者”十分平淡无奇“(比如Person::agr)的函数身上

总结

因此,将内联函数放在头文件里实现是合适的,省却你为每个文件实现一次的麻烦.而所以声明跟定义要一致,其实是指,如果在每个文件里都实现一次该内联函数的话,那么,最好保证每个定义都是一样的,否则,将会引起未定义的行为,即是说,如果不是每个文件里的定义都一样,那么,编译器展开的是哪一个,那要看具体的编译器而定.所以,最好将内联函数定义放在头文件中.

  • 请将大多数inlining限制在小型、被频繁调用的函数上。这可以使得日后的调试过程和二进制升级更容易,也可以使得潜在的代码膨胀问题最小化,使程序的速度提升最大化
  • 不要只因为function template出现在头文件,就将它们声明为inline
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值