"Inline" senza "statico" o "esterno" è mai utile in C99?


96

Quando provo a creare questo codice

inline void f() {}

int main()
{
    f();
}

utilizzando la riga di comando

gcc -std=c99 -o a a.c

Ottengo un errore del linker (riferimento indefinito a f). L'errore scompare se uso static inlineo extern inlineinvece di solo inline, o se compilo con -O(quindi la funzione è effettivamente inline).

Questo comportamento sembra essere definito nel paragrafo 6.7.4 (6) dello standard C99:

Se tutte le dichiarazioni dell'ambito del file per una funzione in un'unità di traduzione includono l' inlineidentificatore di funzione senza extern, la definizione in tale unità di traduzione è una definizione inline. Una definizione inline non fornisce una definizione esterna per la funzione e non vieta una definizione esterna in un'altra unità di traduzione. Una definizione inline 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 inline o la definizione esterna.

Se ho capito bene tutto ciò, un'unità di compilazione con una funzione definita inlinecome nell'esempio sopra viene compilata in modo coerente solo se esiste anche una funzione esterna con lo stesso nome e non so mai se viene chiamata la mia funzione o la funzione esterna.

Questo comportamento non è completamente sciocco? È mai utile definire una funzione inlinesenza statico externin C99? Mi sto perdendo qualcosa?

Riepilogo delle risposte

Ovviamente mi mancava qualcosa e il comportamento non è stupido. :)

Come spiega Nemo , l'idea è di mettere la definizione della funzione

inline void f() {}

nel file di intestazione e solo una dichiarazione

extern inline void f();

nel file .c corrispondente. Solo la externdichiarazione attiva la generazione di codice binario visibile esternamente. E non c'è davvero alcun uso di inlinein un file .c - è utile solo nelle intestazioni.

Come spiega la logica del comitato C99 citata nella risposta di Jonathan , si inlinetratta di ottimizzazioni del compilatore che richiedono che la definizione di una funzione sia visibile nel sito di una chiamata. Ciò può essere ottenuto solo inserendo la definizione nell'intestazione, e ovviamente una definizione in un'intestazione non deve emettere codice ogni volta che viene vista dal compilatore. Ma poiché il compilatore non è obbligato a inserire effettivamente una funzione, deve esistere una definizione esterna da qualche parte.




@Earlz: grazie per il collegamento. La mia domanda riguarda la logica alla base del paragrafo citato dello standard e se ci sono mai casi d'uso per inlinesenza statice extern, però. Sfortunatamente, nessuno di questi problemi è trattato in questa domanda.
Sven Marnach

@Nemo: ho letto quella domanda e le risposte prima di pubblicare la mia. Di nuovo, non sto chiedendo "come si comporta?" ma piuttosto "qual è l'idea alla base di questo comportamento"? Sono abbastanza sicuro che mi manchi qualcosa qui.
Sven Marnach

1
sì, è per questo che ho detto "borderline". Personalmente penso che questa sia una domanda completamente diversa
Earlz

Risposte:


40

In realtà questa ottima risposta risponde anche alla tua domanda, penso:

Cosa fa l'extern inline?

L'idea è che "inline" possa essere utilizzato in un file di intestazione e quindi "extern inline" in un file .c. "extern inline" è proprio il modo in cui istruisci il compilatore su quale file oggetto deve contenere il codice generato (visibile esternamente).

[aggiornare, elaborare]

Non penso che ci sia alcun uso di "inline" (senza "static" o "extern") in un file .c. Ma in un file di intestazione ha senso e richiede una corrispondente dichiarazione "extern inline" in un file .c per generare effettivamente il codice autonomo.


1
Grazie, sto iniziando a farmi un'idea!
Sven Marnach

8
Sì, non l'ho mai capito fino ad ora, quindi grazie per avermelo chiesto :-). È strano in quanto la definizione "inline" non extern va nell'intestazione (ma non comporta necessariamente alcuna generazione di codice), mentre la dichiarazione "inline extern" va nel file .c e in realtà fa sì che il codice essere generato.
Nemo

