Встраиваемые функции

Теги: Встраиваемые функции, inline, __inline, __forceinline, always_inline



Встраиваемые функции

Вызов функции, хоть в си он очень быстрый, отнимает некоторое время. В современном си есть возможность объявлять встраиваемые функции. При компиляции вызов функции будет заменён её телом.

Для объявления встраиваемой функции используется ключевое слово inline (или __inline, __forceinline в зависимости от компилятора)

#include <stdio.h>

inline int fun (int a, int b) __attribute__((always_inline));

int main() {
        int result = fun(2, 3);
        printf("%d", result);
        getchar();
        return 0;
}

inline int fun(int a, int b) {
        return a + b;
}

Здесь, для тестирования, использованы атрибуты компилятора gcc, которые форсируют встраивание. Рассмотрим код, который компилируется при использовании inline

0004016f0 <_main>:
  4016f0:       55                      push   %ebp
  4016f1:       89 e5                   mov    %esp,%ebp
  4016f3:       83 e4 f0                and    $0xfffffff0,%esp
  4016f6:       83 ec 20                sub    $0x20,%esp
  4016f9:       e8 c2 00 00 00          call   4017c0 <___main>
  4016fe:       c7 44 24 18 02 00 00    movl   $0x2,0x18(%esp)
  401705:       00 
  401706:       c7 44 24 14 03 00 00    movl   $0x3,0x14(%esp)
  40170d:       00 
  40170e:       8b 54 24 18             mov    0x18(%esp),%edx
  401712:       8b 44 24 14             mov    0x14(%esp),%eax
  401716:       01 d0                   add    %edx,%eax
  401718:       89 44 24 1c             mov    %eax,0x1c(%esp)
  40171c:       8b 44 24 1c             mov    0x1c(%esp),%eax
  401720:       89 44 24 04             mov    %eax,0x4(%esp)
  401724:       c7 04 24 64 50 40 00    movl   $0x405064,(%esp)
  40172b:       e8 a8 1f 00 00          call   4036d8 <_printf>
  401730:       e8 cb 1f 00 00          call   403700 <_getchar>
  401735:       b8 00 00 00 00          mov    $0x0,%eax
  40173a:       c9                      leave  
  40173b:       c3                      ret    
И без использования (видим вызов функции CALL в строке 10)
004016f0 <_main>:
  4016f0:       55                      push   %ebp
  4016f1:       89 e5                   mov    %esp,%ebp
  4016f3:       83 e4 f0                and    $0xfffffff0,%esp
  4016f6:       83 ec 20                sub    $0x20,%esp
  4016f9:       e8 d2 00 00 00          call   4017d0 <___main>
  4016fe:       c7 44 24 04 03 00 00    movl   $0x3,0x4(%esp)
  401705:       00 
  401706:       c7 04 24 02 00 00 00    movl   $0x2,(%esp)
  40170d:       e8 24 00 00 00          call   401736 <_fun>
  401712:       89 44 24 1c             mov    %eax,0x1c(%esp)
  401716:       8b 44 24 1c             mov    0x1c(%esp),%eax
  40171a:       89 44 24 04             mov    %eax,0x4(%esp)
  40171e:       c7 04 24 64 50 40 00    movl   $0x405064,(%esp)
  401725:       e8 be 1f 00 00          call   4036e8 <_printf>
  40172a:       e8 e1 1f 00 00          call   403710 <_getchar>
  40172f:       b8 00 00 00 00          mov    $0x0,%eax
  401734:       c9                      leave  
  401735:       c3                      ret    

00401736 <_fun>:
  401736:       55                      push   %ebp
  401737:       89 e5                   mov    %esp,%ebp
  401739:       8b 55 08                mov    0x8(%ebp),%edx
  40173c:       8b 45 0c                mov    0xc(%ebp),%eax
  40173f:       01 d0                   add    %edx,%eax
  401741:       5d                      pop    %ebp
  401742:       c3                      ret    
  401743:       90                      nop

Inline функции имеют ряд недостатков. Во-первых, компилятор может отказать во встраивании функции, если это снижает скорость выполнения. Снижение может происходить в том числе и из-за того, что кеш инструкций будет переполняться. Вообще, inline следует скорее рассматривать как подсказку компилятору, а не руководство к действию.

Во-вторых, для встраиваемых систем, в которых разные функции могут располагаться в разных сегментах памяти, это недопустимо, так как вызов может произойти не в том сегменте, в котором ожидалось.

В-третьих, это даёт достаточно малый прирост производительности, но усложняет процесс сборки, оптимизации и увеличивает время компиляции. Во время внешнего связывания (external linkage) также могут возникнуть проблемы, если функция не была объявлена inline во всех компилируемых модулях. Поэтому часто встраиваемые функции объявляют также статическими.

Q&A

Всё ещё не понятно? – пиши вопросы на ящик email
Реализация перегрузки функций по типу с помощью макроса _Generic