main () prototipi di funzioni

7

Per quanto ne so,

main ()
La funzione

ha i seguenti prototipi:

    int main();
    int main(int argc, char **argv);

Ora, C non supporta l'overloading, quindi come sono i prototipi multipli di

    main ()

supportato?

    
posta high5 02.03.2013 - 09:36
fonte

4 risposte

8

Poiché un programma può contenere solo una funzione chiamata main , in realtà è molto semplice per il compilatore supportare le firme multiple.

Nella sequenza di chiamate per main , il chiamante è responsabile della pulizia degli argomenti che sono stati forniti.
E il compilatore fornisce un pezzo di codice di avvio che prepara gli argomenti per main e quindi richiama l'unica funzione di quel nome, senza verificare se effettivamente userà quegli argomenti.

Come nota a margine, gli ultimi due prototipi sono identici e in una definizione di funzione, anche i primi due sarebbero identici. Questo lascia solo due firme supportate per main .

    
risposta data 02.03.2013 - 10:39
fonte
4

Una cosa che devi capire su C o su qualsiasi altra lingua che produce file di oggetti nativi è che l'unica funzione temporale dei prototipi di funzione è durante la compilazione. Nel momento in cui il linker lo vede, l'unica cosa da fare è compilare gli indirizzi mancanti delle funzioni esterne.

L'organizzazione del passaggio dei parametri di funzione dipende interamente dai compilatori, in genere basati su una convenzione specifica dell'architettura. La maggior parte delle implementazioni su macchine con stack spingono i parametri in ordine inverso seguiti dall'indirizzo di ritorno. Ergo, quando il codice vuole chiamare f(int a1, int a2, int a3) , lo stack conterrà Return Address a1 a2 a3 se lo stai leggendo dall'alto verso il basso. Ciò significa che la funzione può raggiungere a1 guardando il secondo elemento nello stack, a2 guardando il terzo, ecc.

Chiamare main() funziona esattamente nello stesso modo, quindi dal suo punto di vista, lo stack conterrà Return Address argc argv envp . (L'ultimo elemento è qualcosa che Unix e altri sistemi hanno fatto per decenni, ma non è in alcun standard. Vedrai perché non importa in un secondo.)

Sezione 5.1.2.2.1 dello standard C specificamente afferma che nessuna implementazione definirà un prototipo per main() e che le due implementazioni standard sono main() e main(int argc, char **argv) . Non avere un prototipo predefinito ti permette di dichiarare main come preferisci e non avere il compilatore che lo trattiene. Una versione a zero argomento ignorerà completamente la pila e il sapore a due argomenti guarderà il secondo e il terzo elemento. Potresti anche, fare una versione a un argomento, main(int argc) , che funzionerebbe perché consumerebbe argc e non sapere che argv è lì.

Come avrete intuito, questo trucco funziona solo nella direzione di un chiamante che chiama una funzione con più argomenti rispetto a quanto la funzione chiamata consuma avrà appena attivato più di quanto il chiamante vedrà. Una funzione chiamata con più parametri rispetto al chiamante premuto semplicemente guarderà più in basso di quanto dovrebbe e sarà vittima del principio GIGO . Dato che il chiamante annulla la regolazione del puntatore allo stack, lo stack ne esce indenne dopo il ritorno della funzione chiamata.

Addendum, affrontando il commento di Bart van Ingen Schenau:

C ++ maneggia i suoi nomi di simboli per prevenire collisioni tra funzioni con lo stesso nome e con prototipi diversi, poiché quasi tutti i formati di file di oggetti nativi sul pianeta differenziano i simboli solo per nome. Questo ha il piacevole effetto collaterale di evitare chiamate non corrispondenti tra i file oggetto C ++, ma solo perché un compilatore C ++ che compila una chiamata prototipata erroneamente emetterà un simbolo che non sarà presente nel file oggetto che conterrebbe la funzione chiamata. Accade che si ottengano nomi di funzioni C ++ leggibili da umani nei messaggi di errore del linker perché molti di loro riconoscono e distraggono i simboli C ++.

Un file oggetto può riferirsi a una funzione C ++ in un altro file oggetto dal suo nome storpiato e provare a chiamarlo. (L'ho fatto in modo sperimentale e in un milione di anni non l'avrei mai permesso di farlo in codice "reale".) Il linker, che non ha idea se un oggetto sia stato prodotto da un compilatore C ++ o da un po 'di assembly scritto a mano , non farà nulla per impedirlo finché i nomi dei simboli coincidono. Ciò rende asserzione che il linker abbia un ruolo diretto nell'imporre erroneamente le politiche del linguaggio C ++.

    
risposta data 02.03.2013 - 14:49
fonte
0

Se definisci una funzione denominata foo , puoi assegnarle qualsiasi prototipo (legale) che ti piace:

int foo(void);
int foo(int argc, char *argv[]);
void foo(long double this, const unsigned short ***that, struct thingie the_other);

Stai definendo foo te stesso, quindi puoi definirlo in qualsiasi modo.

Allo stesso modo, stai definendo main te stesso; l'implementazione non lo definisce per te. Ciò che è diverso su main non è che può avere uno dei due diversi prototipi, è che è limitato ad avere uno di solo quei due diversi prototipi (o equivalenti). E questo per garantire che il compilatore e l'ambiente di chiamata sappiano come richiamare la tua funzione main . (Per foo , qualsiasi chiamata sarà la tua.)

( main può anche essere definito in qualche altro modulo definito dall'implementazione, ma tali moduli non sono portabili.)

Per quanto riguarda il motivo per cui questi due sono consentiti, piuttosto che, per dire, richiedendo esattamente la seconda forma, è soprattutto per ragioni storiche. I primi compilatori C non avevano prototipi (furono introdotti nello standard C del 1989, preso in prestito da C ++). In pre-standard C, definendo

main() { /* ... */ }

probabilmente voleva dire che l'ambiente chiamante avrebbe passato argc e argv valori (e possibilmente envp ) a main , e main li avrebbe semplicemente ignorati. Era conveniente omettere le definizioni di argc e argv se il tuo programma non le avrebbe usate. Le convenzioni di chiamata erano tali che le due forme erano effettivamente compatibili.

Per ragioni storiche, le convenzioni di chiamata moderne potrebbero funzionare in modo simile, ma dal momento che i compilatori standard ANSI del 1989 sono stati autorizzati a rendere le due forme incompatibili - ma se lo sono, l'implementazione deve fare tutto il necessario per fare entrambe le forme funzionano.

    
risposta data 02.03.2013 - 21:20
fonte
-2

C does not support overloading

C supporta l'overloading, non supporta l'overloading delle funzioni definito dall'utente, ma gli operatori hanno un set fisso di overload definiti standard.

how are multiple prototypes of main () supported?

Il compilatore deve fare le cose giuste per le diverse definizioni possibili di main() , ma deve solo mantenere un prototipo per i controlli delle chiamate recurse; non deve comportarsi in modo diverso per il main che per qualsiasi altra funzione, tranne quando si compila la sua definizione. Non puoi fornirti un prototipo che non corrisponda alla definizione che usi e si aspetti che le cose si comportino in modo sano.

BTW, nota che le tue ultime due righe sono strettamente equivalenti e che la differenza tra i primi due può verificarsi in programmi validi per funzioni diverse da quelle principali.

    
risposta data 02.03.2013 - 10:42
fonte

Leggi altre domande sui tag