Funzioni incorporate in C ++. Qual e il punto?


19

Secondo quanto ho letto, il compilatore non è obbligato a sostituire la chiamata di funzione di una funzione inline con il suo corpo, ma lo farà se può. Questo mi ha fatto pensare: perché abbiamo la parola in linea se è così? Perché non rendere tutte le funzioni incorporate per impostazione predefinita e lasciare che il compilatore capisca se può sostituire le chiamate con il corpo della funzione o no?


1
QUI . Pagina 6
Epsilon,

Risposte:


40

inlineviene da C; non era una novità di C ++.

Esistono parole chiave C ( registere inline) progettate per consentire al programmatore di fornire assistenza nell'ottimizzazione del codice. Questi sono generalmente ignorati al giorno d'oggi, poiché i compilatori possono fare meglio nell'assegnazione dei registri e decidere quando incorporare le funzioni (in effetti, un compilatore può incorporare o non incorporare una funzione in momenti diversi). La generazione di codice sui processori moderni è molto più complicata rispetto a quelli più deterministici comuni quando Ritchie stava inventando C.

Ciò che la parola significa ora, in C ++, è che può avere più definizioni identiche e deve essere definita in ogni unità di traduzione che la utilizza. (In altre parole, è necessario assicurarsi che possa essere integrato.) È possibile avere una inlinefunzione in un'intestazione senza problemi e le funzioni membro definite in una definizione di classe sono automaticamente efficaci inline.


3
+1 per la storia di inline.
Tamara Wijsman,

11
Sono abbastanza sicuro che sia inlinestato prima standardizzato in C ++, sebbene fosse già disponibile come estensione del fornitore in C .... sì, sembra che sia stato aggiunto allo standard C in C99.
Ben Voigt,

@Ben Voigt: hai ragione. L'ho incontrato per la prima volta in C.
David Thornley l'

5
Inoltre, tieni presente che non solo la cronologia è errata, ma le regole per inlinein C99 e successive sono diverse dalle regole per inlinein C ++.
Alf P. Steinbach,

27

Inizialmente inlineera un suggerimento molto forte che le chiamate alla funzione dovevano essere integrate.

Ma l'unico effetto garantitoinline è quello di consentire la definizione (efficacemente identica) di una funzione in più unità di traduzione, ad es. Di posizionare la definizione in un file di intestazione.

Al giorno d'oggi, alcuni compilatori sono molto interessati a seguire il suggerimento interno, ad esempio g ++. E alcuni compilatori lo prendono meno sul serio, ad esempio Visual C ++. Ma tutti devono attenersi alla garanzia.

È un peccato che questi due significati - suggerimento di ottimizzazione e quella che potremmo chiamare una definizione scartabile a livello di linker - risiedano con la stessa parola chiave, perché significa che non puoi praticamente avere uno senza l'altro.

È anche un peccato che inline(o meglio, una parola chiave separata sulla definizione scartabile) ¹ non possa essere applicato ai dati .

La necessità di dati scartabili a livello di linker è aumentata man mano che i moduli solo intestazione sono diventati più popolari. Ad esempio, molte sotto-librerie Boost sono solo intestazione.

Per i dati è tuttavia possibile applicare un piccolo trucco con i modelli. Definiscilo in un modello di classe, fornisci un typedefparametro template void(o qualsiasi altra cosa). Questo perché la One Definition Rule fa un'eccezione specifica per i template.

Note:
¹ le inlinevariabili saranno supportate in C ++ 17 .


1
+1 per ulteriori informazioni su inline.
Tamara Wijsman,

1
" efficacemente identico " è in realtà una condizione molto delicata, poiché i linguaggi C ++ progettati in modo molto malvagio si sforzano molto di rendere programmatori normalmente competenti e attenti scrivere codice ragionevole ma formalmente indefinito che utilizza oggetti statici - mi chiedo quanti membri del comitato cadano nella trappola stessi
curioso

6

Perché non rendere tutte le funzioni in linea per impostazione predefinita? Perché è un compromesso di ingegneria. Esistono almeno due tipi di "ottimizzazione": velocizzare il programma e ridurre le dimensioni (footprint di memoria) del programma. L'allineamento generalmente accelera le cose. Elimina l'overhead della chiamata di funzione, evitando di spingere e quindi estrarre i parametri dallo stack. Tuttavia, aumenta anche l'impronta di memoria del programma, poiché ogni chiamata di funzione deve ora essere sostituita con il codice completo della funzione. Per rendere le cose ancora più complicate, ricorda che la CPU memorizza blocchi di memoria utilizzati di frequente in una cache della CPU per un accesso ultra-rapido. Se si ingrandisce abbastanza l'immagine della memoria del programma, il programma non sarà in grado di utilizzare la cache in modo efficiente e, nel peggiore dei casi, l'allineamento potrebbe effettivamente rallentare il programma.


