niedziela, 23 października 2016

C - Funkcja inline

W tym poście chciałbym opisać funkcje inline.



W przypadku standardowych funkcji mamy do czynienia z przekazywaniem argumentów na stos czy do rejestrów. Następnie następuje skok do kodu funkcji, odczytanie przekazanych argumentów, wykonanie obliczeń, przekazanie wyniku, skok powrotny oraz odczytanie wyniku. 

Działanie funkcji inline polega na tym, że w kodzie maszynowym, czyli już po skompilowaniu, funkcje zdefiniowane będą wstawiane bezpośrednio w miejsce wywołania. Nie będzie takiego samego skoku jak w przypadku opisanych wcześniej standardowych funkcji. Wobec tego stosuje się wszędzie tam gdzie zależy na maksymalnej szybkości działania.

Trzeba pamiętać, że słowo inline jest tylko sugestią dla kompilatora i niekoniecznie będzie to działało tak jak się oczekuje.

Wobec tego przyjrzyjmy się przykładowemu programowi:

  1. #include <stdio.h>
  2. #include <stdint.h>
  3.  
  4. #define MAX_INT(a, b) a > b ? a : b
  5.                              
  6. inline static int MaxInt(int a, int b)
  7. {
  8.   return a > b ? a : b;
  9. }
  10.  
  11. int main()
  12. {
  13.     int a = 19;
  14.     int b = 84;
  15.     printf("Podaj wynik inline %d\r\n", MaxInt(a, b));
  16.     printf("Podaj wynik define %d\r\n", MAX_INT(a, b));
  17.     return 0;
  18. }

Powyżej znajduje się funkcja inline wybierająca większą wartość z dwóch podanych zmiennych. Dodatkowo instrukcja warunkowa define wykonująca identyczną operację. 

Wynik obu tych operacji będzie identyczny. Natomiast samo wykonanie niekoniecznie. Dużo zależy od kompilatora czy uzna funkcję jako inline czy od wybranego poziomu optymalizacji. Z tego powodu warto sprawdzać kod asemblerowy. 

Tutaj z pomocą przychodzi godbolt:


Jak można zauważyć powyżej funkcja nie została potraktowana jako inline (funkcja wywołana z instrukcją call). 

Aby wymusić wykonanie funkcji jako inline można posłużyć się atrybutem always_inline:


W tym przypadku całość działa tak jak by się tego oczekiwało. 

Dla poziomu optymalizacja O2, całość także działa poprawnie (już bez atrybutu):

 
Dla wyższych poziomów optymalizacji funkcja jest i tak traktowana jako inline, nawet z usuniętą informacją. Kompilator zdecyduje czy dana funkcja może być traktowana w ten sposób. Decydują o tym flagi kompilatora które krótko opiszę poniżej. 

Można stosować też odpowiednie flagi kompilatora w celu wymuszenia odpowiedniego zachowania np.

-fno-inline - nie wykonuj funkcji jako inline, chyba, że została opisana atrybutem always_inline,
-finline-functions - wszystkie funkcje rozpatrywane jako inline. Automatycznie ustawione na poziomach optymalizacji O2, O3 oraz Os.

Funkcja inline musi być zdeklarowana jako static lub extern. 

  1. extern int MaxInt(int a, int b);
  2. inline int MaxInt(int a, int b)
  3. {
  4.     return a > b ? a : b;
  5. }
  6. // lub                        
  7. // inline static int MaxInt(int a, int b)

Inline jest głównie stosowany w przypadku gdy chcemy otrzymywać kontrolę sprawdzania błędów, otrzymywać ostrzeżenia. Dodatkowo ułatwia pracę podczas debugowania, ponieważ do funkcji inline można wejść jak do każdej innej funkcji.