内联函数的本质是以空间换时间:增加了代码量,但减少了函数调用时带来的性能损耗。默认情况下,编译器会自己决定是否将一个函数内联。在编译优化时,默认情况下编译器会根据时间对性能进行优化。如果编译器决定内联一个函数,它会确保避免大量的代码增长。当使用-Oz或-Os等编译优化选项来限制代码大小时,编译器对内联做出智能的判断,并旨在将代码量保持在最小。
如果将函数内联可以提高代码性能,Arm嵌入式编译器会自动将函数内联化,这种内联不会显著提高代码量,此外用户也可以用编译选项或者关键词来控制是否将一个函数内联。
内联选项、关键字或属性 | 描述 |
---|---|
__inline__ | 在函数定义或声明中指定此关键字,以提示编译器支持内联函数。然而,对于每个函数调用,仍然是由编译器来决定是否将函数内联。这个关键字相当于__inline。 |
__attribute__((always_inline)) | 在函数定义或声明中指定此函数属性,以告诉编译器始终内联此函数,除了递归函数等某些例外情况。此属性将会覆盖-fno-inline-functions选项。 |
__attribute__((noinline)) | 在函数定义或声明中指定此函数属性,以告诉编译器不要内联函数。这个属性相当于 __declspec(noinline) . |
-fno-inline-functions | 这是一个编译器命令行选项。将此选项指定给编译器以禁用内联。此选项覆盖__inline__关键字的功能。 |
- 在大多数情况下,是否将函数内联的决定权最好留给编译器。使用
__inline__
或者inline
关键字修饰函数,只是向编译器暗示该函数应该内联,但最终的决定权仍在编译器。除非使用__attribute__((always_inline)),它会强制编译器将该函数内联。 - c++和C99提供了内联语言关键字。此内联语言关键字的效果与使用内联编译器关键字的效果相同。但是,C99模式下的效果与c++或其他不遵循C99标准的C中的效果不同。
- 函数内联通常发生在更高的优化级别,例如-O2,除非特别指定了__attribute__((always_inline))。
c源代码的默认语义规则遵循C99规则。当建议将函数内联时,对于内联,编译器希望找到不使用内联的函数的等效实现。当编译器决定不内联时,它使用这个等价的实现。如果编译器找不到等效的实现,则会失败并显示以下错误 :
"Error: L6218E: Undefined symbol <symbol> (referred from <file>)"
为了避免这个问题,有几个办法:
- 提供函数的等效实现。
- 将inline或__inline__关键字更改为static inline。
- 删除inline或__inline__关键字,因为它只是作为一个建议。
- 使用-std=gnu90选项,使用GNU C90方言编译程序。
下面将演示__attribute__((always_inline))
和-fno-inline-functions 对编译带来的影响:
int bar(int a)
{
a=a*(a+1);
return a;
}
__attribute__((always_inline)) static int row(int a)
{
a=a*(a+1);
return a;
}
int foo (int i)
{
i=bar(i);
i=i-2;
i=bar(i);
i++;
i=row(i);
i++;
return i;
}
在示例程序中,bar和row是一样的函数,只不过 使用 __attribute__((always_inline))来修饰了row函数,接下来使用如下两条指令对该源码进行编译:
armclang --target=arm-arm-none-eabi -march=armv8-a -S file.c -O2 -o file_with_inline.s
armclang --target=arm-arm-none-eabi -march=armv8-a -S file.c -O2 -o file_no_inline.s -fno-inline-functions
程序1- 使用-fno-inline-functions的编译结果:
foo: @ @foo
.fnstart
@ BB#0:
.save {r11, lr}
push {r11, lr}
bl bar
sub r0, r0, #2
bl bar
add r1, r0, #1
add r0, r0, #2
mul r0, r0, r1
add r0, r0, #1
pop {r11, pc}
.Lfunc_end0:
.size foo, .Lfunc_end0-foo
.cantunwind
.fnend
程序2-不使用-fno-inline-functions的编译结果:
foo: @ @foo
.fnstart
@ BB#0:
add r1, r0, #1
mul r0, r1, r0
sub r1, r0, #2
sub r0, r0, #1
mul r0, r0, r1
add r1, r0, #1
add r0, r0, #2
mul r0, r0, r1
add r0, r0, #1
bx lr
.Lfunc_end0:
.size foo, .Lfunc_end0-foo
.cantunwind
.fnend
从程序1汇编代码中可以看出, __attribute__((always_inline))
属性将会覆盖-fno-inline-functions选项,即使在命令行中使用了-fno-inline-functions,来告诉编译器不要内联函数,但编译器仍把row函数内联化了。
从程序2汇编代码中可以看出,就算没有使用inline关键词来修饰函数,编译器会自动判断,将bar函数内联。
参考链接:
Inlining functions