【c++随笔05】C++ 内联函数的优势和使用方法

编程入门 行业动态 更新时间:2024-10-07 05:27:17

【c++随笔05】C++ <a href=https://www.elefans.com/category/jswz/34/1724866.html style=内联函数的优势和使用方法"/>

【c++随笔05】C++ 内联函数的优势和使用方法

【c++随笔05】C++ 内联函数的优势和使用方法

  • 一、内联函数的概念
  • 二、内联函数的优势
  • 三、内联函数的使用方法
  • 四、示例代码
  • 五、总结
  • 六、内联函数再探
    • 1、c++内联函数demo
    • 2、内联函数发生在什么阶段?
    • 3、编译mian.cpp——生成编译文件main.s
    • 4、未看到有内联替换?
    • 5、编译优化等级有哪些?
    • 6、使用-O1 优化等级编译我们的代码

QQ技术交流群:921273910
博客原创地址:

概述:
在 C++ 中,内联函数是一种编译器优化技术,用于替代函数调用过程中的开销。通过将代码直接插入到调用处,内联函数可以显著提高程序的执行效率。本文将介绍内联函数的优势和使用方法,并提供示例帮助读者更好地理解和应用这一特性。

一、内联函数的概念

内联函数是一种用于告知编译器对特定函数进行内联展开的机制。它通过将函数的定义与声明放置在一起,并使用 inline 关键字进行标记,告知编译器应该将该函数的调用处用函数体直接替换。这种替代避免了函数调用造成的额外开销,提高了程序的执行速度。

二、内联函数的优势

减少函数调用开销:函数调用涉及到参数的传递、栈帧的创建和销毁等额外操作,而内联函数将直接插入到调用处,避免了这些开销,提高了程序的执行速度。

编译器优化:内联函数的展开发生在编译阶段,使得编译器可以更好地分析和优化代码。例如,编译器可以进行常量折叠、循环展开等优化,进一步提高程序的性能。

代码可读性:内联函数将函数调用处替换为函数体,使得代码更加紧凑和直观。这对于一些简单的函数,特别是只有几行代码的小函数来说,可以提高代码可读性和可维护性。

三、内联函数的使用方法

  1. 定义方式:内联函数的定义通常放在头文件中,以便在需要使用该函数的源文件中进行内联展开。在函数定义之前使用 inline 关键字进行标记,示例如下:
    cpp
inline int add(int a, int b)
{return a + b;
}
  1. 适用场景:
    内联函数适用于函数体较小、频繁调用且性能要求较高的情况。例如,简单的数学运算、访问器函数等都可以考虑使用内联函数。

  2. 注意事项:
    避免过多使用内联函数,因为如果函数体过大,会导致代码膨胀,反而降低了执行效率。
    内联函数不能递归,并且不能包含复杂的控制流程(比如循环、switch 等)。
    根据实际情况,可以使用编译器选项设置是否启用内联优化,或者使用函数前缀 inline 强制进行内联展开。

四、示例代码

下面是一个使用内联函数的示例代码,用于计算两个整数的平均值:

#include <iostream>inline int add(int a, int b) {return a + b;
}int main() {int x = 5;int y = 10;int result = add(x, y);std::cout << "Result: " << result << std::endl;return 0;
}

在这个示例中,average 函数被定义为内联函数,并且在 main 函数中被调用。由于函数体非常简单,编译器将会直接将函数体插入到调用处,避免了函数调用的开销。

五、总结

内联函数是一种优化技术,通过减少函数调用开销和提供编译器优化,可以显著提高程序的执行效率和性能。在使用内联函数时,我们需要注意合适的场景和使用方法,以充分发挥内联函数的优势。

六、内联函数再探

1、c++内联函数demo

如上,我们使用上面的demo

#include <iostream>inline int add(int a, int b) {return a + b;
}int main() {int x = 5;int y = 10;int result = add(x, y);std::cout << "Result: " << result << std::endl;return 0;
}

2、内联函数发生在什么阶段?

内联函数的展开发生在编译阶段。在编译时,当编译器遇到一个内联函数的调用语句时,它会将该函数的定义直接插入到调用语句的位置上,而不是生成一个函数调用并跳转到函数代码。这个过程称为内联函数的展开或内联替换。

由于内联函数的展开发生在编译时,所以展开后的代码会被包含在最终的可执行文件中,在运行时无需进行函数调用,从而提高了执行效率。

需要注意的是,内联函数只是对编译器的建议,并不能强制要求编译器进行内联展开。编译器会根据自身的内联策略和具体情况来决定是否进行内联展开,所以并不是所有的内联函数都一定会被展开为内联代码。

