quali sono / sono gli scopi di inline?

Ho avuto una discussione con Johannes Schaub riguardo alla parola chiave in inline . Il codice c’era questo:

 namespace ... { static void someFunction() { MYCLASS::GetInstance()->someFunction(); } }; 

Ha affermato che:

Mettendo questo come una funzione in linea si può salvare la dimensione del codice nell’eseguibile

Ma secondo i miei risultati qui e qui non sarebbe necessario, dal momento che:

  • [In linea] si verifica solo se l’analisi costi / benefici del compilatore mostra che è redditizio
  • I compilatori C ++ mainstream come Microsoft Visual C ++ e GCC supportano un’opzione che consente ai compilatori di integrare automaticamente qualsiasi funzione appropriata, anche quelle non contrassegnate come funzioni inline.

Johannes afferma tuttavia che ci sono altri vantaggi esplicitamente specificandoli. Purtroppo non li capisco. Ad esempio, ha dichiarato che E “in linea” consente di definire la funzione più volte nel programma. , che sto facendo fatica a capire (e trovare riferimenti a).

Così

  1. È solo una raccomandazione per il compilatore?
  2. Dovrebbe essere esplicitamente dichiarato quando si ha una piccola funzione (suppongo 1-4 istruzioni?)
  3. Quali altri vantaggi ci sono con la scrittura in inline ?
  4. è necessario dichiarare in inline per ridurre la dimensione del file eseguibile, anche se il compilatore (secondo wikipedia [lo so, riferimento errato]) dovrebbe trovare tali funzioni da solo?

C’è qualcos’altro che mi manca?

È solo una raccomandazione per il compilatore?

Sì.

7.1.2 Specificatori di funzione

2 Una dichiarazione di funzione (8.3.5, 9.3, 11.4) con un identificatore in linea dichiara una funzione in linea. Lo specificatore in linea indica all’implementazione che la sostituzione in linea del corpo della funzione nel punto di chiamata deve essere preferita al solito meccanismo di chiamata di funzione. Non è richiesta un’implementazione per eseguire questa sostituzione in linea al punto di chiamata; tuttavia, anche se questa sostituzione inline viene omessa, le altre regole per le funzioni inline definite da 7.1.2 devono comunque essere rispettate.

Ad esempio da MSDN:

Il compilatore tratta le opzioni di espansione in linea e le parole chiave come suggerimenti. Non vi è alcuna garanzia che le funzioni saranno in linea. Non è ansible forzare il compilatore a incorporare una particolare funzione, anche con la parola chiave __forceinline. Durante la compilazione con / clr, il compilatore non esegue la funzione inline se sono presenti attributi di sicurezza applicati alla funzione.

Nota però:

3.2 Una regola di definizione

3 […] Una funzione inline deve essere definita in ogni unità di traduzione in cui viene utilizzata.

4 Una funzione inline deve essere definita in ogni unità di traduzione in cui viene utilizzata e deve avere esattamente la stessa definizione in ogni caso (3.2). [Nota: è ansible incontrare una chiamata alla funzione inline prima che la sua definizione venga visualizzata nell’unità di traduzione. -End note] Se la definizione di una funzione appare in un’unità di traduzione prima della sua prima dichiarazione come inline, il programma è mal formato. Se una funzione con collegamento esterno è dichiarata in linea in una unità di traduzione, deve essere dichiarata in linea in tutte le unità di traduzione in cui appare; non è richiesta alcuna diagnosi Una funzione inline con collegamento esterno deve avere lo stesso indirizzo in tutte le unità di traduzione. Una variabile locale statica in una funzione incorporata esterna fa sempre riferimento allo stesso object. Una stringa letterale nel corpo di una funzione inline esterna è lo stesso object in diverse unità di traduzione. [Nota: una stringa letterale che appare in un’espressione di argomento predefinita non si trova nel corpo di una funzione in linea semplicemente perché l’espressione viene utilizzata in una chiamata di funzione da quella funzione in linea. -End note] Un tipo definito all’interno del corpo di una funzione inline esterna è lo stesso tipo in ogni unità di traduzione.

[Nota: enfasi mia]

Una TU è fondamentalmente un insieme di intestazioni più un file di implementazione ( .cpp ) che porta a un file object.

Dovrebbe essere esplicitamente dichiarato quando si ha una piccola funzione (suppongo 1-4 istruzioni?)

