Inline funkce - Inline function

V programovacích jazycích C a C ++ je vložená funkce kvalifikovaná pomocí klíčového slova ; to slouží dvěma účelům. Za prvé slouží jako směrnice kompilátoru, která naznačuje (ale nevyžaduje), aby kompilátor nahradil tělo vložené funkce provedením vloženého rozšíření , tj. Vložením kódu funkce na adresu každého volání funkce, čímž se ušetří režie volání funkce. V tomto ohledu je analogický se specifikátorem třídy úložiště , který podobně poskytuje nápovědu k optimalizaci. Druhým účelem je změnit chování propojení; detaily jsou komplikované. To je nutné vzhledem k C/C ++ separátnímu modelu kompilace+ propojení, konkrétně proto, že definice (tělo) funkce musí být duplikována ve všech překladových jednotkách, kde je použita, aby bylo možné při kompilaci vkládat , což, pokud má funkce externí propojení , způsobí během propojení kolizi (narušuje jedinečnost vnějších symbolů). C a C ++ (a dialekty jako GNU C a Visual C ++) to řeší různými způsoby. inlineregister inline

Příklad

inlineFunkce může být napsán v C nebo C ++ takto:

inline void swap(int *m, int *n)
{
    int tmp = *m;
    *m = *n;
    *n = tmp;
}

Potom prohlášení, jako je následující:

swap(&x, &y);

lze přeložit do (pokud se kompilátor rozhodne provést vložení, které obvykle vyžaduje povolení optimalizace):

int tmp = x;
x = y;
y = tmp;

Když implementujete řadicí algoritmus, který dělá spoustu swapů, může to zvýšit rychlost provádění.

Standardní podpora

C ++ a C99 , ale ne jeho předchůdci K&R C a C89 , mají podporu inlinefunkcí, i když s jinou sémantikou. V obou případech inlinenevynucuje vložení; kompilátor se může svobodně rozhodnout, že funkci nebude vkládat vůbec, nebo jen v některých případech. Různé překladače se liší v tom, jak složitou funkci mohou spravovat vložené. Tradiční kompilátory C ++, jako jsou Microsoft Visual C ++ a GCC, podporují možnost, která umožňuje kompilátorům automaticky vkládat jakoukoli vhodnou funkci, dokonce i ty, které nejsou označeny jako inlinefunkce. Jednoduše vynechání inlineklíčového slova, aby kompilátor mohl provádět veškerá rozhodnutí o vložení, není možné, protože linker si pak bude stěžovat na duplicitní definice v různých překladových jednotkách. Důvodem je, že inlinenejen dává kompilátoru nápovědu, že by měla být funkce vložená, ale má také vliv na to, zda kompilátor vygeneruje kopii funkce s možností volání mimo řádek (viz třídy úložiště vložených funkcí ).

Nestandardní rozšíření

GNU C , jako součást dialektu gnu89, který nabízí, má podporu inlinejako rozšíření C89. Sémantika se však liší od C ++ a C99. armcc v režimu C90 také nabízí inlinejako nestandardní rozšíření se sémantikou odlišnou od gnu89 a C99.

Některé implementace poskytují prostředky k vynucení kompilátoru pro vložení funkce, obvykle pomocí specifikátorů deklarace specifických pro implementaci:

  • Microsoft Visual C ++: __forceinline
  • gcc nebo clang: __attribute__((always_inline))nebo __attribute__((__always_inline__)), který je užitečný, aby se předešlo konfliktu s uživatelem definovaným makrem pojmenovaným always_inline.

Nerozlišené použití může mít za následek větší kód (nafouklý spustitelný soubor), minimální nebo žádný nárůst výkonu a v některých případech dokonce ztrátu výkonu. Kompilátor navíc nemůže za všech okolností vkládat funkci, i když je vložení vynucené; v tomto případě generují varování gcc i Visual C ++.

Vynucení inliningu je užitečné, pokud

  • inline není kompilátorem respektován (ignorován analyzátorem nákladů a přínosů kompilátoru) a
  • inlining vede k nezbytnému zvýšení výkonu

Pro přenositelnost kódu lze použít následující směrnice preprocesoru:

#ifdef _MSC_VER
    #define forceinline __forceinline
#elif defined(__GNUC__)
    #define forceinline inline __attribute__((__always_inline__))
#elif defined(__CLANG__)
    #if __has_attribute(__always_inline__)
        #define forceinline inline __attribute__((__always_inline__))
    #else
        #define forceinline inline
    #endif
#else
    #define forceinline inline
#endif

Třídy úložiště vložených funkcí

static inlinemá stejné efekty ve všech C dialektech a C ++. V případě potřeby vydá místně viditelnou (out-of-line kopii) funkce.