3、编译mian.cpp——生成编译文件main.s

生成编译文件指令

 g++ -S main.cpp -o main.s

编译文件main.s,内容如下

	.file	"main.cpp".local	_ZStL8__ioinitm	_ZStL8__ioinit,1,1.section	.text._Z3addii,"axG",@progbits,_Z3addii,comdat.weak	_Z3addii.type	_Z3addii, @function
_Z3addii:
.LFB971:.cfi_startprocpushq	%rbp.cfi_def_cfa_offset 16.cfi_offset 6, -16movq	%rsp, %rbp.cfi_def_cfa_register 6movl	%edi, -4(%rbp)movl	%esi, -8(%rbp)movl	-8(%rbp), %eaxmovl	-4(%rbp), %edxaddl	%edx, %eaxpopq	%rbp.cfi_def_cfa 7, 8ret.cfi_endproc
.LFE971:.size	_Z3addii, .-_Z3addii.section	.rodata
.LC0:.string	"Result: ".text.globl	main.type	main, @function
main:
.LFB972:.cfi_startprocpushq	%rbp.cfi_def_cfa_offset 16.cfi_offset 6, -16movq	%rsp, %rbp.cfi_def_cfa_register 6subq	$16, %rspmovl	$5, -4(%rbp)movl	$10, -8(%rbp)movl	-8(%rbp), %edxmovl	-4(%rbp), %eaxmovl	%edx, %esimovl	%eax, %edicall	_Z3addiimovl	%eax, -12(%rbp)movl	$.LC0, %esimovl	$_ZSt4cout, %edicall	_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKcmovl	-12(%rbp), %edxmovl	%edx, %esimovq	%rax, %rdicall	_ZNSolsEimovl	$_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, %esimovq	%rax, %rdicall	_ZNSolsEPFRSoS_Emovl	$0, %eaxleave.cfi_def_cfa 7, 8ret.cfi_endproc
.LFE972:.size	main, .-main.type	_Z41__static_initialization_and_destruction_0ii, @function
_Z41__static_initialization_and_destruction_0ii:
.LFB981:.cfi_startprocpushq	%rbp.cfi_def_cfa_offset 16.cfi_offset 6, -16movq	%rsp, %rbp.cfi_def_cfa_register 6subq	$16, %rspmovl	%edi, -4(%rbp)movl	%esi, -8(%rbp)cmpl	$1, -4(%rbp)jne	.L5cmpl	$65535, -8(%rbp)jne	.L5movl	$_ZStL8__ioinit, %edicall	_ZNSt8ios_base4InitC1Evmovl	$__dso_handle, %edxmovl	$_ZStL8__ioinit, %esimovl	$_ZNSt8ios_base4InitD1Ev, %edicall	__cxa_atexit
.L5:leave.cfi_def_cfa 7, 8ret.cfi_endproc
.LFE981:.size	_Z41__static_initialization_and_destruction_0ii, .-_Z41__static_initialization_and_destruction_0ii.type	_GLOBAL__sub_I_main, @function
_GLOBAL__sub_I_main:
.LFB982:.cfi_startprocpushq	%rbp.cfi_def_cfa_offset 16.cfi_offset 6, -16movq	%rsp, %rbp.cfi_def_cfa_register 6movl	$65535, %esimovl	$1, %edicall	_Z41__static_initialization_and_destruction_0iipopq	%rbp.cfi_def_cfa 7, 8ret.cfi_endproc
.LFE982:.size	_GLOBAL__sub_I_main, .-_GLOBAL__sub_I_main.section	.init_array,"aw".align 8.quad	_GLOBAL__sub_I_main.hidden	__dso_handle.ident	"GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-44)".section	.note.GNU-stack,"",@progbits

可以看到调用add函数

call	_Z3addii

4、未看到有内联替换?

根据你提供的代码和编译后的汇编代码来看,编译器可能没有进行内联替换。这是由于编译器在决定是否对一个函数进行内联展开时通常会考虑多个因素,包括函数的大小、函数调用的频率、编译器的优化等级等。

在你的示例中,add() 函数被声明为 inline,但是最终编译生成的汇编代码中并没有包含 add() 函数的展开代码。这可能是由于编译器认为 add() 函数较小且调用频率不高,所以没有选择进行内联展开。

需要注意的是,即使编译器没有进行内联展开,仍然可以正常调用 add() 函数并获得正确的结果。只是没有使用内联展开导致函数调用的开销略微增加,但在你的示例中这种开销较小。

