Расширения GCC: Как использовать встраиваемые функции в качестве макросов

К оглавлению


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

Влияние встраиваемых функций на размер кода является непредсказуемым и все зависит от соответствующего случая, когда объектный код может быть больше или меньше. Так же можно заставить GCC попытаться интегрировать все "достаточно простые" функции внутри вызывающей её функции с использованием опции -finline-functions.

GCC способен выполнять три различные семантики декларации inline-функций:

  1. первая доступна с -std=gnu89 или -fgnu89-inline или когда атрибут gnu_inline представлен во всех inline-декларациях;
  2. вторая доступна с опциями -std=c99-std=c11-std=gnu99 или -std=gnu11 ( без -fgnu89-inline)
  3. третья, последняя используется при компиляции C++.

При этом GCC не произведет встраивание, если не указана опция -O c любым из уровней оптимизации. Кроме случая, когда в прототипе этой функции указан атрибут always_inline, как показано в листинге 1а, что подтверждает результат дизассемблирования GDB в листинге 1б.

Листинг 1а

 /* Прототип встраиваемой функции decinc, которая производит 
    инкремент значения, переданного ей  по указателю в пределе 
    текущей трансляции, т.е. локально. */
  static inline int decinc ( int *a) 
__attribute__((always_inline)); 
 
  int main ( void ) 
  {
     int b = 100000, r;
   
     r = decinc (&b); /*Вызываем встраиваимую функцию */

     /*Чепятыем результат */
     printf ("return value: %d, value (by pointer): %d\n", b, r );

     return 0;
  }

  static inline int decinc ( int *a)
  {
    return (*a)++; /* в конечном итоге останется 
    лишь операция инкремента, реализуемого через 
    счетчик */
  }
  /* 
     Команда для сборки: gcc -g -finline-functions -o test inline-use.c  
   */
 

Листинг 1б

   0x080483fb <+0>:	lea    0x4(%esp),%ecx
   0x080483ff <+4>:	and    $0xfffffff0,%esp
   0x08048402 <+7>:	pushl  -0x4(%ecx)
   0x08048405 <+10>:	push   %ebp
   0x08048406 <+11>:	mov    %esp,%ebp
   0x08048408 <+13>:	push   %ecx
   0x08048409 <+14>:	sub    $0x14,%esp
   0x0804840c <+17>:	movl   $0x186a0,-0x14(%ebp)
   0x08048413 <+24>:	lea    -0x14(%ebp),%eax
   0x08048416 <+27>:	mov    %eax,-0x10(%ebp)
   0x08048419 <+30>:	mov    -0x10(%ebp),%eax
   0x0804841c <+33>:	mov    (%eax),%eax
   0x0804841e <+35>:	lea    0x1(%eax),%ecx
   0x08048421 <+38>:	mov    -0x10(%ebp),%edx
   0x08048424 <+41>:	mov    %ecx,(%edx)
   0x08048426 <+43>:	mov    %eax,-0xc(%ebp)
   0x08048429 <+46>:	mov    -0x14(%ebp),%eax
   0x0804842c <+49>:	sub    $0x4,%esp
   0x0804842f <+52>:	pushl  -0xc(%ebp)
   0x08048432 <+55>:	push   %eax
   0x08048433 <+56>:	push   $0x80484e0
   0x08048438 <+61>:	call   0x80482d0 <printf@plt> 
   0x0804843d <+66>:	add    $0x10,%esp
   0x08048440 <+69>:	mov    $0x0,%eax
   0x08048445 <+74>:	mov    -0x4(%ebp),%ecx
   0x08048448 <+77>:	leave  
   0x08048449 <+78>:	lea    -0x4(%ecx),%esp
   0x0804844c <+81>:	ret    
 

Для стандарта ISO C90 взамен inline следует писать __inline__ (См. Альтернативные ключевые слова).

Перечисленные ранее способы встраивания работают одинаково в двух случаях из трех, даже когда в прототипе функции отсутствует ключевое слово inline, но указывается в теле её определения, как показано в листинге 2a и в листинге 2б после дизассемблирования в GDB.

Листинг 2a

 /* 
    Прототип встраиваемой функции decinc, которая производит 
    инкремент значения, переданного ей  по указателю не только 
    в текущей, но и последующих трансляциях, т.е. глобально. 
  */
 extern int decinc (int *a);
  
 int main ( void ) 
 {
     int b = 100000, r;
   
     r = decinc (&b); /*Вызываем встраиваимую функцию */

     /*Чепятыем результат */
     printf ("return value: %d, value (by pointer): %d\n", b, r );

     return 0;
 }

/*
   Определение тела встраиваемой функции decinc
 */ 
 inline int decinc (int *a)
 {
      return (*a)++;/* Как следует из Листинга 2б операция 
       инкремента заменяется на макроподстановку
       константного значения 0x186a1 */
 }

 /*
   Команда для сборки: 
               gcc -g -O2 -finline-functions -o test inline-use.c
  */
 

Листинг 2б


   0x08048300 <+0>:	lea    0x4(%esp),%ecx
   0x08048304 <+4>:	and    $0xfffffff0,%esp
   0x08048307 <+7>:	pushl  -0x4(%ecx)
   0x0804830a <+10>:	push   %ebp
   0x0804830b <+11>:	mov    %esp,%ebp
   0x0804830d <+13>:	push   %ecx
   0x0804830e <+14>:	sub    $0x8,%esp
   0x08048311 <+17>:	push   $0x186a0
   0x08048316 <+22>:	push   $0x186a1
   0x0804831b <+27>:	push   $0x80484d0
   0x08048320 <+32>:	call   0x80482d0 <printf@plt>
   0x08048325 <+37>:	mov    -0x4(%ebp),%ecx
   0x08048328 <+40>:	add    $0x10,%esp
   0x0804832b <+43>:	xor    %eax,%eax
   0x0804832d <+45>:	leave  
   0x0804832e <+46>:	lea    -0x4(%ecx),%esp
   0x08048331 <+49>:	ret    
 

Комбинация ключевых слов inline и extern почти всегда приводит к эффекту макроподстановки. Такой способ встраивания позволяет определить прототип функции в заголовочном файле, а её тело (как показано в листинге 2а) отдельно в объектном модуле для совместного использования. При этом, даже если вызов такой функции будет встречаться в других объектных модулях, компилятор осуществит процедуру встраивания кода на этапе компиляции.

К оглавлению