it-swarm.it

Perché è possibile avere la definizione del metodo all'interno del file di intestazione in C ++ quando in C non è possibile?

In C, non è possibile avere la definizione/implementazione della funzione all'interno del file di intestazione. Tuttavia, in C++ è possibile avere l'implementazione completa del metodo all'interno del file di intestazione. Perché il comportamento è diverso?

25
Joshua Partogi

In C, se si definisce una funzione in un file di intestazione, tale funzione verrà visualizzata in ciascun modulo che viene compilato che include quel file di intestazione e verrà esportato un simbolo pubblico per la funzione. Quindi, se la funzione additup è definita in header.h e foo.c e bar.c includono entrambi header.h, allora foo.o e bar.o includeranno entrambi copie di additup.

Quando vai a collegare insieme questi due file di oggetti, il linker vedrà che il simbolo additup è definito più di una volta e non lo consentirà.

Se si dichiara che la funzione è statica, non verrà esportato alcun simbolo. I file oggetto foo.o e bar.o conterranno entrambi copie separate del codice per la funzione e saranno in grado di usarli, ma il linker non sarà in grado di vedere alcuna copia della funzione, quindi non si lamenterà. Naturalmente, nessun altro modulo sarà in grado di vedere neanche la funzione. E il tuo programma sarà gonfiato con due copie identiche della stessa funzione.

Se dichiari solo la funzione nel file di intestazione, ma non la definisci, e poi la definisci in un solo modulo, il linker vedrà una copia della funzione e ogni modulo nel tuo programma sarà in grado di vederla e usalo. E il tuo programma compilato conterrà solo una copia della funzione.

Quindi puoi hai la definizione della funzione nel file di intestazione in C, è solo cattivo stile, cattiva forma e una cattiva idea a tutto tondo.

(Per "dichiarare" intendo fornire un prototipo di funzione senza un corpo; per "definire" intendo fornire il codice effettivo del corpo della funzione; questa è la terminologia C standard.)

29
David Conrad

C e C++ si comportano allo stesso modo in questo senso - puoi avere inline funzioni nelle intestazioni. In C++, qualsiasi metodo il cui corpo è all'interno della definizione della classe è implicitamente inline. Se vuoi fare lo stesso in C, dichiara le funzioni static inline.

30
Simon Richter

Il concetto di un file di intestazione richiede una piccola spiegazione:

O dai un file sulla riga di comando del compilatore o fai un '#include'. La maggior parte dei compilatori accetta un file di comando con estensione c, C, cpp, c ++, ecc. Come file di origine. Tuttavia, di solito includono un'opzione della riga di comando per consentire l'utilizzo di qualsiasi estensione arbitraria anche a un file di origine.

Generalmente il file fornito nella riga di comando si chiama 'Origine' e quello incluso si chiama 'Intestazione'.

Il passaggio del preprocessore li prende effettivamente tutti e fa apparire tutto come un singolo file di grandi dimensioni al compilatore. Ciò che era nell'intestazione o nella fonte in realtà non è rilevante a questo punto. Di solito c'è un'opzione di un compilatore che può mostrare l'output di questo stage.

Quindi per ogni file che è stato dato sulla riga di comando del compilatore, al compilatore viene assegnato un file enorme. Questo può contenere codice/dati che occuperanno memoria e/o creeranno un simbolo a cui fare riferimento da altri file. Ora ciascuno di questi genererà un'immagine "oggetto". Il linker può dare un "simbolo duplicato" se lo stesso simbolo viene trovato in più di due file oggetto che sono collegati tra loro. Forse questo è il motivo; non è consigliabile inserire il codice in un file di intestazione, che può creare simboli nel file oggetto.

Gli "inline" sono generalmente incorporati ... ma durante il debug potrebbero non essere incorporati. Quindi perché il linker non fornisce errori definiti in modo multiplo? Semplice ... Questi sono simboli "deboli" e fintanto che tutti i dati/codice per un simbolo debole di tutti gli oggetti hanno le stesse dimensioni e il contenuto, il collegamento manterrà una copia e rilascerà copia da altri oggetti. Funziona.

7
vrdhn

Puoi farlo in C99: le funzioni inline sono garantite per essere fornite da qualche altra parte, quindi se una funzione non è stata sottolineata, la sua definizione viene tradotta in una dichiarazione (cioè l'implementazione viene scartata). E, naturalmente, puoi usare static.

4
SK-logic

Virgolette standard C++

C++ 17 N4659 standard draft 10.1.6 "Lo specificatore inline" dice che i metodi sono implicitamente inline:

4 Una funzione definita all'interno di una definizione di classe è una funzione incorporata.

e poi più in basso vediamo che i metodi inline non solo possono essere definiti, ma must su tutte le unità di traduzione:

6 Una funzione o variabile in linea deve essere definita in ogni unità di traduzione in cui è usata per un errore e deve avere esattamente la stessa definizione in ogni caso (6.2).

Ciò è anche esplicitamente menzionato in una nota in 12.2.1 "Funzioni membro":

1 Una funzione membro può essere definita (11.4) nella sua definizione di classe, nel qual caso si tratta di una funzione membro in linea (10.1.6) [...]

3 [Nota: in un programma può esserci al massimo una definizione di una funzione membro non inline. Potrebbe esserci più di una definizione di funzione membro in linea in un programma. Vedi 6.2 e 10.1.6. - nota finale]

Implementazione di GCC 8.3

main.cpp

struct MyClass {
    void myMethod() {}
};

int main() {
    MyClass().myMethod();
}

Compilare e visualizzare simboli:

g++ -c main.cpp
nm -C main.o

produzione:

                 U _GLOBAL_OFFSET_TABLE_
0000000000000000 W MyClass::myMethod()
                 U __stack_chk_fail
0000000000000000 T main

allora vediamo da man nm che il simbolo MyClass::myMethod è contrassegnato come debole sui file oggetto ELF, il che implica che può apparire su più file oggetto:

"W" "w" Il simbolo è un simbolo debole che non è stato specificamente etichettato come simbolo di un oggetto debole. Quando un simbolo definito debole è collegato con un simbolo definito normale, il simbolo definito normale viene utilizzato senza errori. Quando un simbolo indefinito debole è collegato e il simbolo non è definito, il valore del simbolo viene determinato in modo specifico per il sistema senza errori. Su alcuni sistemi, lettere maiuscole indicano che è stato specificato un valore predefinito.