5
Ma il compilatore può (e lo fa!) Capirlo molto meglio del programmatore in generale. Quindi questo non è un argomento valido.
Konrad Rudolph,

" Ogni chiamata di funzione deve ora essere sostituita con il codice completo della funzione " E la funzione inline non è una funzione in cui la sequenza di chiamate viene omessa e in cui viene copiato il codice assembly della funzione. Viene creata una funzione inline, portando in linea il codice intermedio di alto livello. Ciò consente al compilatore di trattare efficacemente il corpo della funzione come una macro pulita (una macro pulita è quella che non ha le stranezze delle macro del preprocessore C), con potenzialmente molte ottimizzazioni disponibili.
curioso

3

Per capire "in linea" devi capire la storia e com'era la vita 20 (e 30) anni fa.

Stavamo scrivendo codice su computer che avevano poca memoria, quindi non era possibile per un compilatore elaborare tutto il codice che costituiva un programma in una volta sola. Anche il compilatore è stato molto lento, quindi non volevi ricompilare il codice che non era cambiato - prendere oltre 24 ore (su un computer che costava più di un'auto di fascia alta) per ricompilare tutto il codice era normale per alcuni progetti Lavorato su.

Pertanto ogni file di codice è stato compilato separatamente in un file oggetto. Ogni file oggetto è iniziato con un elenco di tutte le funzioni contenute, insieme all '"indirizzo" della funzione. Un file oggetto aveva anche un elenco di tutte le funzioni che chiamava in altri file oggetto insieme alla posizione della chiamata.

Un linker legge prima tutti i file oggetto e crea un elenco di tutte le funzioni che hanno esportato, insieme al file in cui si trovavano e all'indirizzo. Rileggerebbe quindi tutti i file oggetto, emettendoli nel file di programma, aggiornando tutte le chiamate di funzione "esterne" con l'indirizzo della funzione.

Il linker non ha modificato o ottimizzato il codice macchina prodotto dal compilatore in alcun modo se non per correggere riferimenti a chiamate di funzioni esterne. Il linker faceva parte del sistema operativo e precede la maggior parte dei compilatori. Quando le persone scrivevano un nuovo compilatore, avevano bisogno che funzionasse con i linker correnti e che fosse in grado di collegarsi ai file oggetto correnti, altrimenti non sarebbe stato possibile effettuare chiamate di sistema.

Il compilatore ha sempre visto il codice nel file ".c" o ".cpp" che stava compilando insieme a tutti i file di intestazione inclusi. Quindi non è stato possibile effettuare alcuna ottimizzazione basata sul codice in altri file ".c" o ".cpp".

La parola chiave "inline" ha consentito di definire il corpo di una funzione (metodo) in un file di intestazione, consentendo quindi al compilatore di utilizzare il codice della funzione durante la compilazione del codice che lo chiama. Ad esempio supponiamo che tu abbia una classe di raccolta definita in un altro file .cpp, questa classe avrebbe un metodo "isEmpty", che contiene una riga di codice, ci sarebbe una grande velocità del programma risultante se invece di una chiamata a una funzione , la chiamata di funzione è stata sostituita con questa riga.

La parola chiave "inline" era vista all'epoca come un modo "economico e semplice" per consentire l'incapsulamento dei dati evitando il costo delle chiamate di funzione, senza di essa molti programmatori avrebbero semplicemente accesso ai campi privati ​​dell'oggetto. (Le macro in cui un modo molto peggiore "allineano" il codice era comune all'epoca.)

In questi giorni i "linker" fanno molta ottimizzazione del codice e tendono ad essere scritti da alcuni team come compilatore. Il compilatore spesso verifica semplicemente che il codice sia corretto e lo "comprime", lasciando gran parte dell'attività di creazione del codice macchina al linker.


2

Vediamo cosa dice lo standard (parti importanti evidenziate in grassetto):

2. 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 nel punto di chiamata deve essere preferita al normale meccanismo di chiamata di funzione. Non è necessaria un'implementazione per eseguire questa sostituzione in linea al punto di chiamata; tuttavia, anche se questa sostituzione in linea viene omessa, le altre regole per le funzioni in linea devono comunque essere rispettate.

- Standard C ++, ISO / IEC 14882: 2003 , 7.1.2 Specificatori di funzioni [dcl.fct.spec]

Quindi, se vuoi essere sicuro, dovresti leggere la documentazione del tuo compilatore.