Assolutamente. Perché non aiutare il compilatore ad aiutarti a generare meno codice? Di solito, se la parte prolog / epilog incorre in un costo maggiore rispetto a farla forzare inline il compilatore per generarli? Ma devi assolutamente passare attraverso questo articolo GOTW prima di iniziare con inlining: GotW # 33: Inline

Quali altri vantaggi ci sono con la scrittura in linea?

  • namespace può essere in inline . Nota che le funzioni membro definite nel corpo della class sono di default in linea. Quindi sono implicitamente generate funzioni speciali per i membri.

  • I modelli di funzione non possono essere definiti in un file di implementazione (vedere FAQ 35.12 ) a meno che, ovviamente, non si forniscano esplicitazioni esplicative (per tutti i tipi per i quali viene utilizzato il modello, in genere PITA IMO). Vedere l’articolo di DDJ su come spostare i modelli dai file di intestazione (se vi sentite strani leggere su questo altro articolo sulla parola chiave export che è stata eliminata dallo standard).

È necessario dichiarare in linea per ridurre la dimensione del file eseguibile, anche se il compilatore (secondo wikipedia [lo so, cattivo riferimento]) dovrebbe trovare tali funzioni da solo?

Di nuovo, come ho detto, come buon programmatore, dovresti, quando puoi, aiutare il compilatore. Ma ecco cosa ha da offrire le FAQ C ++ su inline . Quindi fai attenzione. Non tutti i compilatori eseguono questo tipo di analisi, quindi è necessario leggere la documentazione sui propri interruttori di ottimizzazione. Ad esempio: GCC fa qualcosa di simile:

Puoi anche indirizzare GCC a provare a integrare tutte le funzioni “abbastanza semplici” nei loro chiamanti con l’opzione -finline-functions.

La maggior parte dei compilatori ti consente di sovrascrivere l’analisi costi / benefici del compilatore in una certa misura. Vale la pena leggere la documentazione MSDN e GCC .

Ristabilire ciò che ho detto in quelle piccole caselle di commento. In particolare, non ho mai parlato di inlinging:

 // foo.h: static void f() { // code that can't be inlined } // TU1 calls f // TU2 calls f 

Ora, sia TU1 che TU2 hanno una propria copia di f – il codice di f è nell’eseguibile due volte.

 // foo.h: inline void f() { // code that can't be inlined } // TU1 calls f // TU2 calls f 

Entrambe le TU emetteranno versioni appositamente marcate di f che sono effettivamente unite dal linker scartando tutte tranne una di esse. Il codice di f esiste solo una volta nell’eseguibile.

Quindi abbiamo salvato spazio nell’eseguibile .

È solo una raccomandazione per il compilatore?

Sì. Ma il linker ha bisogno se ci sono più definizioni della funzione (vedi sotto)

Dovrebbe essere esplicitamente dichiarato quando si ha una piccola funzione (suppongo 1-4 istruzioni?)

Sulle funzioni definite nei file di intestazione è (solitamente) necessario. Non fa male aggiungerlo a piccole funzioni (ma non mi preoccupo). Nota i membri della class definiti all’interno della dichiarazione della class vengono dichiarati automaticamente in linea.

Quali altri vantaggi ci sono con la scrittura in linea?

Interrompe gli errori del linker se usato correttamente.

è necessario dichiarare in linea per ridurre la dimensione del file eseguibile, anche se il compilatore (secondo wikipedia [lo so, riferimento errato]) dovrebbe trovare tali funzioni da solo?

No. Il compilatore effettua un confronto costi / benefici per l’inlining di ogni chiamata di funzione e fa una scelta appropriata. Pertanto, le chiamate a una funzione possono essere evidenziate in situazioni di cortina e non inserite in altre (a seconda di come funziona l’algoritmo dei compilatori).

Velocità / Spazio sono due forze in competizione e dipende da ciò che il compilatore sta ottimizzando per cui determinerà le funzioni meteorologiche sono in linea e il tempo in cui l’eseguibile aumenterà o diminuirà.

Inoltre, se si utilizza un inline eccessivamente aggressivo che fa sì che il programma si espanda troppo, la località di riferimento viene persa e questo può effettivamente rallentare il programma (dal momento che è necessario portare in memoria più pagine eseguibili).

Definizione multipla:

File: head.h

 // Without inline the linker will choke. /*inline*/ int add(int x, int y) { return x + y; } extern void test() 

File: main.cpp

 #include "head.h" #include  int main() { std::cout << add(2,3) << std::endl; test(); } 

File: test.cpp

 #include "head.h" #include  void test() { std::cout << add(2,3) << std::endl; } 

Qui abbiamo due definizioni di add (). Uno in main.o uno in test.o

  1. Sì. Non è niente di più.
  2. No.
  3. Si suggerisce al compilatore che si tratta di una funzione che viene chiamata molto , in cui la parte “jump-to-the-function” richiede molto tempo di esecuzione. Il compilatore potrebbe decidere di mettere il codice funzione esattamente dove viene chiamato, invece dove sono le normali funzioni. Tuttavia, se una funzione è racchiusa in x posizioni, è necessario x volte lo spazio di una funzione normale.
  4. Fidati sempre del tuo compilatore per essere molto più intelligente di te stesso sul tema dell’ottimizzazione prematura .

In realtà, la funzione inline può aumentare le dimensioni eseguibili, perché il codice funzione inline è duplicato in ogni punto in cui viene chiamata questa funzione. Con i moderni compilatori C ++, in linea consente in gran parte al programmatore di credere, che scrive codice ad alte prestazioni. Il compilatore decide se rendere la funzione in linea o meno. Quindi, scrivere in linea ci consente solo di sentirci meglio …

A proposito di questo:

E “in linea” consente di definire la funzione più volte nel programma.

Posso pensare a un’istanza in cui ciò è utile: rendere più difficile il crack del codice di protezione dalla copia. Se si dispone di un programma che raccoglie le informazioni dell’utente e lo confronta con una chiave di registrazione, la funzione che esegue la verifica renderà più difficile per un cracker trovare tutti i duplicati di tale funzione.

Per quanto riguarda altri punti:

  1. inline è solo una raccomandazione per il compilatore, ma ci sono le direttive #pragma che possono forzare l’inlining di qualsiasi funzione.
  2. Poiché si tratta solo di una raccomandazione, è probabilmente sicuro chiedere esplicitamente e lasciare che il compilatore sovrascrivi la tua raccomandazione. Ma probabilmente è meglio ometterlo del tutto e lasciare decidere al compilatore.
  3. L’offuscamento di cui sopra è un ansible vantaggio dell’inlining.
  4. Come altri hanno già detto, in inline aumenterebbe effettivamente la dimensione del codice compilato.
  1. Sì, lo ignorerà facilmente quando pensa che la funzione sia troppo big o usi caratteristiche incompatibili (forse una gestione delle eccezioni). Inoltre, di solito c’è un’impostazione del compilatore che consente di eseguire automaticamente funzioni incorporate che ritiene meritevoli (/ Ob2 in MSVC).

  2. Dovrebbe essere esplicitamente indicato se si inserisce la definizione della funzione nel file di intestazione. Di solito è necessario per garantire che più unità di traduzione possano trarne vantaggio. E per evitare errori di definizione multipli. Inoltre, le funzioni inline sono inserite nella sezione COMDAT. Che dice al linker che può scegliere solo una delle molteplici definizioni. Equivalente a __declspec (selectany) in MSVC.

  3. Le funzioni in linea di solito non rendono l’eseguibile più piccolo. Poiché l’opcode della chiamata è in genere più piccolo del codice lavorato in linea, ad eccezione delle funzioni di stile di accesso alle proprietà molto piccole. Dipende ma più grande non è un risultato insolito.

Un altro vantaggio dell’in-lining (notare che l’inlining effettivo a volte è ortogonale all’uso della direttiva “inline”) si verifica quando una funzione utilizza parametri di riferimento. Passare due variabili a una funzione non inline per aggiungere il suo primo operando al secondo richiederebbe di forzare il valore del primo operando e l’indirizzo del secondo e quindi chiamare una funzione che dovrebbe inserire il primo operando e l’indirizzo del secondo e quindi aggiungere il precedente valore indirettamente all’indirizzo popped. Se la funzione fosse espansa in linea, il compilatore potrebbe semplicemente aggiungere una variabile direttamente all’altra.

In realtà la creazione porta a eseguibili più grandi, non più piccoli. Serve a ridurre un livello di riferimento indiretto, incollando il codice della funzione.

http://www.parashift.com/c++-faq-lite/inline-functions.html