Расширения GCC: Как использовать встраиваемые функции в качестве макросов
Использование встраиваемых (inline-) функций позволяет оптимизировать выполнение кода программы за счет их интегрирации внутри вызывающей функции. При этом нужно помнить, что не всякий встраиваемый код следует делать встраиваемым, так как в процессе компиляции программы он может быть упрощен за счет известных значений, относящиеся к константным ( См. листинг 2б ).
Влияние встраиваемых функций на размер кода является непредсказуемым и все зависит от соответствующего случая, когда объектный код может быть больше или меньше. Так же можно заставить GCC попытаться интегрировать все "достаточно простые" функции внутри вызывающей её функции с использованием опции -finline-functions.
GCC способен выполнять три различные семантики декларации inline-функций:
- первая доступна с -std=gnu89 или -fgnu89-inline или когда атрибут gnu_inline представлен во всех inline-декларациях;
- вторая доступна с опциями -std=c99, -std=c11, -std=gnu99 или -std=gnu11 ( без -fgnu89-inline)
- третья, последняя используется при компиляции 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а) отдельно в объектном модуле для совместного использования. При этом, даже если вызов такой функции будет встречаться в других объектных модулях, компилятор осуществит процедуру встраивания кода на этапе компиляции.