Funzione in linea - Inline function
Nei linguaggi di programmazione C e C++ , una funzione in linea è qualificata con la parola chiave ; questo serve a due scopi. In primo luogo, funge da direttiva del compilatore che suggerisce (ma non richiede) che il compilatore sostituisca il corpo della funzione inline eseguendo l'espansione inline , ovvero inserendo il codice della funzione all'indirizzo di ciascuna chiamata di funzione, risparmiando così il sovraccarico di una chiamata di funzione. A questo proposito è analogo all'identificatore della classe di archiviazione , che fornisce allo stesso modo un suggerimento per l'ottimizzazione. Il secondo scopo di è cambiare il comportamento del collegamento; i dettagli di questo sono complicati. Ciò è necessario a causa del modello di compilazione + collegamento separato C/C++, in particolare perché la definizione (corpo) della funzione deve essere duplicata in tutte le unità di traduzione in cui viene utilizzata, per consentire l'inlining durante la compilazione , che, se la funzione ha esterni linkage , provoca una collisione durante il collegamento (viola l'unicità dei simboli esterni). C e C++ (e dialetti come GNU C e Visual C++) risolvono questo problema in modi diversi.
inlineregister inline
Esempio
Una inlinefunzione può essere scritta in C o C++ in questo modo:
inline void swap(int *m, int *n)
{
int tmp = *m;
*m = *n;
*n = tmp;
}
Quindi, una dichiarazione come la seguente:
swap(&x, &y);
può essere tradotto in (se il compilatore decide di eseguire l'inlining, che in genere richiede l'attivazione dell'ottimizzazione):
int tmp = x;
x = y;
y = tmp;
Quando si implementa un algoritmo di ordinamento che esegue molti scambi, ciò può aumentare la velocità di esecuzione.
Supporto standard
C++ e C99 , ma non i suoi predecessori K&R C e C89 , supportano le inlinefunzioni, sebbene con semantica diversa. In entrambi i casi, inlinenon forza l'inlining; il compilatore è libero di scegliere di non inlineare affatto la funzione, o solo in alcuni casi. Diversi compilatori variano in base alla complessità di una funzione che possono gestire in linea. I compilatori C++ tradizionali come Microsoft Visual C++ e GCC supportano un'opzione che consente ai compilatori di incorporare automaticamente qualsiasi funzione adatta, anche quelle non contrassegnate come inlinefunzioni. Tuttavia, inlinenon è possibile semplicemente omettere la parola chiave per consentire al compilatore di prendere tutte le decisioni in linea, poiché il linker si lamenterà quindi di definizioni duplicate in diverse unità di traduzione. Questo perché inlinenon solo dà al compilatore un suggerimento che la funzione dovrebbe essere inline, ma ha anche un effetto sul fatto che il compilatore genererà una copia fuori linea chiamabile della funzione (vedi classi di archiviazione delle funzioni inline ).
Estensioni non standard
GNU C , come parte del dialetto gnu89 che offre, supporta inlinecome estensione a C89. Tuttavia, la semantica differisce da quella di C++ e C99. armcc in modalità C90 offre anche inlinecome estensione non standard, con semantica diversa da gnu89 e C99.
Alcune implementazioni forniscono un mezzo con cui forzare il compilatore a incorporare una funzione, di solito mediante identificatori di dichiarazione specifici dell'implementazione:
- Microsoft Visual C++:
__forceinline - gcc o clang:
__attribute__((always_inline))o__attribute__((__always_inline__)), quest'ultimo dei quali è utile per evitare un conflitto con una macro definita dall'utente denominataalways_inline.
L'uso indiscriminato di ciò può comportare un codice più grande (file eseguibile gonfio), un guadagno minimo o nullo delle prestazioni e, in alcuni casi, anche una perdita di prestazioni. Inoltre, il compilatore non può inlineare la funzione in tutte le circostanze, anche quando l'inline è forzato; in questo caso sia gcc che Visual C++ generano avvisi.
Forzare l'allineamento è utile se
-
inlinenon è rispettato dal compilatore (ignorato dall'analizzatore costi/benefici del compilatore), e - l'inlining si traduce in un necessario incremento delle prestazioni
Per la portabilità del codice, possono essere utilizzate le seguenti direttive per il preprocessore:
#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
Classi di archiviazione delle funzioni in linea
static inlineha gli stessi effetti in tutti i dialetti C e C++. Se necessario, emetterà una funzione visibile localmente (copia fuori linea della).
Indipendentemente dalla classe di archiviazione, il compilatore può ignorare il inlinequalificatore e generare una chiamata di funzione in tutti i dialetti C e C++.
L'effetto della classe di archiviazione externquando applicata o non applicata alle inlinefunzioni differisce tra i dialetti C e C++.
C99
In C99, una funzione definita inlinenon emetterà mai e una funzione definita extern inlineemetterà sempre una funzione visibile esternamente. A differenza del C++, non c'è modo di chiedere che una funzione visibile esternamente condivisa tra le unità di traduzione venga emessa solo se necessario.
Se le inlinedichiarazioni sono mescolate con extern inlinedichiarazioni o con dichiarazioni non qualificate (cioè, senza inlinequalificatore o classe di archiviazione), l'unità di traduzione deve contenere una definizione (non importa se non qualificata inline, o extern inline) e verrà emessa una funzione visibile esternamente.
Una funzione definita inlinerichiede esattamente una funzione con quel nome da qualche altra parte nel programma che sia definita extern inlineo senza qualificatore. Se più di una di queste definizioni è fornita nell'intero programma, il linker si lamenterà di simboli duplicati. Se, invece, manca, il linker non si lamenta necessariamente, perché, se tutti gli usi possono essere inline, non è necessario. Ma potrebbe lamentarsi, dal momento che il compilatore può sempre ignorare il inlinequalificatore e generare invece chiamate alla funzione, come accade tipicamente se il codice viene compilato senza ottimizzazione. (Questo potrebbe essere il comportamento desiderato, se si suppone che la funzione sia inline ovunque con tutti i mezzi, e un errore dovrebbe essere generato se non lo è.) Un modo conveniente è definire le inlinefunzioni nei file di intestazione e creare un file .c per funzione, contenente una extern inlinedichiarazione per essa e includendo il rispettivo file di intestazione con la definizione. Non importa se la dichiarazione è prima o dopo l'inclusione.
Per impedire l'aggiunta di codice irraggiungibile all'eseguibile finale se tutti gli usi di una funzione erano inline, si consiglia di inserire i file oggetto di tutti questi file .c con una singola extern inlinefunzione in un file di libreria statica , in genere con ar rcs, quindi collegarsi a quella libreria invece dei singoli file oggetto. Ciò fa sì che solo quei file oggetto vengano collegati effettivamente necessari, a differenza del collegamento diretto dei file oggetto, che fa sì che siano sempre inclusi nell'eseguibile. Tuttavia, il file di libreria deve essere specificato dopo tutti gli altri file oggetto sulla riga di comando del linker, poiché le chiamate dai file oggetto specificati dopo il file di libreria alle funzioni non verranno considerate dal linker. Le chiamate da inlinefunzioni ad altre inlinefunzioni verranno risolte automaticamente dal linker (l' sopzione in lo ar rcsgarantisce).
Una soluzione alternativa consiste nell'utilizzare l'ottimizzazione del tempo di collegamento invece di una libreria. gcc fornisce il flag -Wl,--gc-sectionsper omettere le sezioni in cui tutte le funzioni non sono utilizzate. Questo sarà il caso dei file oggetto contenenti il codice di una singola extern inlinefunzione inutilizzata . Tuttavia, rimuove anche tutte le altre sezioni inutilizzate da tutti gli altri file oggetto, non solo quelle relative alle extern inlinefunzioni non utilizzate . (Può essere opportuno collegare funzioni nell'eseguibile che devono essere chiamate dal programmatore dal debugger piuttosto che dal programma stesso, ad esempio, per esaminare lo stato interno del programma.) Con questo approccio, è anche possibile per utilizzare un singolo file .c con tutte le extern inlinefunzioni invece di un file .c per funzione. Quindi il file deve essere compilato con -fdata-sections -ffunction-sections. Tuttavia, la pagina del manuale di gcc avverte di questo, dicendo "Usa queste opzioni solo quando ci sono vantaggi significativi dal farlo".
Alcuni raccomandano un approccio completamente diverso, che consiste nel definire le funzioni come static inlineinvece che inlinenei file di intestazione. Quindi, non verrà generato alcun codice irraggiungibile. Tuttavia, questo approccio presenta uno svantaggio nel caso opposto: verrà generato codice duplicato se la funzione non può essere inline in più di un'unità di traduzione. Il codice funzione emesso non può essere condiviso tra unità di traduzione perché deve avere indirizzi diversi. Questo è un altro svantaggio; prendere l'indirizzo di una tale funzione definita come static inlinein un file di intestazione produrrà valori diversi in diverse unità di traduzione. Pertanto, le static inlinefunzioni dovrebbero essere utilizzate solo se sono utilizzate in una sola unità di traduzione, il che significa che dovrebbero andare solo nel rispettivo file .c, non in un file di intestazione.
gnu89
gnu89 semantica di inlinee extern inlinesono essenzialmente l'esatto opposto di quelli in C99, con l'eccezione che gnu89 consente la ridefinizione di una extern inlinefunzione come funzione non qualificata, mentre C99 inlineno. Quindi, gnu89 extern inlinesenza ridefinizione è come C99 inlinee gnu89 inlineè come C99 extern inline; in altre parole, in gnu89, una funzione definita inlineemetterà sempre una funzione definita extern inlinee non emetterà mai una funzione visibile esternamente. La logica di ciò è che corrisponde a variabili, per le quali l'archiviazione non sarà mai riservata se definita come externe sempre se definita senza. La logica per C99, al contrario, è che sarebbe sorprendente se l'uso inlineavesse un effetto collaterale - emettere sempre una versione non in linea della funzione - che è contrario a quanto suggerisce il nome.
Le osservazioni per C99 sulla necessità di fornire esattamente un'istanza di funzione visibile esternamente per le funzioni inline e sul problema risultante con codice irraggiungibile si applicano mutatis mutandis anche a gnu89.
gcc fino alla versione 4.2 inclusa usava la inlinesemantica gnu89 anche quando -std=c99era esplicitamente specificato. Con la versione 5, gcc è passato dal dialetto gnu89 al dialetto gnu11, abilitando effettivamente la inlinesemantica C99 per impostazione predefinita. Per usare invece la semantica gnu89, devono essere abilitati esplicitamente, con -std=gnu89o, per influenzare solo l'inlining -fgnu89-inline, o aggiungendo l' gnu_inlineattributo a tutte le inlinedichiarazioni. Per garantire la semantica C99, è possibile utilizzare -std=c99, -std=c11, -std=gnu99o -std=gnu11(senza -fgnu89-inline).
C++
In C++, una funzione definita inline, se richiesto, emetterà una funzione condivisa tra le unità di traduzione, tipicamente inserendola nella sezione comune del file oggetto per cui è necessaria. La funzione deve avere la stessa definizione ovunque, sempre con il inlinequalificatore. In C++, extern inlineè lo stesso di inline. La logica dell'approccio C++ è che è il modo più conveniente per il programmatore, poiché non devono essere prese precauzioni speciali per l'eliminazione del codice irraggiungibile e, come per le funzioni ordinarie, non fa differenza se externè specificato o meno.
Il inlinequalificatore viene aggiunto automaticamente a una funzione definita come parte di una definizione di classe.
armcc
armcc in modalità C90 fornisce extern inlinee inlinesemantiche lo stesso come in C ++: Tali definizioni emetteranno una funzione condivisa tra unità di traduzione se necessario. In modalità C99, extern inlineemette sempre una funzione, ma come in C++, sarà condivisa tra le unità di traduzione. Pertanto, la stessa funzione può essere definita extern inlinein diverse unità di traduzione. Ciò corrisponde al comportamento tradizionale dei compilatori C Unix per più non externdefinizioni di variabili globali non inizializzate.
Restrizioni
L' inlineacquisizione dell'indirizzo di una funzione richiede in ogni caso il codice per l'emissione di una copia non in linea di tale funzione.
In C99, una funzione inlineo extern inlinenon deve accedere a staticvariabili globali o definire const staticvariabili non locali. const staticle variabili locali possono o meno essere oggetti diversi in unità di traduzione diverse, a seconda che la funzione sia inline o che sia stata effettuata una chiamata. Solo le static inlinedefinizioni possono fare riferimento a identificatori con collegamento interno senza restrizioni; questi saranno oggetti diversi in ciascuna unità di traduzione. In C++, sono consentiti sia i constnon const staticlocali che i non locali e fanno riferimento allo stesso oggetto in tutte le unità di traduzione.
gcc non può funzioni in linea se
- sono variadici ,
- utilizzo
alloca - usa calcolato
goto - usa non locale
goto - usa funzioni annidate
- utilizzo
setjmp - utilizzo
__builtin_longjmp - usa
__builtin_return, o - utilizzo
__builtin_apply_args
In base alle specifiche Microsoft su MSDN, MS Visual C++ non può essere integrato (nemmeno con __forceinline), se
- La funzione o il suo chiamante viene compilato con /Ob0 (l'opzione predefinita per le build di debug).
- La funzione e il chiamante utilizzano diversi tipi di gestione delle eccezioni ( gestione delle eccezioni C++ in una, gestione delle eccezioni strutturata nell'altra).
- La funzione ha un elenco di argomenti variabili .
- La funzione usa l'assembly inline , a meno che non sia compilata con /Og, /Ox, /O1 o /O2.
- La funzione è ricorsiva e non accompagnata da
#pragma inline_recursion(on). Con il pragma, le funzioni ricorsive sono inline a una profondità predefinita di 16 chiamate. Per ridurre la profondità di inlining, utilizzareinline_depthpragma. - La funzione è virtuale e viene chiamata virtualmente. Le chiamate dirette alle funzioni virtuali possono essere inline.
- Il programma prende l'indirizzo della funzione e la chiamata viene effettuata tramite il puntatore alla funzione. Le chiamate dirette a funzioni il cui indirizzo è stato preso possono essere inline.
- La funzione è anche contrassegnata con il
__declspecmodificatore nudo .
I problemi
Oltre ai problemi con l'espansione in linea in generale (vedi Espansione in linea § Effetto sulle prestazioni ), le inlinefunzioni come caratteristica del linguaggio potrebbero non essere così preziose come sembrano, per una serie di motivi:
- Spesso, un compilatore è in una posizione migliore di un essere umano per decidere se una particolare funzione debba essere inline. A volte il compilatore potrebbe non essere in grado di incorporare tutte le funzioni indicate dal programmatore.
- Un punto importante da notare è che il codice (della
inlinefunzione) viene esposto al suo client (la funzione chiamante). - Man mano che le funzioni si evolvono, possono diventare adatte per l'inlining dove non erano prima o non più adatte per l'inlining dove erano prima. Sebbene l'inserimento o il disinserimento in linea di una funzione sia più semplice rispetto alla conversione da e verso macro, richiede comunque una manutenzione aggiuntiva che in genere produce benefici relativamente scarsi.
- Le funzioni in linea utilizzate in proliferazione nei sistemi di compilazione basati su C nativi possono aumentare i tempi di compilazione, poiché la rappresentazione intermedia dei loro corpi viene copiata in ogni sito di chiamata.
- La specifica di
inlinein C99 richiede esattamente una definizione esterna della funzione, se viene utilizzata da qualche parte. Se tale definizione non è stata fornita dal programmatore, ciò può facilmente portare a errori del linker. Ciò può accadere con l'ottimizzazione disattivata, che in genere impedisce l'inlining. L'aggiunta delle definizioni, d'altra parte, può causare codice irraggiungibile se il programmatore non lo evita accuratamente, inserendole in una libreria per il collegamento, utilizzando l'ottimizzazione del tempo di collegamento ostatic inline. - In C++ è necessario definire una
inlinefunzione in ogni modulo (unità di traduzione) che la utilizza, mentre una funzione ordinaria deve essere definita in un solo modulo. Altrimenti non sarebbe possibile compilare un singolo modulo indipendentemente da tutti gli altri moduli. A seconda del compilatore, questo può far sì che ogni rispettivo file oggetto contenga una copia del codice della funzione, per ogni modulo con un uso che non può essere inline. - Nel software incorporato , spesso alcune funzioni devono essere inserite in determinate sezioni di codice mediante l'uso di istruzioni speciali del compilatore come le istruzioni "pragma". A volte, una funzione in un segmento di memoria potrebbe dover chiamare una funzione in un altro segmento di memoria e, se si verifica l'inline della funzione chiamata, il codice della funzione chiamata potrebbe finire in un segmento dove non dovrebbe essere. Ad esempio, i segmenti di memoria ad alte prestazioni possono essere molto limitati nello spazio del codice e se una funzione appartenente a tale spazio chiama un'altra funzione di grandi dimensioni che non dovrebbe essere nella sezione ad alte prestazioni e la funzione chiamata viene inline in modo inappropriato, allora ciò potrebbe causare l'esaurimento dello spazio del codice nel segmento di memoria ad alte prestazioni. Per questo motivo, a volte è necessario assicurarsi che le funzioni non diventino inline.
Citazioni
- "Una dichiarazione di funzione [ . . . ] con un identificatore in linea dichiara una funzione in linea. L' identificatore in linea indica all'implementazione che la sostituzione in linea del corpo della funzione al punto di chiamata deve essere preferita al solito meccanismo di chiamata di funzione. Un'implementazione non è tenuto ad eseguire questa sostituzione in linea al punto di chiamata; tuttavia, anche se questa sostituzione in linea viene omessa, devono comunque essere rispettate le altre regole per le funzioni in linea definite al punto 7.1.2."
- — ISO/IEC 14882:2011, l'attuale standard C++, sezione 7.1.2
- "Una funzione dichiarata con un identificatore di funzione in linea è una funzione in linea. [ . . . ] Rendere una funzione una funzione in linea suggerisce che le chiamate alla funzione siano il più veloci possibile. La misura in cui tali suggerimenti sono efficaci è definita dall'implementazione ( nota a piè di pagina: ad esempio, un'implementazione potrebbe non eseguire mai la sostituzione in linea o potrebbe eseguire solo sostituzioni in linea alle chiamate nell'ambito di una dichiarazione in linea. )
- "[ . . . ] Una definizione in linea non fornisce una definizione esterna per la funzione e non vieta una definizione esterna in un'altra unità di traduzione . Una definizione in linea fornisce un'alternativa a una definizione esterna, che un traduttore può utilizzare per implementare qualsiasi chiamata alla funzione nella stessa unità di traduzione. Non è specificato se una chiamata alla funzione utilizza la definizione in linea o la definizione esterna."
- — ISO 9899:1999(E), lo standard C99, sezione 6.7.4
Guarda anche
Riferimenti
- JANA, DEBASISH (1 gennaio 2005). C++ E PARADIGMA DI PROGRAMMAZIONE ORIENTATA A OGGETTI . PHI Learning Pvt. Ltd. ISBN 978-81-203-2871-6.
- Sengupta, Probal (1 agosto 2004). Programmazione orientata agli oggetti: fondamenti e applicazioni . PHI Learning Pvt. Ltd. ISBN 978-81-203-1258-6.
- Svenk, Goran (2003). Programmazione orientata agli oggetti: utilizzo di C++ per l'ingegneria e la tecnologia . Impegnarsi nell'apprendimento. ISBN 0-7668-3894-3.
- Balagurusamy (2013). Programmazione orientata agli oggetti con C++ . Tata McGraw-Hill Education. ISBN 978-1-259-02993-6.
- Kirch-Prinz, Ulla; Prinz, Peter (2002). Una guida completa alla programmazione in C++ . Jones & Bartlett Apprendimento. ISBN 978-0-7637-1817-6.
- Conger, David (2006). Creazione di giochi in C++: una guida passo passo . Nuovi cavalieri. ISBN 978-0-7357-1434-2.
- Skinner, MT (1992). Il libro C++ avanzato . Silicio Press. ISBN 978-0-929306-10-0.
- Amore (1 settembre 2005). Sviluppo del kernel Linux . Pearson Education. ISBN 978-81-7758-910-8.
- DEHURI, SATCHIDANANDA; JAGADEV, ALOK KUMAR; RATH, AMIYA KUMAR (8 maggio 2007). PROGRAMMAZIONE ORIENTATA AGLI OGGETTI IN C++ . PHI Learning Pvt. Ltd. ISBN 978-81-203-3085-6.
link esterno
- Funzioni in linea con la GNU Compiler Collection (GCC)
- Riepilogo della semantica "in linea" in C e C++ , del collaboratore di LLVM David Chisnall