3
Questo significa che scrivo inline void f() {}nell'intestazione e extern inline void f();nel file .c? Quindi la definizione della funzione effettiva va nell'intestazione e il file .c contiene una semplice dichiarazione in questo caso, al contrario del solito ordine?
Sven Marnach

1
@endolith: non capisco la tua domanda. Stiamo discutendo inline senza static o extern. Ovviamente static inlineva bene, ma non è questo l'argomento di questa domanda e risposta.
Nemo

2
@MatthieuMoy È necessario utilizzare al -std=c99posto di -std=gnu89.
A3f

27

Dallo standard (ISO / IEC 9899: 1999) stesso:

Appendice J.2 Comportamento non definito

  • ...
  • Una funzione con collegamento esterno è dichiarata con un inlineidentificatore di funzione, ma non è definita anche nella stessa unità di traduzione (6.7.4).
  • ...

Il Comitato C99 ha scritto una logica e dice:

6.7.4 specificatori di funzione

Una nuova funzionalità di C99: la inlineparola chiave, adattata da C ++, è un identificatore di funzione che può essere utilizzato solo nelle dichiarazioni di funzione. È utile per le ottimizzazioni del programma che richiedono che la definizione di una funzione sia visibile nel sito di una chiamata. (Si noti che lo Standard non cerca di specificare la natura di queste ottimizzazioni.)

La visibilità è assicurata se la funzione ha un collegamento interno o se ha un collegamento esterno e la chiamata si trova nella stessa unità di traduzione della definizione esterna. In questi casi, la presenza della inlineparola chiave in una dichiarazione o definizione della funzione non ha alcun effetto oltre a indicare una preferenza che le chiamate di quella funzione dovrebbero essere ottimizzate rispetto alle chiamate di altre funzioni dichiarate senza la inlineparola chiave.

La visibilità è un problema per una chiamata di una funzione con collegamento esterno in cui la chiamata si trova in un'unità di traduzione diversa dalla definizione della funzione. In questo caso, la inlineparola chiave consente all'unità di traduzione contenente la chiamata di contenere anche una definizione locale, o inline, della funzione.

Un programma può contenere un'unità di traduzione con una definizione esterna, un'unità di traduzione con una definizione inline e un'unità di traduzione con una dichiarazione ma nessuna definizione per una funzione. Le chiamate in quest'ultima unità di traduzione useranno la definizione esterna come al solito.

Una definizione inline di una funzione è considerata una definizione diversa dalla definizione esterna. Se si verifica una chiamata a una funzione funccon collegamento esterno dove è visibile una definizione inline, il comportamento è lo stesso come se la chiamata fosse stata effettuata a un'altra funzione, ad esempio __funccon collegamento interno. Un programma conforme non deve dipendere dalla funzione chiamata. Questo è il modello in linea nello Standard.

Un programma conforme non deve fare affidamento sull'implementazione utilizzando la definizione inline, né può fare affidamento sull'implementazione utilizzando la definizione esterna. L'indirizzo di una funzione è sempre l'indirizzo corrispondente alla definizione esterna, ma quando questo indirizzo viene utilizzato per chiamare la funzione, potrebbe essere utilizzata la definizione inline. Pertanto, il seguente esempio potrebbe non funzionare come previsto.

inline const char *saddr(void)
{
    static const char name[] = "saddr";
    return name;
}
int compare_name(void)
{
    return saddr() == saddr(); // unspecified behavior
}

Poiché l'implementazione potrebbe utilizzare la definizione inline per una delle chiamate a saddre utilizzare la definizione esterna per l'altra, non è garantito che l'operazione di uguaglianza restituisca 1 (true). Ciò mostra che gli oggetti statici definiti all'interno della definizione inline sono distinti dal loro oggetto corrispondente nella definizione esterna. Ciò ha motivato il vincolo anche contro la definizione di un non constoggetto di questo tipo.

L'inlining è stato aggiunto allo standard in modo tale da poter essere implementato con la tecnologia linker esistente e un sottoinsieme di inlining C99 è compatibile con C ++. Ciò è stato ottenuto richiedendo che esattamente un'unità di traduzione contenente la definizione di una funzione inline sia specificata come quella che fornisce la definizione esterna per la funzione. Perché quella specifica consiste semplicemente di una dichiarazione che o manca la inlineparola chiave, o contiene sia inlinee extern, sarà anche accettati da un traduttore C ++.