Bez ohledu na třídu úložiště může kompilátor ignorovat inlinekvalifikátor a generovat volání funkce ve všech dialektech C a C ++.

Efekt třídy úložiště externpři použití nebo nepoužití na inlinefunkce se mezi dialekty C a C ++ liší.

C99

V C99 inlinebude definovaná funkce nikdy a definovaná funkce extern inlinebude vždy vysílat externě viditelnou funkci. Na rozdíl od C ++ neexistuje žádný způsob, jak požádat o externě viditelnou funkci sdílenou mezi překladovými jednotkami, aby byla vydávána pouze v případě potřeby.

Pokud inlinejsou deklarace smíchány s extern inlinedeklaracemi nebo s nekvalifikovanými deklaracemi (tj. Bez inlinekvalifikátoru nebo třídy úložiště), překladová jednotka musí obsahovat definici (bez ohledu na to, zda je nekvalifikovaná inline, nebo extern inline) a bude pro ni vydána externě viditelná funkce.

Definovaná funkce inlinevyžaduje přesně jednu funkci s tímto názvem někde jinde v programu, která je definována extern inlinenebo bez kvalifikátoru. Pokud je v celém programu k dispozici více než jedna taková definice, bude linker stěžovat na duplicitní symboly. Pokud však chybí, linker si nemusí nutně stěžovat, protože pokud by bylo možné naznačit všechna použití, není to nutné. Může si ale stěžovat, protože kompilátor může inlinekvalifikátor vždy ignorovat a místo toho generovat volání funkce, jak se obvykle stává, pokud je kód kompilován bez optimalizace. (To může být požadované chování, pokud se předpokládá, že je tato funkce všemi způsoby podtržena, a pokud tomu tak není, měla by být vygenerována chyba.) Pohodlným způsobem je definovat inlinefunkce v hlavičkových souborech a vytvořit jeden soubor .c na funkci, obsahující extern inlinedeklaraci a včetně příslušného hlavičkového souboru s definicí. Nezáleží na tom, zda je deklarace před nebo za zahrnutím.

Aby se zabránilo přidání nedosažitelného kódu do konečného spustitelného souboru, pokud by byla vložena všechna použití funkce, doporučujeme umístit objektové soubory všech takových souborů .c s jedinou extern inlinefunkcí do souboru statické knihovny , typicky s ar rcs, pak propojit proti že knihovna namísto souborů jednotlivých objektů. To způsobí propojení pouze těch souborů objektů, které jsou skutečně potřebné, na rozdíl od přímého propojení souborů objektů, což způsobí, že budou vždy zahrnuty do spustitelného souboru. Soubor knihovny však musí být zadán po všech ostatních souborech objektů na příkazovém řádku linkeru, protože linker nebude zvažovat volání ze souborů objektů zadaných po souboru knihovny do funkcí. Hovory z inlinefunkcí do jiných inlinefunkcí budou automaticky vyřešeny linkerem ( smožnost v ar rcszajišťuje toto).

Alternativním řešením je použít optimalizaci času propojení místo knihovny. gcc poskytuje příznak -Wl,--gc-sectionspro vynechání sekcí, ve kterých jsou všechny funkce nepoužité. To bude případ objektových souborů obsahujících kód jedné nepoužívané extern inlinefunkce. Odebere však také všechny ostatní nepoužité sekce ze všech ostatních souborů objektů, nejen z těch, které souvisejí s nepoužívanými extern inlinefunkcemi. (Může být žádoucí propojit funkce do spustitelného souboru, které mají být vyvolány programátorem z debuggeru, nikoli samotným programem, např. Pro zkoumání vnitřního stavu programu.) S tímto přístupem je také možné použít jeden soubor .c se všemi extern inlinefunkcemi místo jednoho souboru .c na funkci. Poté musí být soubor zkompilován pomocí -fdata-sections -ffunction-sections. Stránka manuálu gcc na to však upozorňuje slovy: „Tyto možnosti používejte pouze tehdy, jsou -li z toho významné výhody“.

Někteří doporučují zcela odlišný přístup, kterým je definovat funkce jako static inlinemísto inlinev hlavičkových souborech. Poté nebude vygenerován žádný nedostupný kód. Tento přístup má však v opačném případě nevýhodu: Duplicitní kód bude vygenerován, pokud funkci nelze vložit do více než jedné překladové jednotky. Kód emitované funkce nelze sdílet mezi překladovými jednotkami, protože musí mít různé adresy. To je další nevýhoda; převzetí adresy takové funkce definované static inlinev souboru záhlaví přinese různé hodnoty v různých překladových jednotkách. static inlineFunkce by proto měly být použity pouze tehdy, pokud jsou použity pouze v jedné překladové jednotce, což znamená, že by měly jít pouze do příslušného souboru .c, nikoli do souboru záhlaví.

