ARM嵌入式编程优化之内联函数(inline)

内联函数的本质是以空间换时间:增加了代码量,但减少了函数调用时带来的性能损耗。默认情况下,编译器会自己决定是否将一个函数内联。在编译优化时,默认情况下编译器会根据时间对性能进行优化。如果编译器决定内联一个函数,它会确保避免大量的代码增长。当使用-Oz或-Os等编译优化选项来限制代码大小时,编译器对内联做出智能的判断,并旨在将代码量保持在最小。

如果将函数内联可以提高代码性能,Arm嵌入式编译器会自动将函数内联化,这种内联不会显著提高代码量,此外用户也可以用编译选项或者关键词来控制是否将一个函数内联。

Table 1. 函数内联
内联选项、关键字或属性 描述
__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 (referred from )"

为了避免这个问题,有几个办法:

  • 提供函数的等效实现。
  • 将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

你可能感兴趣的:(Linux_C,ARM,Compiler,arm开发,c语言,嵌入式编译器,内联函数)