L'inlining in C99 estende la specifica C ++ in due modi. Primo, se una funzione è dichiarata inlinein un'unità di traduzione, non è necessario che sia dichiarata inlinein ogni altra unità di traduzione. Ciò consente, ad esempio, una funzione di libreria che deve essere inline all'interno della libreria ma disponibile solo tramite una definizione esterna altrove. L'alternativa di utilizzare una funzione wrapper per la funzione esterna richiede un nome aggiuntivo; e può anche avere un impatto negativo sulle prestazioni se un traduttore non esegue effettivamente la sostituzione in linea.

In secondo luogo, il requisito che tutte le definizioni di una funzione inline siano "esattamente le stesse" è sostituito dal requisito che il comportamento del programma non debba dipendere dal fatto che una chiamata sia implementata con una definizione inline visibile, o la definizione esterna, di un funzione. Ciò consente a una definizione inline di essere specializzata per il suo utilizzo all'interno di una particolare unità di traduzione. Ad esempio, la definizione esterna di una funzione di libreria potrebbe includere una convalida di argomenti che non è necessaria per le chiamate effettuate da altre funzioni nella stessa libreria. Queste estensioni offrono alcuni vantaggi; ei programmatori preoccupati per la compatibilità possono semplicemente attenersi alle regole C ++ più rigide.

Si noti che è non è appropriato per le implementazioni di fornire definizioni in linea di funzioni della libreria standard nelle intestazioni standard, perché questo può rompere alcune codice legacy che redeclares funzioni della libreria standard dopo compresi i loro intestazioni. La inlineparola chiave ha il solo scopo di fornire agli utenti un modo portabile per suggerire l'integrazione di funzioni. Poiché le intestazioni standard non devono essere portabili, le implementazioni hanno altre opzioni sulla falsariga di:

#define abs(x) __builtin_abs(x)

o altri meccanismi non portabili per incorporare funzioni di libreria standard.


Grazie, è molto dettagliato e leggibile quanto lo standard stesso. :-) Sapevo che mi doveva mancare qualcosa. Potresti fornire un riferimento da dove l'hai preso?
Sven Marnach

Mi è appena venuto in
Sven Marnach

@Sven: ho preso in prestito l'URL dalla tua ricerca e l'ho inserito nella risposta. Il documento che ho usato era una copia della motivazione che avevo salvato in precedenza (nel 2005), ma era anche V5.10.
Jonathan Leffler

4
Grazie ancora. L'intuizione più preziosa che ho ottenuto dalla risposta è che esiste un documento contenente la logica dello standard C99 (e probabilmente ci sono anche documenti per altri standard). Mentre la risposta di Nemo mi ha dato un pesce, questo mi ha insegnato a pescare.
Sven Marnach

0

> Ottengo un errore del linker (riferimento indefinito a f)

Funziona qui: Linux x86-64, GCC 4.1.2. Potrebbe essere un bug nel tuo compilatore; Non vedo nulla nel paragrafo citato dello standard che vieti il ​​programma dato. Notare l'uso di if piuttosto che iff .

Una definizione inline fornisce un'alternativa a una definizione esterna , che un traduttore può utilizzare per implementare qualsiasi chiamata alla funzione nella stessa unità di traduzione.

Quindi, se conosci il comportamento della funzione fe vuoi chiamarla in un ciclo ristretto, puoi copiare e incollare la sua definizione in un modulo per evitare chiamate di funzione; oppure , puoi fornire una definizione che, per gli scopi del modulo corrente, è equivalente (ma salta la validazione dell'input, o qualunque ottimizzazione tu possa immaginare). Il writer del compilatore, tuttavia, ha la possibilità di eseguire l'ottimizzazione per la dimensione del programma.


2
Lo standard afferma che il compilatore può sempre presumere che ci sia una funzione esterna con lo stesso nome e chiamarla invece della versione inline, quindi non penso che sia un bug del compilatore. Ho provato con gcc 4.3, 4.4 e 4.5, tutti danno un errore del linker.
Sven Marnach
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.