gnu89

gnu89 sémantika inlinea extern inlinejsou v podstatě pravým opakem těch v C99, s výjimkou, že gnu89 umožňuje předefinování extern inlinefunkce jako nekvalifikované funkce, zatímco C99 inlinene. Gnu89 extern inlinebez redefinice je tedy jako C99 inlinea gnu89 inlineje jako C99 extern inline; jinými slovy, v gnu89 inlinebude definovaná funkce vždy a definovaná funkce extern inlinenikdy nevydávat externě viditelnou funkci. Důvodem je to, že odpovídá proměnným, pro které nebude úložiště nikdy rezervováno, pokud je definováno jako externa vždy, pokud je definováno bez. Důvodem pro C99 je naopak to, že by bylo úžasné, kdyby použití inlinemělo vedlejší účinek-vždy vyzařovat neinlinovanou verzi funkce-což je v rozporu s tím, co naznačuje její název.

Poznámky pro C99 o potřebě poskytnout přesně jednu externě viditelnou instanci funkce pro vložené funkce a o výsledném problému s nedosažitelným kódem platí obdobně i pro gnu89.

gcc až do verze 4.2 včetně používala inlinesémantiku gnu89, i když -std=c99byla výslovně uvedena. Ve verzi 5 gcc přešlo z gnu89 na gnu11 dialekt, což inlineve výchozím nastavení účinně umožňuje sémantiku C99 . Chcete -li místo toho použít sémantiku gnu89, musí být povolena explicitně, buď s -std=gnu89nebo, aby ovlivnila pouze vložení -fgnu89-inline, nebo přidáním gnu_inlineatributu ke všem inlinedeklaracím. Pro zajištění C99 sémantiku, a to buď -std=c99, -std=c11, -std=gnu99nebo -std=gnu11(bez -fgnu89-inlinelze použít).

C ++

V C ++ inlinebude definovaná funkce v případě potřeby vysílat funkci sdílenou mezi překladovými jednotkami, obvykle vložením do společné části souboru objektu, pro který je potřeba. Funkce musí mít všude stejnou definici, vždy s inlinekvalifikátorem. V C ++ extern inlineje stejné jako inline. Důvodem přístupu C ++ je, že je to pro programátora nejpohodlnější způsob, protože není nutné provádět žádná zvláštní opatření pro eliminaci nedosažitelného kódu a stejně jako u běžných funkcí nezáleží na tom, zda externje zadán či nikoli.

inlineKvalifikátor se automaticky přidá do funkce definované v rámci definice třídy.

armcc

armcc v režimu C90 poskytuje extern inlinea inlinesémantiku, která jsou stejná jako v C ++: Takové definice budou v případě potřeby vysílat funkci sdílenou mezi překladovými jednotkami. V režimu C99 extern inlinevždy vydává funkci, ale stejně jako v C ++ bude sdílena mezi překladovými jednotkami. Stejnou funkci lze tedy definovat extern inlinev různých translačních jednotkách. To odpovídá tradičnímu chování kompilátorů Unixu C pro více externnedefinic neinicializovaných globálních proměnných.

Omezení

Převzetí adresy inlinefunkce vyžaduje v každém případě kód pro neinlinovanou kopii této funkce.

V C99 funkce an inlinenebo extern inlinenesmí přistupovat ke staticglobálním proměnným ani definovat nelokální const staticproměnné. const staticmístní proměnné mohou, ale nemusí být různé objekty v různých překladových jednotkách, v závislosti na tom, zda byla funkce vložena nebo zda bylo provedeno volání. Pouze static inlinedefinice mohou odkazovat na identifikátory s interním propojením bez omezení; budou to různé objekty v každé překladové jednotce. V C ++ jsou povoleni oba consti ne- const staticmístní obyvatelé a odkazují na stejný objekt ve všech překladových jednotkách.

gcc nemůže vkládat funkce, pokud

  1. jsou variadičtí ,
  2. použití alloca
  3. použít vypočítaný goto
  4. použít nelokální goto
  5. používat vnořené funkce
  6. použití setjmp
  7. použití __builtin_longjmp
  8. použít __builtin_return, popř
  9. použití __builtin_apply_args

Na základě specifikací Microsoft na MSDN nemůže MS Visual C ++ vkládat (ani s __forceinline), pokud

  1. Funkce nebo její volající je zkompilován s /Ob0 (výchozí volba pro sestavení ladění).
  2. Funkce a volající používají různé typy zpracování výjimek ( zpracování výjimek C ++ v jednom, zpracování strukturovaných výjimek v druhém).
  3. Funkce má seznam proměnných argumentů .
  4. Funkce používá vložené sestavení , pokud není kompilováno s /Og, /Ox, /O1 nebo /O2.
  5. Funkce je rekurzivní a není doprovázena #pragma inline_recursion(on). S pragma jsou rekurzivní funkce vloženy do výchozí hloubky 16 hovorů. Chcete -li snížit hloubku vložení, použijte inline_depthpragma.
  6. Funkce je virtuální a nazývá se virtuálně. Přímá volání virtuálních funkcí mohou být vložena.
  7. Program převezme adresu funkce a volání se uskuteční přes ukazatel na funkci. Přímá volání funkcí, jejichž adresa byla přijata, mohou být podtržena.
  8. Funkce je také označena nahým __declspecmodifikátorem.