如果你希望确保 add() 函数能够进行内联展开,你可以尝试以下几种方法:

  • 提高编译器的优化等级:增加编译器的优化等级可能会导致更多的函数被选择进行内联展开。

  • 将函数定义放在头文件中:将 add() 函数的定义放在头文件中,在调用处包含该头文件,这样编译器在编译调用处时能够看到函数定义,有更多机会进行内联展开。

  • 调整代码结构:如果你的函数逻辑相对简单,你可以尝试将函数定义和函数调用放在同一个源文件中,或者将函数调用处移到函数定义之后,以便编译器能够更好地优化和进行内联展开。

总的来说,编译器是否进行内联展开取决于多个因素,包括编译器的实现、优化等级和代码结构等。虽然 inline 关键字可以提供内联的建议,但最终是否进行内联展开还是由编译器决定。

5、编译优化等级有哪些?

g++ 是一种常用的 C++ 编译器,它提供了多个编译优化等级来帮助开发者优化程序的性能和执行效率。以下是 g++ 中常用的编译优化等级:

  1. 不使用任何优化选项:通过使用以下命令编译源代码,关闭所有优化选项。
g++ -O0 source.cpp -o output

这会生成一个未经优化的可执行文件。这种情况下,编译速度较快,但生成的执行文件可能性能较低。

  1. 基本优化等级:使用 -O1 选项开启基本优化等级,例如:
g++ -O1 source.cpp -o output

这会对代码进行一些优化,以提高执行速度。该选项会启用一些简单的优化,如消除无用代码、变量复用和局部强度削减。

  1. 中级优化等级:使用 -O2 选项开启中级优化等级,例如:
g++ -O2 source.cpp -o output

该选项会在基本优化的基础上进一步优化代码,例如内联函数展开、函数内循环展开、功能性内存访问优化等。这可以提高程序的性能,但也会增加编译时间。

  1. 高级优化等级:使用 -O3 选项开启高级优化等级,例如:
g++ -O3 source.cpp -o output

这是最高级别的优化选项。它会启用更多的优化策略,包括更大范围的循环展开、内存访问优化、更复杂的函数内联等。这可能会显著提高程序的性能,但编译时间也会更长。

需要注意的是,随着优化等级的增加,编译时间会变长,而且在某些情况下,高级优化可能会导致意外的行为或不正确的结果。因此,在选择优化等级时,可以根据具体要求和系统需求进行权衡,进行适度的优化。

6、使用-O1 优化等级编译我们的代码

编译指令

[dev1@localhost test(第 3 个复件)]$ g++ -S main.cpp -o main.s -O1

生成的编译文件main.s如下,可以看到确实把add函数优化掉了,还是还是通过call函数调用的。

	.file	"main.cpp".section	.rodata.str1.1,"aMS",@progbits,1
.LC0:.string	"Result: ".text.globl	main.type	main, @function
main:
.LFB976:.cfi_startprocsubq	$8, %rsp.cfi_def_cfa_offset 16movl	$.LC0, %esimovl	$_ZSt4cout, %edicall	_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKcmovl	$15, %esimovq	%rax, %rdicall	_ZNSolsEimovq	%rax, %rdicall	_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_movl	$0, %eaxaddq	$8, %rsp.cfi_def_cfa_offset 8ret.cfi_endproc
.LFE976:.size	main, .-main.type	_GLOBAL__sub_I_main, @function
_GLOBAL__sub_I_main:
.LFB986:.cfi_startprocsubq	$8, %rsp.cfi_def_cfa_offset 16movl	$_ZStL8__ioinit, %edicall	_ZNSt8ios_base4InitC1Evmovl	$__dso_handle, %edxmovl	$_ZStL8__ioinit, %esimovl	$_ZNSt8ios_base4InitD1Ev, %edicall	__cxa_atexitaddq	$8, %rsp.cfi_def_cfa_offset 8ret.cfi_endproc
.LFE986:.size	_GLOBAL__sub_I_main, .-_GLOBAL__sub_I_main.section	.init_array,"aw".align 8.quad	_GLOBAL__sub_I_main.local	_ZStL8__ioinitm	_ZStL8__ioinit,1,1.hidden	__dso_handle.ident	"GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-44)".section	.note.GNU-stack,"",@progbits

更多推荐

【c++随笔05】C++ 内联函数的优势和使用方法

本文发布于:2024-03-14 07:24:27,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1735939.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:内联   使用方法   函数   随笔   优势

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!