Funkcja wbudowana
W językach programowania C i C++ funkcja wbudowana to funkcja kwalifikowana słowem kluczowym inline
; służy to dwóm celom:
- Służy jako dyrektywa kompilatora , która sugeruje (ale nie wymaga), aby kompilator zastąpił treść funkcji w wierszu, wykonując rozszerzenie w wierszu , tj. Wstawiając kod funkcji pod adresem każdego wywołania funkcji, oszczędzając w ten sposób narzut funkcji dzwonić. Pod tym względem jest to analogiczne do specyfikatora klasy przechowywania
rejestrów
, który podobnie zapewnia wskazówkę optymalizacyjną. - Drugim celem
inline
jest zmiana zachowania powiązań; szczegóły tego są skomplikowane. Jest to konieczne ze względu na oddzielną kompilację C/C++ + model powiązań, szczególnie dlatego, że definicja (treść) funkcji musi być zduplikowana we wszystkich jednostkach tłumaczeniowych , w których jest używana, aby umożliwić wstawianie podczas kompilacji , co, jeśli funkcja ma zewnętrzne linkage , powoduje kolizję podczas łączenia (narusza unikalność symboli zewnętrznych). C i C++ (oraz dialekty takie jak GNU C i Visual C++) rozwiązują ten problem na różne sposoby.
Przykład
Funkcję wbudowaną
można napisać w C lub C++ w następujący sposób:
wbudowana nieważna zamiana ( int * m , int * n ) { int tmp = * m ; * m = * n ; * n = tmp ; }
Następnie oświadczenie takie jak:
zamień ( & x , & y );
może zostać przetłumaczone na (jeśli kompilator zdecyduje się na wstawienie, co zwykle wymaga włączenia optymalizacji):
int tmp = x ; x = y ; y = tmp ;
Wdrażając algorytm sortowania wykonujący wiele zamian, może to zwiększyć szybkość wykonywania.
Standardowe wsparcie
C++ i C99 , ale nie ich poprzednicy K&R C i C89 , obsługują funkcje wbudowane
, choć z inną semantyką. W obu przypadkach inline
nie wymusza wstawiania; kompilator może w ogóle nie wstawiać funkcji lub tylko w niektórych przypadkach. Różne kompilatory różnią się stopniem złożoności funkcji, którą mogą zarządzać wbudowaną. Główne kompilatory C++, takie jak Microsoft Visual C++ i GCC obsługują opcję, która pozwala kompilatorom automatycznie wstawiać dowolne odpowiednie funkcje, nawet te, które nie są oznaczone jako funkcje wbudowane .
Jednak po prostu pominięcie słowa kluczowego inline
, aby umożliwić kompilatorowi podejmowanie wszystkich decyzji dotyczących inline, nie jest możliwe, ponieważ linker będzie wtedy narzekał na zduplikowane definicje w różnych jednostkach tłumaczeniowych. Dzieje się tak dlatego, że inline
nie tylko daje kompilatorowi wskazówkę, że funkcja powinna być wstawiona, ale ma również wpływ na to, czy kompilator wygeneruje wywoływalną kopię funkcji poza linią (patrz klasy przechowywania funkcji wbudowanych ) .
Niestandardowe rozszerzenia
GNU C , jako część oferowanego przez siebie dialektu gnu89, obsługuje inline
jako rozszerzenie C89. Jednak semantyka różni się zarówno od semantyki C++, jak i C99. armcc w trybie C90 oferuje również inline
jako niestandardowe rozszerzenie, z semantyką inną niż gnu89 i C99.
Niektóre implementacje zapewniają środki, za pomocą których można zmusić kompilator do wbudowania funkcji, zwykle za pomocą specyfikatorów deklaracji specyficznych dla implementacji:
- Microsoft Visual C++:
__forceinline
- gcc lub clang:
__attribute__((always_inline))
lub__attribute__((__always_inline__))
, z których ten ostatni jest przydatny w celu uniknięcia konfliktu ze zdefiniowanym przez użytkownika makrem o nazwiealways_inline
.
Bezkrytyczne użycie tego może skutkować większym kodem (rozdęty plik wykonywalny), minimalnym lub żadnym wzrostem wydajności, aw niektórych przypadkach nawet utratą wydajności. Co więcej, kompilator nie może wstawiać funkcji we wszystkich okolicznościach, nawet jeśli wstawianie jest wymuszone; w tym przypadku zarówno gcc, jak i Visual C++ generują ostrzeżenia.
Wymuszanie wstawiania jest przydatne, jeśli
-
inline
nie jest respektowane przez kompilator (ignorowane przez analizator kosztów i korzyści kompilatora), oraz - inlining powoduje niezbędny wzrost wydajności
Aby zapewnić przenośność kodu, można zastosować następujące dyrektywy preprocesora:
#ifdef _MSC_VER #define forceinline __forceinline #elif zdefiniowany(__GNUC__) #define forceinline inline __attribute__((__always_inline__)) #elif zdefiniowany(__CLANG__) #if __has_attribute(__always_inline__) #define forceinline __attribute__((__always_inline__)) #else #define forceinline inline #endif #else #define forceinline inline #endif
Klasy przechowywania funkcji wbudowanych
static inline
ma takie same efekty we wszystkich dialektach C i C++. W razie potrzeby wyemituje lokalnie widoczną (poza linią) kopię funkcji.
Niezależnie od klasy pamięci kompilator może zignorować kwalifikator wbudowany
i wygenerować wywołanie funkcji we wszystkich dialektach C i C++.
Efekt klasy pamięci extern
zastosowany lub nie zastosowany do funkcji wbudowanych
różni się między dialektami C i C++.
C99
W C99 funkcja zdefiniowana inline
nigdy, a funkcja zdefiniowana extern inline
zawsze, wyemituje funkcję widoczną z zewnątrz. Inaczej niż w C++, nie ma sposobu, aby poprosić o wyemitowanie widocznej zewnętrznie funkcji współdzielonej przez jednostki tłumaczące tylko wtedy, gdy jest to wymagane.
Jeśli deklaracje inline
są mieszane z deklaracjami extern inline
lub deklaracjami niekwalifikowanymi (tj. bez kwalifikatora inline
lub klasy przechowywania), jednostka translacji musi zawierać definicję (bez względu na to, czy jest to unqualified, inline
czy extern inline
), a widoczna z zewnątrz funkcja będzie być za to wyemitowany.
Funkcja zdefiniowana w wierszu
wymaga dokładnie jednej funkcji o tej nazwie w innym miejscu programu, która jest albo zdefiniowana w linii extern
, albo bez kwalifikatora. Jeśli w całym programie podano więcej niż jedną taką definicję, linker będzie narzekał na zduplikowane symbole. Jeśli jednak go brakuje, linker niekoniecznie narzeka, ponieważ gdyby wszystkie zastosowania można było wstawić, nie jest to potrzebne. Ale może narzekać, ponieważ kompilator zawsze może zignorować inline
kwalifikator i zamiast tego generuj wywołania funkcji, co zwykle ma miejsce, jeśli kod jest kompilowany bez optymalizacji. (Może to być pożądane zachowanie, jeśli funkcja ma być wstawiana wszędzie za wszelką cenę, a jeśli tak nie jest, powinien zostać wygenerowany błąd.) Wygodnym sposobem jest zdefiniowanie funkcji wbudowanych w
plikach nagłówkowych i utworzenie jednego .c plik dla każdej funkcji, zawierający dla niej deklarację wbudowaną extern
i zawierający odpowiedni plik nagłówkowy z definicją. Nie ma znaczenia, czy deklaracja jest przed, czy po dołączeniu.
Aby zapobiec dodaniu nieosiągalnego kodu do końcowego pliku wykonywalnego, jeśli wszystkie zastosowania funkcji zostały wstawione, zaleca się umieszczenie plików obiektowych wszystkich takich plików .c z pojedynczą funkcją wbudowaną extern w statycznym pliku biblioteki ,
zwykle z ar rcs
, a następnie połącz się z tą biblioteką zamiast z pojedynczymi plikami obiektowymi. Powoduje to, że łączone są tylko te pliki obiektowe, które są faktycznie potrzebne, w przeciwieństwie do bezpośredniego łączenia plików obiektowych, co powoduje, że są one zawsze dołączane do pliku wykonywalnego. Jednak plik biblioteki musi być określony po wszystkich innych plikach obiektowych w linii poleceń linkera, ponieważ wywołania funkcji z plików obiektowych określonych po pliku biblioteki nie będą brane pod uwagę przez linker. Wywołania wbudowanych
do innych funkcji wbudowanych
zostaną automatycznie rozwiązane przez konsolidator ( opcja s
w ar rcs
zapewnia to).
Alternatywnym rozwiązaniem jest użycie optymalizacji czasu łącza zamiast biblioteki. gcc udostępnia flagę -Wl, --gc-sections
, aby pominąć sekcje, w których wszystkie funkcje są nieużywane. Będzie tak w przypadku plików obiektowych zawierających kod pojedynczej nieużywanej wbudowanej extern
. Jednak usuwa również wszystkie inne nieużywane sekcje ze wszystkich innych plików obiektowych, nie tylko te związane z nieużywanymi funkcjami wbudowanymi extern .
(Może być pożądane dołączenie do pliku wykonywalnego funkcji, które mają być wywoływane przez programistę z debuggera, a nie przez sam program, np. w celu zbadania wewnętrznego stanu programu). Przy takim podejściu możliwe jest również używać jednego pliku .c ze wszystkimi extern wbudowane
funkcje zamiast jednego pliku .c na funkcję. Następnie plik musi zostać skompilowany za pomocą -fdata-sections -ffunction-sections
. Jednak strona podręcznika gcc ostrzega przed tym, mówiąc: „Używaj tych opcji tylko wtedy, gdy są z tego znaczące korzyści”.
Niektórzy zalecają zupełnie inne podejście, polegające na zdefiniowaniu funkcji jako statycznej wbudowanej
zamiast wbudowanej
w plikach nagłówkowych. Wtedy żaden nieosiągalny kod nie zostanie wygenerowany. Jednak to podejście ma wadę w odwrotnym przypadku: zostanie wygenerowany duplikat kodu, jeśli funkcja nie może być wstawiona w więcej niż jedną jednostkę tłumaczeniową. Emitowany kod funkcji nie może być współużytkowany przez jednostki tłumaczące, ponieważ musi mieć różne adresy. To kolejna wada; przyjmując adres takiej funkcji zdefiniowanej jako static inline
w pliku nagłówkowym zwróci różne wartości w różnych jednostkach tłumaczeniowych. Dlatego statyczne
funkcje wbudowane powinny być używane tylko wtedy, gdy są używane tylko w jednej jednostce tłumaczeniowej, co oznacza, że powinny przechodzić tylko do odpowiedniego pliku .c, a nie do pliku nagłówkowego.
gnu89
semantyka gnu89 inline
i extern inline
jest zasadniczo dokładnym przeciwieństwem semantyki w C99, z wyjątkiem tego, że gnu89 pozwala na przedefiniowanie funkcji extern inline
jako funkcji niekwalifikowanej, podczas gdy C99 inline
nie. Zatem gnu89 extern inline
bez redefinicji jest jak C99 inline
, a gnu89 inline
jest jak C99 extern inline
; innymi słowy, w gnu89 funkcja zdefiniowana inline
zawsze będzie działać, a funkcja zdefiniowana extern inline
nigdy nie wyemituje funkcji widocznej z zewnątrz. Powodem tego jest to, że pasuje do zmiennych, dla których pamięć nigdy nie zostanie zarezerwowana, jeśli zdefiniowano jako extern
i zawsze, jeśli zdefiniowano bez. Z kolei uzasadnieniem dla C99 jest to, że byłoby zdumiewające , gdyby użycie inline
miało efekt uboczny - zawsze emitowało wersję funkcji bez inline - co jest sprzeczne z tym, co sugeruje jej nazwa.
Uwagi dotyczące C99 dotyczące konieczności zapewnienia dokładnie jednej widocznej z zewnątrz instancji funkcji dla funkcji wstawianych oraz wynikającego z tego problemu z nieosiągalnym kodem odnoszą się mutatis mutandis również do gnu89.
gcc do wersji 4.2 włącznie używało wbudowanej
semantyki gnu89, nawet jeśli -std=c99
zostało wyraźnie określone. W wersji 5 gcc przełączyło się z dialektu gnu89 na gnu11, skutecznie włączając domyślnie semantykę inline C99.
Aby zamiast tego użyć semantyki gnu89, należy je jawnie włączyć, albo za pomocą -std=gnu89
albo, aby dotyczyło tylko wstawiania, -fgnu89-inline
, albo dodając atrybut gnu_inline
do wszystkich deklaracji wbudowanych .
Aby zapewnić semantykę C99, albo -std=c99
, -std=c11
, można użyć -std=gnu99
lub -std=gnu11
(bez -fgnu89-inline ).
C++
W C++ funkcja zdefiniowana w wierszu
w razie potrzeby wyemituje funkcję współdzieloną przez jednostki translacji, zazwyczaj umieszczając ją we wspólnej sekcji pliku obiektowego, dla którego jest potrzebna. Funkcja musi mieć wszędzie taką samą definicję, zawsze z wbudowanym
. W C++ extern inline
jest tym samym co inline
. Uzasadnieniem podejścia C++ jest to, że jest to najwygodniejszy sposób dla programisty, ponieważ nie trzeba podejmować żadnych specjalnych środków ostrożności w celu wyeliminowania nieosiągalnego kodu i, podobnie jak w przypadku zwykłych funkcji, nie ma znaczenia, czy extern
jest określony czy nie.
Kwalifikator wbudowany
jest automatycznie dodawany do funkcji zdefiniowanej jako część definicji klasy.
armcc
armcc w trybie C90 zapewnia semantykę extern inline
i inline
, która jest taka sama jak w C++: takie definicje w razie potrzeby emitują funkcję współdzieloną przez jednostki tłumaczące. W trybie C99 extern inline
zawsze emituje funkcję, ale podobnie jak w C++, będzie współdzielona między jednostkami tłumaczącymi. W ten sposób tę samą funkcję można zdefiniować extern inline
w różnych jednostkach translacyjnych. Jest to zgodne z tradycyjnym zachowaniem kompilatorów Unix C dla wielu nie- zewnętrznych
definicji niezainicjowanych zmiennych globalnych.
Ograniczenia
Pobranie adresu funkcji wbudowanej
wymaga kodu, aby w każdym przypadku została wyemitowana niewbudowana kopia tej funkcji.
W C99 funkcja inline
lub extern inline
nie może uzyskiwać dostępu do statycznych
zmiennych globalnych ani definiować statycznych
zmiennych lokalnych innych niż const .
const static
zmienne lokalne mogą, ale nie muszą być różnymi obiektami w różnych jednostkach translacji, w zależności od tego, czy funkcja została wstawiona, czy też wykonano wywołanie. Tylko statyczne
definicje wbudowane mogą odwoływać się do identyfikatorów z wewnętrznym powiązaniem bez ograniczeń; będą to różne obiekty w każdej jednostce tłumaczeniowej. W C++ zarówno const
, jak i non- const
statyczne
lokalizacje są dozwolone i odnoszą się do tego samego obiektu we wszystkich jednostkach tłumaczeniowych.
gcc nie może wstawiać funkcji if
- są zmienne ,
- użyj
alokacji
- użyj obliczonego
goto
- użyj nielokalnego
goto
- używać funkcji zagnieżdżonych
- użyj
setjmp
- użyj
__builtin_longjmp
- użyj
__builtin_return
lub - użyj
__builtin_apply_args
W oparciu o specyfikacje firmy Microsoft w MSDN, MS Visual C++ nie może być wbudowane (nawet z __forceinline
), jeśli
- Funkcja lub jej obiekt wywołujący jest kompilowany z /Ob0 (opcja domyślna dla kompilacji debugowania).
- Funkcja i wywołujący używają różnych typów obsługi wyjątków (obsługa wyjątków C++ w jednym, obsługa wyjątków strukturalnych w drugim).
- Funkcja ma zmienną listę argumentów .
- Funkcja używa zestawu wbudowanego , chyba że została skompilowana z /Og, /Ox, /O1 lub /O2.
- Funkcja jest rekurencyjna i nie towarzyszy jej
#pragma inline_recursion(on)
. Dzięki pragma funkcje rekurencyjne są wstawiane do domyślnej głębokości 16 wywołań. Aby zmniejszyć głębokość wstawiania, użyjinline_depth
. - Funkcja jest wirtualna i nazywa się virtual. Bezpośrednie wywołania funkcji wirtualnych mogą być wstawiane.
- Program pobiera adres funkcji i wywołanie następuje poprzez wskaźnik do funkcji. Bezpośrednie wywołania funkcji, których adresy zostały pobrane, mogą być wstawiane.
- Funkcja jest również oznaczona nagim modyfikatorem
__declspec
.
Problemy
Poza ogólnymi problemami związanymi z rozwijaniem w wierszu (patrz Rozbudowa w wierszu § Wpływ na wydajność ), funkcje wbudowane
jako cecha języka mogą nie być tak cenne, jak się wydaje, z wielu powodów:
- Często kompilator jest w lepszej pozycji niż człowiek, aby zdecydować, czy dana funkcja powinna zostać wstawiona. Czasami kompilator może nie być w stanie wstawić tylu funkcji, ile wskazuje programista.
- Ważną kwestią, na którą należy zwrócić uwagę, jest to, że kod ( funkcji
wbudowanej
) zostaje ujawniony klientowi (funkcji wywołującej). - W miarę ewolucji funkcji mogą one stać się odpowiednie do wstawiania tam, gdzie nie były wcześniej, lub mogą już nie nadawać się do wstawiania tam, gdzie były wcześniej. Chociaż wstawianie lub usuwanie funkcji jest łatwiejsze niż konwersja do iz makr, nadal wymaga dodatkowej konserwacji, która zazwyczaj daje stosunkowo niewielkie korzyści.
- Funkcje wbudowane używane podczas rozprzestrzeniania się w natywnych systemach kompilacji opartych na C mogą wydłużyć czas kompilacji, ponieważ pośrednia reprezentacja ich treści jest kopiowana do każdej witryny wywołania.
- Specyfikacja
inline
w C99 wymaga dokładnie jednej zewnętrznej definicji funkcji, jeśli jest gdzieś używana. Jeśli taka definicja nie została dostarczona przez programistę, może to łatwo doprowadzić do błędów linkera. Może się to zdarzyć, gdy optymalizacja jest wyłączona, co zwykle zapobiega wstawianiu. Z drugiej strony dodanie definicji może spowodować, że kod będzie nieosiągalny, jeśli programista nie będzie tego ostrożnie unikał, umieszczając je w bibliotece do łączenia, stosując optymalizację czasu łącza lub statyczne wbudowanepliki
. - W C++ konieczne jest zdefiniowanie funkcji
wbudowanej
w każdym module (jednostce translacji), który jej używa, podczas gdy zwykła funkcja musi być zdefiniowana tylko w jednym module. W przeciwnym razie nie byłoby możliwe skompilowanie pojedynczego modułu niezależnie od wszystkich innych modułów. W zależności od kompilatora może to spowodować, że każdy odpowiedni plik obiektowy będzie zawierał kopię kodu funkcji dla każdego modułu z pewnym zastosowaniem, którego nie można wstawić. - W oprogramowaniu wbudowanym często pewne funkcje muszą być umieszczone w określonych sekcjach kodu przy użyciu specjalnych instrukcji kompilatora, takich jak instrukcje „pragma”. Czasami funkcja w jednym segmencie pamięci może wymagać wywołania funkcji w innym segmencie pamięci, a jeśli nastąpi wstawienie wywoływanej funkcji, kod wywoływanej funkcji może znaleźć się w segmencie, w którym nie powinien. Na przykład segmenty pamięci o wysokiej wydajności mogą mieć bardzo ograniczoną przestrzeń kodu, a jeśli funkcja należąca do takiej przestrzeni wywołuje inną dużą funkcję, która nie powinna znajdować się w sekcji o wysokiej wydajności, a wywoływana funkcja zostanie niewłaściwie wstawiona, to może to spowodować, że w segmencie pamięci o wysokiej wydajności zabraknie miejsca na kod. Z tego powodu czasami konieczne jest upewnienie się, że funkcje działają nie stać się wstawionym.
cytaty
- „Deklaracja funkcji […] ze specyfikatorem inline deklaruje funkcję inline. Specyfikator inline wskazuje implementacji, że podstawianie ciała funkcji w miejscu wywołania ma być preferowane w stosunku do zwykłego mechanizmu wywoływania funkcji. Implementacja nie jest wymagane wykonanie tej zamiany w wierszu w punkcie wywołania; jednak nawet jeśli to podstawienie w wierszu zostanie pominięte, nadal należy przestrzegać innych zasad dotyczących funkcji wbudowanych, określonych w pkt 7.1.2.
- — ISO/IEC 14882:2011, aktualna norma C++, sekcja 7.1.2
- „Funkcja zadeklarowana z inline specyfikator funkcji jest funkcją wbudowaną. [ . . . ] Uczynienie funkcji funkcją wbudowaną sugeruje, że wywołania funkcji powinny być tak szybkie, jak to możliwe. Zakres, w jakim takie sugestie są skuteczne, jest definiowany przez implementację ( wierszu wywołań w zakresie deklaracji wbudowanej) .
- w
- definicja wbudowana nie zapewnia zewnętrznej definicji funkcji i nie zabrania zewnętrznej definicji w innej jednostce tłumaczeniowej . Definicja wbudowana stanowi alternatywę dla definicji zewnętrznej, której tłumacz może użyć do zaimplementowania dowolnego wywołania funkcji w tej samej jednostce translacji. Nie określono, czy wywołanie funkcji wykorzystuje definicję wbudowaną, czy definicję zewnętrzną.”
- — ISO 9899:1999(E), norma C99, sekcja 6.7.4
Zobacz też
- JANA, DEBASISH (1 stycznia 2005). C++ I PARADYGMAT PROGRAMOWANIA OBIEKTOWEGO . PHI Learning Pvt. Ltd. ISBN 978-81-203-2871-6 .
- Sengupta, Probal (1 sierpnia 2004). Programowanie obiektowe: podstawy i zastosowania . PHI Learning Pvt. Ltd. ISBN 978-81-203-1258-6 .
- Svenk, Goran (2003). Programowanie obiektowe: używanie C++ dla inżynierii i technologii . Nauka Cengage'a. ISBN 0-7668-3894-3 .
- Balagurusamy (2013). Programowanie obiektowe w C++ . Edukacja Taty McGraw-Hill. ISBN 978-1-259-02993-6 .
- Kirch-Prinz, Ulla; Prinz, Piotr (2002). Kompletny przewodnik po programowaniu w C++ . Nauka Jonesa i Bartletta. ISBN 978-0-7637-1817-6 .
- Conger, David (2006). Tworzenie gier w C++: przewodnik krok po kroku . Nowi jeźdźcy. ISBN 978-0-7357-1434-2 .
- Skinner, MT (1992). Książka Zaawansowany C++ . Prasa silikonowa. ISBN 978-0-929306-10-0 .
- Miłość (1 września 2005). Rozwój jądra Linuksa . Edukacja Pearsona. ISBN 978-81-7758-910-8 .
- DEHURI, SATCHIDANANDA; JAGADEW, ALOK KUMAR; RATH, AMIYA KUMAR (8 maja 2007). PROGRAMOWANIE OBIEKTOWE W C++ . PHI Learning Pvt. Ltd. ISBN 978-81-203-3085-6 .
Linki zewnętrzne
- Funkcje wbudowane w GNU Compiler Collection (GCC)
- Podsumowanie semantyki „wbudowanej” w C i C++ , autorstwa współtwórcy LLVM , Davida Chisnalla