Problémy

Kromě problémů s inline expanzí obecně (viz Inline expanze § Vliv na výkon ) inlinenemusí být funkce jako jazyková funkce tak cenné, jak se zdá, a to z řady důvodů:

  • Kompilátor je často v lepší pozici než člověk, aby se rozhodl, zda má být konkrétní funkce vložena. Někdy kompilátor nemusí být schopen vložit tolik funkcí, jak programátor uvádí.
  • Důležitým bodem, který je třeba poznamenat, je, že kód ( inlinefunkce) se vystaví svému klientovi (volající funkce).
  • Jak se funkce vyvíjejí, mohou se stát vhodnými pro inlining tam, kde dříve nebyly, nebo již nejsou vhodné pro inlining tam, kde byly dříve. I když je vložení nebo zrušení vložení funkce jednodušší než převod do az makra, stále vyžaduje zvláštní údržbu, která obvykle přináší relativně malý užitek.
  • Vložené funkce používané při šíření v nativních kompilačních systémech na bázi C mohou prodloužit dobu kompilace, protože do každého místa volání se zkopíruje přechodná reprezentace jejich těl.
  • Specifikace inlinev C99 vyžaduje přesně jednu externí definici funkce, pokud je někde použita. Pokud takovou definici programátor neposkytl, může to snadno vést k chybám linkeru. To se může stát při vypnuté optimalizaci, která obvykle brání vložení. Přidání definic na druhé straně může způsobit nedostupnost kódu, pokud se mu programátor pečlivě nevyhne, vložením do knihovny pro propojení, pomocí optimalizace času propojení, popř static inline.
  • V C ++ je nutné definovat inlinefunkci v každém modulu (překladové jednotce), který ji používá, zatímco běžná funkce musí být definována pouze v jednom modulu. Jinak by nebylo možné sestavit jeden modul nezávisle na všech ostatních modulech. V závislosti na kompilátoru to může způsobit, že každý příslušný objektový soubor bude obsahovat kopii kódu funkce pro každý modul s určitým použitím, které nelze vložit.
  • V integrovaném softwaru je často nutné určité funkce umístit do určitých sekcí kódu pomocí speciálních instrukcí kompilátoru, jako jsou příkazy „pragma“. Někdy může funkce v jednom paměťovém segmentu vyžadovat volání funkce v jiném paměťovém segmentu, a pokud dojde k vložení volané funkce, pak kód volané funkce může skončit v segmentu, kde by neměl být. Například vysoce výkonné paměťové segmenty mohou být v kódovém prostoru velmi omezené, a pokud funkce patřící do takového prostoru volá jinou velkou funkci, která není určena k tomu, aby byla v sekci s vysokým výkonem, a volaná funkce se nevhodně vloží, pak to může způsobit, že segmentu vysoce výkonné paměti dojde místo v kódu. Z tohoto důvodu je někdy nutné zajistit, aby funkce nebyly vloženy.

Citáty

„S deklaraci funkce [...] Inline specifikátor deklaruje vložené funkce. Čím inline specifikátor ukazuje na realizaci, že inline substituce funkce těla v místě volání se upřednostňuje, aby obvyklým mechanismem volání funkce. Implementace není vyžadováno k provedení této vložené náhrady v místě volání; nicméně i když je tato vložená náhrada vynechána, ostatní pravidla pro vložené funkce definované v 7.1.2 budou stále respektována. “
- ISO/IEC 14882: 2011, aktuální norma C ++, část 7.1.2
„Funkce deklarovaná se specifikátorem vložené funkce je vložená funkce. [. poznámka pod čarou: Například implementace nemusí nikdy provádět vložené nahrazování nebo může provádět pouze vložené nahrazování volání v rozsahu vložené deklarace. )
„[...] Definice Inline neposkytuje externí definici pro funkci, a nezakazuje externí definice v jiné jednotce překlad . Definice Inline poskytuje alternativu k externímu definice, která překládá se může použít pro realizaci kteréhokoliv volání funkce ve stejné překladové jednotce. Není určeno, zda volání funkce používá vloženou definici nebo externí definici. "
- ISO 9899: 1999 (E), norma C99, oddíl 6.7.4

Viz také

Reference

externí odkazy