对下面讲解的一个目录:
以下使用GNU89的标准
内联就是一个关键字inline加载函数定义处,告诉编译器在编译的时候请对这个函数调用的地方进行内联调用(这里说的请,编译器可以拒绝这个操作因为内联函数的失败)
内联是为了节约函数的调用开销而诞生的,我们在调用一个普通函数的时候,存在的额外的开销(压栈、出栈)等。内联是让编译器使用内联的方式编译函数,在调用这个内联函数的时候,直接把这个函数展开,不存在压栈、出栈的操作,汇编中就是不断的寄存器的读取,内联就减少了这些寄存器的使用,一句话就是快速执行函数体
使用起来很简单直接在函数定义处加上inline关键字,注意是定义处
? 1. 例子1(普通函数)
#include <iostream>
inline void example() {
std::cout << "example" << std::endl;
}
int main() {
example();
return 0;
}
在类的头文件直接声明定义的不需要加参数会自动inline
但是如果是头文件声明(不加inline的类函数声明),.cpp定义那就还是需要函数定义加上inline
class Test {
public:
Test() { //类中直接实现自动内联
}
~Test();
};
inline Test::~Test() { //类外定义实现,需要inline声明
}
这里只说和inline有关的
想要实现程序内inline关键字的真正运行需要加上编译优化选项,默认的gcc编译的优化选项是-O0的,如果直接gcc test.c -o xx,这样是不内联的,有些版本还编译不过
想要编译通过需要使用 -O -O1 -O2 -O3 -Og,-O和-O1是相同的,2和3是优化的等级提升
-O1中除了添加了对inline内联的支持还加入了-finline-functions-called-once, 官方解释
-finline-functions-called-once
Consider all functions called once for inlining into their caller even if they are not marked . If a call to a given function is integrated, then the function is not output as assembler code in its own right. staticinline
Enabled at levels , , and , but not . -O1-O2-O3-Os-Og
意思就是查看代码中所有的函数如果仅仅被调用了一次那么默认 inline内联
如果有函数调用了这样的static修饰的函数,那么static 函数本身不会被汇编代码输出(默认的程序会保存到.text段),static和内联的事情我们内联的时候分析
-Og
在保持快速编译和良好调试体验的同时,提供合理的优化级别,这个会在-O1基础上去除finline-functions-called-once,因为finline-functions-called-once会让我们在函数调用一次的情况下,即使不加inline也会被优化被带inline
也说一下-O2,里面有一个-finline-functions
-finline-functions
Consider all functions for inlining, even if they are not declared inline. The compiler heuristically decides which functions are worth integrating in this way.
If all calls to a given function are integrated, and the function is declared , then the function is normally not output as assembler code in its own right. `static`
Enabled at levels , , . Also enabled by and . -O2-O3-Os-fprofile-use-fauto-profile
意思就是考虑将所有的函数内联编译,即使他们没有加上inline。
如果给定函数的调用集中一起,就是函数前面加了static修饰,这样这个函数最终就只有一个,就不会生成汇编代码,static和内联的事情我们内联的时候分析
还有一个-finline-small-functions
-finline-small-functions
Integrate functions into their callers when their body is smaller than expected function call code (so overall size of program gets smaller). The compiler heuristically decides which functions are simple enough to be worth integrating in this way. This inlining applies to all functions, even those not declared inline.
Enabled at levels , , . -O2-O3-Os
编译器会看,当调用那些函数体长度小于调用者的函数调用的长度时,会直接内联插入,即使没有函数没有使用inline编译,这个之后进行说明
? 下面我们从汇编层面看看,因为最终c程序是要变成汇编的
? 函数的实现就是getvalue对参数进行了加一并返回,主函数调用并打印2
? 1. 加入inline的函数
//test_inline.c
#include <stdio.h>
#include <stdlib.h>
inline int getValue(int i) {
return i + 1;
}
int main() {
int c = getValue(1);
printf("start = %d\n", c);
return 0;
}
//test.c
#include <stdio.h>
#include <stdlib.h>
int getValue(int i) {
return i + 1;
}
int main() {
int c = getValue(1);
printf("start = %d\n", c);
return 0;
}
进行汇编编译
汇编编译的前提说明:
对没有加入inline的函数进行分析
gcc -g -Og -std=gnu89 test.c -o test
objdump -S test
我们调重点看getValue和Main
000000000000066a <getValue>:
//test.c
#include <stdio.h>
#include <stdlib.h>
int getValue(int i) {
return i + 1;
66a: 8d 47 01 lea 0x1(%rdi),%eax
}
66d: c3 retq
000000000000066e <main>:
int main() {
66e: 48 83 ec 08 sub $0x8,%rsp
int c = getValue(1);
672: bf 01 00 00 00 mov $0x1,%edi
677: e8 ee ff ff ff callq 66a <getValue>
}
__fortify_function int
printf (const char *__restrict __fmt, ...)
{
return __printf_chk (__USE_FORTIFY_LEVEL - 1, __fmt, __va_arg_pack ());
67c: 89 c2 mov %eax,%edx
67e: 48 8d 35 9f 00 00 00 lea 0x9f(%rip),%rsi # 724 <_IO_stdin_used+0x4>
685: bf 01 00 00 00 mov $0x1,%edi
68a: b8 00 00 00 00 mov $0x0,%eax
68f: e8 ac fe ff ff callq 540 <__printf_chk@plt>
printf("start = %d\n", c);
return 0;
}
694: b8 00 00 00 00 mov $0x0,%eax
699: 48 83 c4 08 add $0x8,%rsp
69d: c3 retq
69e: 66 90 xchg %ax,%ax
? 可以看出
? 1. 对应的函数代码存在汇编中
? 2. 进行了callq 66a
? 3. 我们再来看看加了inline的,这次test.c内容在函数前面加上inline
? gcc -Og -g test_inline.c -std=gnu89 -o test_inline
? objdump -S test_inline
? 我们调重点看getValue和Main
000000000000066a <main>:
#include <stdlib.h>
inline int getValue(int i) {
return i + 1;
}
int main() {
66a: 48 83 ec 08 sub $0x8,%rsp
}
__fortify_function int
printf (const char *__restrict __fmt, ...)
{
return __printf_chk (__USE_FORTIFY_LEVEL - 1, __fmt, __va_arg_pack ());
66e: ba 02 00 00 00 mov $0x2,%edx
673: 48 8d 35 aa 00 00 00 lea 0xaa(%rip),%rsi # 724 <_IO_stdin_used+0x4>
67a: bf 01 00 00 00 mov $0x1,%edi
67f: b8 00 00 00 00 mov $0x0,%eax
684: e8 b7 fe ff ff callq 540 <__printf_chk@plt>
int c = getValue(1);
printf("start = %d\n", c);
return 0;
}
689: b8 00 00 00 00 mov $0x0,%eax
68e: 48 83 c4 08 add $0x8,%rsp
692: c3 retq
693: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
69a: 00 00 00
69d: 0f 1f 00 nopl (%rax)
? 1. 对应的函数代码存在汇编中
? 2. 进行了mov $0x2,%edx的直接计算
? 2. 进直接进行了mov $0x2,%edx的计算替换,没有的刚刚的call调用
常见的搭配有 static inline 、extern inline
看一下几个编译器的区别
GNU89:
inline: the function may be inlined (it‘s just a hint though). An out-of-line version is always emitted and externally visible. Hence you can only have such an inline defined in one compilation unit, and every other one needs to see it as an out-of-line function (or you‘ll get duplicate symbols at link time).
extern inline will not generate an out-of-line version, but might call one (which you therefore must define in some other compilation unit. The one-definition rule applies, though; the out-of-line version must have the same code as the inline offered here, in case the compiler calls that instead.
static inline will not generate a externally visible out-of-line version, though it might generate a file static one. The one-definition rule does not apply, since there is never an emitted external symbol nor a call to one.
C99 (or GNU99):
inline: like GNU89 "extern inline"; no externally visible function is emitted, but one might be called and so must exist
extern inline: like GNU89 "inline": externally visible code is emitted, so at most one translation unit can use this.
static inline: like GNU89 "static inline". This is the only portable one between gnu89 and c99
C++:
A function that is inline anywhere must be inline everywhere, with the same definition. The compiler/linker will sort out multiple instances of the symbol. There is no definition of static inline or extern inline, though many compilers have them (typically following the gnu89 model).
? static 我们都知道当修饰函数的时候代表这个函数在当前文件是唯一的不可以extern对函数进行引用,限定了函数的访问空间
看一个例子:
//test_inline.c
#include <stdio.h>
#include "caculMax.h"
int main() {
int value = getMax(4, 5);
printf("value is %d\n", value);
return 0;
}
//caculMax.h
#ifndef __CACULMAX_H_
#define __CACULMAX_H_
static inline int getMax(int left, int right) {
return left > right ? left : right;
}
#endif
汇编编译:
#include <stdio.h>
#include "caculMax.h"
int main() {
66a: 48 83 ec 08 sub $0x8,%rsp
}
__fortify_function int
printf (const char *__restrict __fmt, ...)
{
return __printf_chk (__USE_FORTIFY_LEVEL - 1, __fmt, __va_arg_pack ());
66e: ba 05 00 00 00 mov $0x5,%edx
673: 48 8d 35 aa 00 00 00 lea 0xaa(%rip),%rsi # 724 <_IO_stdin_used+0x4>
67a: bf 01 00 00 00 mov $0x1,%edi
67f: b8 00 00 00 00 mov $0x0,%eax
684: e8 b7 fe ff ff callq 540 <__printf_chk@plt>
int value = getMax(4, 5);
printf("value is %d\n", value);
return 0;
}
689: b8 00 00 00 00 mov $0x0,%eax
68e: 48 83 c4 08 add $0x8,%rsp
692: c3 retq
693: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
69a: 00 00 00
69d: 0f 1f 00 nopl (%rax)
直接被替换为了结果,汇编中没有对应的内联代码记录了
extern 我们常见的做法就是声明变量或者函数,直接使用,等到链接器最终链接到一起进行使用
作用就类同一个宏定义, extern inline定义的函数不会生成具体的汇编代码
那么当你的程序中有些函数使用的内联函数地址或者可能汇编被拒绝,针对这种情况你需要定义一个函数体相同的非汇编代码
Linux使用 string.h中
extern inline char * strncpy(char * dest,const char *src,int count)
{
__asm__("cld\n"
"1:\tdecl %2\n\t"
"js 2f\n\t"
"lodsb\n\t"
"stosb\n\t"
"testb %%al,%%al\n\t"
"jne 1b\n\t"
"rep\n\t"
"stosb\n"
"2:"
::"S" (src),"D" (dest),"c" (count):"si","di","ax","cx");
return dest;
}
在string.c中:
#ifndef __GNUC__
#error I want gcc!
#endif
#define extern
#define inline
#define __LIBRARY__
#include <string.h>
消除了extern和inline,这样最终的汇编代码也是有内联函数的吗,即使取函数地址也还是可以找到对应代码
例子:
//test_inline.c
#include <stdio.h>
#include "caculMax.h"
int main() {
int value = getMax(4, 5);
printf("value is %d\n", value);
return 0;
}
//caculMax.h
#ifndef __CACULMAX_H_
#define __CACULMAX_H_
extern inline int getMax(int left, int right) {
return left > right ? left : right;
}
#endif
000000000000066a <main>:
#include <stdio.h>
#include "caculMax.h"
int main() {
66a: 48 83 ec 08 sub $0x8,%rsp
}
__fortify_function int
printf (const char *__restrict __fmt, ...)
{
return __printf_chk (__USE_FORTIFY_LEVEL - 1, __fmt, __va_arg_pack ());
66e: ba 05 00 00 00 mov $0x5,%edx
673: 48 8d 35 aa 00 00 00 lea 0xaa(%rip),%rsi # 724 <_IO_stdin_used+0x4>
67a: bf 01 00 00 00 mov $0x1,%edi
67f: b8 00 00 00 00 mov $0x0,%eax
684: e8 b7 fe ff ff callq 540 <__printf_chk@plt>
int value = getMax(4, 5);
printf("value is %d\n", value);
return 0;
}
689: b8 00 00 00 00 mov $0x0,%eax
68e: 48 83 c4 08 add $0x8,%rsp
692: c3 retq
693: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
69a: 00 00 00
69d: 0f 1f 00 nopl (%rax)
实现内联嵌入,并且具体的内联函数没有出现在汇编中,那么如果我们取地址呢?
//test_inline.c
#include <stdio.h>
#include "caculMax.h"
int main() {
int value = getMax(4, 5);
printf("value is %d\n", value);
return 0;
}
//caculMax.h
#ifndef __CACULMAX_H_
#define __CACULMAX_H_
extern inline int getMax(int left, int right) {
return left > right ? left : right;
}
#endif
编译:
gcc -Og -g -std=gnu89 test_inline.c -o test_inline
结果:
/tmp/ccwVHoij.o: In function printf‘: /usr/include/x86_64-linux-gnu/bits/stdio2.h:104: undefined reference to
getMax‘
collect2: error: ld returned 1 exit status
未定义的引用
我们添加一个caculMax.c
#define inline
#define extern
#include "caculMax.h"
编译:
gcc -Og -g -std=gnu89 test_inline.c caculMax.c -o test_inline
通过
可以自行查看汇编,发现生成了汇编代码
可能使用extern inline 会出现重复定义的错误,这个就是编译便准的问题了
参见这篇文章
static inline最常用,编译器的对应优化也会去选择是否生成函数体,一般inline都加static,想多个文件调用就放在.h中,不会发生重定义,编译器最终会链接到一起
extern inline最终也是进行内联替换,但是使用是extern 链接的方式,尽量不使用
1. 没有开启-O优化
2. 使用了可变参数
3. 内存分配函数malloc
4. 可变长度数据类型变量
5. 非局部goto
6. 递归函数
7. 内联的函数不能有循环语句、
8. 不能对内联函数地址
9. 函数内部不能太复杂,不能存在过多条件判断
举个例子:
内存分配函数malloc
#include <stdio.h>
static inline int strlen1(const char* str) {
return str ? (*str == ‘\0‘) ? 0 : 1 + strlen1(str + 1) : 0;
}
int main() {
int value = strlen1("haha");
printf("value is %d\n", value);
return 0;
}
gcc -Og -g -std=gnu89 test.c -o test
查看汇编
//test.c
#include <stdio.h>
static inline int strlen1(const char* str) {
return str ? (*str == ‘\0‘) ? 0 : 1 + strlen1(str + 1) : 0;
66a: 48 85 ff test %rdi,%rdi
66d: 74 42 je 6b1 <strlen1+0x47>
66f: 80 3f 00 cmpb $0x0,(%rdi)
672: 75 06 jne 67a <strlen1+0x10>
674: b8 00 00 00 00 mov $0x0,%eax
679: c3 retq
67a: 48 89 f8 mov %rdi,%rax
67d: 48 83 c0 01 add $0x1,%rax
681: 74 27 je 6aa <strlen1+0x40>
683: 80 7f 01 00 cmpb $0x0,0x1(%rdi)
687: 75 09 jne 692 <strlen1+0x28>
689: b8 00 00 00 00 mov $0x0,%eax
68e: 83 c0 01 add $0x1,%eax
}
691: c3 retq
static inline int strlen1(const char* str) {
692: 48 83 ec 08 sub $0x8,%rsp
return str ? (*str == ‘\0‘) ? 0 : 1 + strlen1(str + 1) : 0;
696: 48 8d 78 01 lea 0x1(%rax),%rdi
69a: e8 cb ff ff ff callq 66a <strlen1>
69f: 83 c0 01 add $0x1,%eax
6a2: 83 c0 01 add $0x1,%eax
}
6a5: 48 83 c4 08 add $0x8,%rsp
6a9: c3 retq
return str ? (*str == ‘\0‘) ? 0 : 1 + strlen1(str + 1) : 0;
6aa: b8 00 00 00 00 mov $0x0,%eax
6af: eb dd jmp 68e <strlen1+0x24>
6b1: b8 00 00 00 00 mov $0x0,%eax
6b6: c3 retq
00000000000006b7 <main>:
int main() {
6b7: 48 83 ec 08 sub $0x8,%rsp
return str ? (*str == ‘\0‘) ? 0 : 1 + strlen1(str + 1) : 0;
6bb: 48 8d 3d b3 00 00 00 lea 0xb3(%rip),%rdi # 775 <_IO_stdin_used+0x5>
6c2: e8 a3 ff ff ff callq 66a <strlen1>
6c7: 8d 50 01 lea 0x1(%rax),%edx
}
__fortify_function int
多次的strlen1调用,肯定不是内联了
我们可以加上-Winline让gcc对标志成inline但不能替换的函数给出警告,以及原因
gcc -Og -g -std=gnu89 -Winline test.c -o test
test.c: In function ‘main’:
test.c:3:19: warning: inlining failed in call to ‘strlen1’: call is unlikely and code size would grow [-Winline]
static inline int strlen1(const char* str) {
^~~~~~~
test.c:4:40: note: called from here
return str ? (*str == ‘\0‘) ? 0 : 1 + strlen1(str + 1) : 0;
^~~~~~~~~~~~~~~~
一目了然
内联是还拥有函数的参数检查和类型判断,宏,仅仅是文本替换,我们都知道宏是容易出问题的,看一个简单的代码
例子
#include <iostream>
#define exa(b, c) b + c
inline int Exa(int b, int c) {
return b + c;
}
void sum(int a) {
std::cout << a * exa(2, 3) << std::endl; //结果 7 , 实际运算为 a * b + c
std::cout << a * Exa(2, 3) << std::endl; //结果 10
}
int main() {
sum(2);
}
当你想使用宏定义函数的时候首先考虑内联
当你想封装一个简短的函数,考虑内联
不同的编译标准对inline的效果不同
使用内联代替去除函数和运算宏,使用const代替变量宏
好处:内联的函数,有函数的基础功能,检查类型等,没有开销,直接插入到调用处
坏处:对待嵌入式这种视存储珍贵的,内联就需要仔细考虑,类似那种用空间换时间的感觉,
内联让函数体变得臃肿,需要编译器支持
extern inline 重定义
gnu的优化标准
Linux内核注释0.11
原文:https://www.cnblogs.com/zero-waring/p/14106173.html