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

Теги: Встраиваемые функции, 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