Inline tutto è una cattiva idea, in quanto potrebbe comportare un sacco di codice macchina duplicato ...

Quindi dovrai sapere:

Non ci sono risposte semplici: devi giocarci per vedere cosa è meglio. Do Non accontentarsi di risposte semplicistiche come, "Usa Mai inlinefunzioni" o "Usa sempre inlinele funzioni" o "Usa inlinele funzioni di se e solo se la funzione è inferiore a N righe di codice." Queste regole a misura unica possono essere facili da scrivere, ma produrranno risultati non ottimali.

- Domande frequenti su C ++, Funzioniinline incorporate , 9.3 Le funzioni migliorano le prestazioni?


1
Quello che dici è vero, ma non pertinente perché come programmatore non è una tua decisione se fare la fila o meno. Metti la parola chiave, potresti o meno includere le tue funzioni. Non inserisci la parola chiave, potresti ancora o non farli incorporare. È inutile avere QUALSIASI regola per quando si inserisce la parola chiave, con il compilatore che è quello che prende effettivamente la decisione.
Kate Gregory,

In che modo non è rilevante se dice esattamente lo stesso di te? Non ci sono risposte semplici .
Tamara Wijsman,

Non è pertinente perché non risponde alla domanda dei PO. Non sta chiedendo "cosa fa Inline", sta chiedendo "qual è il punto". E la tua risposta ribadisce semplicemente ciò che già sappiamo in linea.
Davor Ždralo,

@Davor: "Qual è il punto?" non aderisce alle FAQ, quindi sto rispondendo alle domande poste all'interno della domanda. Sto ribadendo il punto inlineribadendo la conoscenza poiché l'OP sembra mancare una parte, quindi questa è la ragione principale per cui non capisce il punto. Per ottenere un punto ha bisogno di capire cosa c'è sotto quel punto ...
Tamara Wijsman,

Perché diavolo non dovrebbe aderire alle FAQ? Lo standard descrive la sintassi e la semantica della parola chiave inline e la domanda di OP è qual è lo scopo, quando dovrebbe essere usato, quali problemi dovrebbe risolvere? Non vedo come ciò non rispetti le FAQ.
Davor Ždralo,

1

Lascia che ti dia una buona ragione per usare la parola chiave inline.

Su un sistema incorporato, come una stampante di biglietti o un sistema più piccolo simile. Il processore è molto limitato e una chiamata di funzione (preparazione dei parametri di funzione nello stack, chiamata, recupero dei parametri dallo stack e risposta di ritorno, ecc.) Può richiedere diversi ms per l'esecuzione, oltre alla funzione stessa.

Diciamo che il tempo di chiamata è di circa 60 ms (solo per la chiamata, non la funzione effettiva) e si hanno 50 iterazioni (loop o chiamate iterative in un albero).

Il tempo per spostarsi avanti e indietro da quella chiamata di funzione richiederà 60 * 50 = 3000 (3 secondi).

Se hai la memoria, faresti sicuramente un inline per risparmiare 3 secondi.

Quindi gli inline sono usati fondamentalmente quando hai bisogno di velocità di esecuzione. In alcuni progetti in cui sono stato coinvolto, il tempo di chiamata era più lungo del tempo di esecuzione, una situazione classica in cui utilizzare in linea.


Eh, cosa? Inserire inline nel tuo codice non significa che il compilatore lo inserirà effettivamente, e non metterlo lì non significa che non lo farà. È solo un suggerimento, che viene per lo più ignorato dai compilatori di oggi. Se il compilatore trova utile inline, allora non lo farà. Non hai un vero controllo lì. OP lo sa già e chiede: "Qual è il punto?". Chi diavolo dà +1 su questo? È sia fuorviante (implicando che la parola chiave inline impone l'allineamento) sia irrilevante per la domanda.
Assapora Ždralo l'

2
@Davor Ždralo Non c'è bisogno di essere scortese. Ho interpretato la domanda come PERCHÉ dovrei usare le linee? Il mio punto era dimostrare che è possibile ottenere il codice più velocemente a costo della memoria. Ogni compilatore può gestire la parola chiave inline in modo diverso, quindi è necessario controllare la documentazione, che non ho menzionato. Dal momento che non puoi sempre permetterti la memoria aggiuntiva inline crea, è utile "guidare" quando usarlo. Inoltre non ho detto che le linee sono state applicate. Qualcuno ha trovato questa risposta utile a lui / lei e ha votato +1, per favore rispetta che altri potrebbero avere un altro livello di esperienza e trovare qualcosa di banale utile.
Max Kielland,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.