È "int main;" un valido programma C / C ++?


113

Lo chiedo perché il mio compilatore sembra pensarlo, anche se non lo faccio.

echo 'int main;' | cc -x c - -Wall
echo 'int main;' | c++ -x c++ - -Wall

Clang non emette alcun avviso o errore con questo, e gcc emette solo il mite avviso:, 'main' is usually a function [-Wmain]ma solo quando compilato come C. Specificare a -std=non sembra avere importanza.

Altrimenti, si compila e si collega correttamente. Ma durante l'esecuzione, termina immediatamente con SIGBUS(per me).

Leggere le (eccellenti) risposte in Cosa dovrebbe restituire main () in C e C ++? e un rapido grep attraverso le specifiche del linguaggio, mi sembrerebbe sicuramente che sia richiesta una funzione principale . Ma la verbosità di gcc -Wmain("main" è di solito una funzione) (e la scarsità di errori qui) sembra suggerire il contrario.

Ma perché? C'è qualche strano uso marginale o "storico" per questo? Qualcuno sa cosa dà?

Il mio punto, suppongo, è che penso davvero che questo dovrebbe essere un errore in un ambiente ospitato, eh?


6
Per rendere gcc un compilatore (per lo più) conforme agli standard è necessariogcc -std=c99 -pedantic ...
pmg

3
@pmg È lo stesso avviso, con o senza -pedantico qualsiasi -std. Il mio sistema c99lo compila anche senza avvertimenti o errori ...
Geoff Nixon

3
Sfortunatamente, se sei "abbastanza intelligente", puoi creare cose che sono accettabili dal compilatore ma non hanno senso. In questo caso, stai collegando la libreria runtime C per chiamare una variabile chiamata main, che è improbabile che funzioni. Se si inizializza main con il valore "giusto", potrebbe effettivamente restituire ...
Mats Petersson

7
E anche se è valido, è una cosa orribile da fare (codice illeggibile). A proposito, potrebbe essere diverso nelle implementazioni ospitate e nelle implementazioni indipendenti (che non conosco main)
Basile Starynkevitch

1
Per momenti più divertenti, provamain=195;
imallett

Risposte:


97

Poiché la domanda è doppiamente contrassegnata come C e C ++, il ragionamento per C ++ e C sarebbe diverso:

  • Il C ++ usa la modifica dei nomi per aiutare il linker a distinguere tra simboli testualmente identici di tipi diversi, ad esempio una variabile globale xyze una funzione globale indipendente xyz(int). Tuttavia, il nome mainnon viene mai alterato.
  • C non usa la manipolazione, quindi è possibile che un programma confonda il linker fornendo un simbolo di un tipo al posto di un simbolo diverso, e il programma si collega correttamente.

Questo è quello che sta succedendo qui: il linker si aspetta di trovare il simbolo maine lo fa. "Fissa" quel simbolo come se fosse una funzione, perché non conosce di meglio. La parte della libreria runtime che passa il controllo al mainlinker richiede main, quindi il linker gli fornisce il simbolo main, lasciando che la fase di collegamento venga completata. Ovviamente questo fallisce in fase di esecuzione, perché mainnon è una funzione.

Ecco un'altra illustrazione dello stesso problema:

file xc:

#include <stdio.h>
int foo(); // <<== main() expects this
int main(){
    printf("%p\n", (void*)&foo);
    return 0;
}

file yc:

int foo; // <<== external definition supplies a symbol of a wrong kind

la compilazione:

gcc x.c y.c

Questo si compila e probabilmente verrebbe eseguito, ma è un comportamento indefinito, perché il tipo del simbolo promesso al compilatore è diverso dal simbolo effettivo fornito al linker.

Per quanto riguarda l'avvertimento, penso che sia ragionevole: C consente di creare librerie che non hanno alcuna mainfunzione, quindi il compilatore libera il nome mainper altri usi se è necessario definire una variabile mainper qualche motivo sconosciuto.


3
Tuttavia, il compilatore C ++ tratta la funzione principale in modo diverso. Il suo nome non è alterato anche senza la "C" esterna. Immagino sia perché altrimenti avrebbe bisogno di emettere il proprio main "C" esterno, per garantire il collegamento.
UldisK

@UldisK Sì, l'ho notato anch'io e l'ho trovato piuttosto interessante. Ha senso, ma non ci avevo mai pensato.
Geoff Nixon

2
In realtà, i risultati per C ++ e C non sono diversi, come sottolineato qui - mainnon è soggetto a alterazione del nome (così sembra) in C ++, indipendentemente dal fatto che si tratti di una funzione.
Geoff Nixon

4
@nm Penso che la tua interpretazione della domanda sia troppo ristretta: oltre a porre la domanda nel titolo del post, OP cerca chiaramente una spiegazione del perché il suo programma sia stato compilato in primo luogo ("il mio compilatore sembra pensarla così, anche se non lo faccio ") e un suggerimento sul perché potrebbe essere utile definire mainqualcosa di diverso da una funzione. La risposta offre una spiegazione per entrambe le parti.
dasblinkenlight

1
Che il simbolo principale non sia soggetto a alterazione del nome è irrilevante. Non vi è alcuna menzione di alterazione dei nomi nello standard C ++. Il nome alterato è un problema di implementazione.
David Hammen

30

mainnon è una parola riservata è solo un identificatore predefinito (come cin, endl, npos...), così da poter dichiarare una variabile denominata main, inizializzare e quindi stampare il suo valore.

Ovviamente:

  • l'avviso è utile poiché è abbastanza soggetto a errori;
  • puoi avere un file sorgente senza la main()funzione (librerie).

MODIFICARE

Alcuni riferimenti:

  • main non è una parola riservata (C ++ 11):

    La funzione mainnon deve essere utilizzata all'interno di un programma. Il collegamento (3.5) di mainè definito dall'implementazione. Un programma che definisce la principale cancellata o che dichiara principale di essere inline, statico constexprè mal-formata. Il nome mainnon è altrimenti riservato. [Esempio: possono essere chiamate funzioni membro, classi ed enumerazioni main, così come entità in altri spazi dei nomi. - esempio finale]

    C ++ 11 - [basic.start.main] 3.6.1.3

    [2.11 / 3] [...] alcuni identificatori sono riservati per l'uso da implementazioni C ++ e librerie standard (17.6.4.3.2) e non devono essere usati altrimenti; non è richiesta alcuna diagnostica.

    [17.6.4.3.2 / 1] Alcuni set di nomi e firme di funzioni sono sempre riservati all'implementazione:

    • Ogni nome che contiene un doppio trattino basso __ o inizia con un trattino basso seguito da una lettera maiuscola (2.12) è riservato all'implementazione per qualsiasi uso.
    • Ogni nome che inizia con un trattino basso è riservato all'implementazione per essere utilizzato come nome nello spazio dei nomi globale.
  • Parole riservate nei linguaggi di programmazione .

    Le parole riservate potrebbero non essere ridefinite dal programmatore, ma spesso le parole predefinite possono essere sovrascritte in qualche modo. Questo è il caso di main: ci sono ambiti in cui una dichiarazione che utilizza quell'identificatore ridefinisce il suo significato.


- Immagino di essere piuttosto ingannato dal fatto che (poiché è così soggetto a errori), perché questo è un avvertimento (non un errore) e perché è solo un avvertimento quando compilato come C - Certo, puoi compilare senza una main()funzione, ma non puoi collegarla come programma. Quello che sta succedendo qui è che un programma "valido" viene collegato senza un main(), solo un main.
Geoff Nixon

7
cine endlnon si trovano nello spazio dei nomi predefinito, sono nello stdspazio dei nomi. nposè un membro di std::basic_string.
AnotherParker

1
main è riservato come nome globale. Nessuna delle altre cose che hai menzionato, né main, è predefinita.
Potatoswatter

1
Vedere C ++ 14 §3.6.1 e C11 §5.1.2.2.1 per le limitazioni su ciò che mainè consentito. C ++ dice "Un'implementazione non deve predefinire la funzione principale" e C dice "L'implementazione non dichiara alcun prototipo per questa funzione."
Potatoswatter

@manlio: per favore chiarisci cosa stai citando. Per quanto riguarda la C semplice, le citazioni sono sbagliate. Quindi immagino che sia uno qualsiasi degli standard c ++ non è vero?
dhein

19

È int main;un programma C / C ++ valido?

Non è del tutto chiaro cosa sia un programma C / C ++.

È int main;un programma C valido?

Sì. Un'implementazione indipendente può accettare tale programma. mainnon deve avere alcun significato speciale in un ambiente indipendente.

E ' non è valido in un ambiente host.

È int main;un programma C ++ valido?

Idem.

Perché si blocca?

Il programma non deve avere senso nel tuo ambiente. In un ambiente indipendente, l'avvio e la chiusura del programma e il significato di mainsono definiti dall'implementazione.

Perché il compilatore mi avvisa?

Il compilatore può avvisarti di qualunque cosa gli piaccia, purché non rifiuti i programmi conformi. D'altra parte, l'avvertimento è tutto ciò che è necessario per diagnosticare un programma non conforme. Poiché questa unità di traduzione non può far parte di un programma ospitato valido, è giustificato un messaggio diagnostico.

È gccun ambiente indipendente o è un ambiente ospitato?

Sì.

gccdocumenta il -ffreestandingflag di compilazione. Aggiungilo e l'avviso scompare. Potresti usarlo durante la creazione, ad esempio, di kernel o firmware.

g++non documenta tale flag. Fornirlo sembra non avere alcun effetto su questo programma. Probabilmente è lecito ritenere che l'ambiente fornito da g ++ sia ospitato. L'assenza di diagnostica in questo caso è un bug.


17

È un avvertimento in quanto non è tecnicamente vietato. Il codice di avvio utilizzerà la posizione del simbolo "main" e vi passerà con i tre argomenti standard (argc, argv e envp). Non lo fa, e al momento del collegamento non può controllare che sia effettivamente una funzione, né che abbia quegli argomenti. Questo è anche il motivo per cui int main (int argc, char ** argv) funziona: il compilatore non conosce l'argomento envp e semplicemente non viene utilizzato, ed è caller-cleanup.

Per scherzo, potresti fare qualcosa di simile

int main = 0xCBCBCBCB;

su una macchina x86 e, ignorando gli avvisi e cose simili, non si limiterà a compilare ma anche a funzionare.

Qualcuno ha usato una tecnica simile a questa per scrivere un eseguibile (una specie di) che gira direttamente su più architetture - http://phrack.org/issues/57/17.html#article . È stato anche utilizzato per vincere il IOCCC - http://www.ioccc.org/1984/mullender/mullender.c .


1
"È un avvertimento in quanto non è tecnicamente vietato" - non è valido in C ++.
Saluti e salute. - Alf

3
"i tre argomenti standard (argc, argv e envp)" - qui forse stai parlando dello standard Posix.
Saluti e salute. - Alf

Sul mio sistema (Ubuntu 14 / x64), la seguente riga funziona con gcc:int main __attribute__ ((section (".text")))= 0xC3C3C3C3;
csharpfolk

@ Cheersandhth.-Alf I primi due sono standard, il terzo è POSIX.
dascandy

9

È un programma valido?

No.

Non è un programma in quanto non ha parti eseguibili.

È valido compilare?

Sì.

Può essere utilizzato con un programma valido?

Sì.

Non tutto il codice compilato deve essere eseguibile per essere valido. Esempi sono le librerie statiche e dinamiche.

Hai effettivamente costruito un file oggetto. Non è un eseguibile valido, tuttavia un altro programma potrebbe collegarsi all'oggetto mainnel file risultante caricandolo in fase di esecuzione.

Questo dovrebbe essere un errore?

Tradizionalmente, il C ++ consente all'utente di fare cose che possono sembrare non avere un uso valido ma che si adattano alla sintassi del linguaggio.

Voglio dire che certo, questo potrebbe essere riclassificato come errore, ma perché? A quale scopo servirebbe se l'avvertimento non ha?

Finché esiste una possibilità teorica che questa funzionalità venga utilizzata nel codice effettivo, è molto improbabile che la chiamata di un oggetto non funzione maincomporti un errore in base al linguaggio.


Crea un simbolo visibile esternamente denominato main. Come può un programma valido, che deve avere una funzione visibile esternamente denominata main, collegarsi ad esso?
Keith Thompson il

@KeithThompson Carica in fase di esecuzione. Chiarirà.
Michael Gazonda

Può perché non è in grado di distinguere tra i tipi di simboli. Il collegamento funziona perfettamente - l'esecuzione (tranne nel caso accuratamente predisposto) no.
Chris Stratton

1
@ChrisStratton: Penso che l'argomento di Keith sia che il collegamento fallisce perché il simbolo è definito in modo multiplo ... perché il "programma valido" non sarebbe un programma valido a meno che non definisca una mainfunzione.
Ben Voigt

@BenVoigt Ma se appare in una libreria, il collegamento non avrà (e probabilmente non può) fallire, perché al momento del collegamento del programma, la int main;definizione non sarà visibile.

6

Mi aggiungo alle risposte già fornite citando gli attuali standard linguistici.

È "int main;" un programma C valido?

Risposta breve (la mia opinione): solo se la tua implementazione utilizza un "ambiente di esecuzione indipendente".

Tutte le seguenti citazioni da C11

5. Ambiente

Un'implementazione traduce i file sorgente C ed esegue programmi C in due ambienti di sistema di elaborazione dati, che saranno chiamati ambiente di traduzione e ambiente di esecuzione [...]

5.1.2 Ambienti di esecuzione

Sono definiti due ambienti di esecuzione: indipendente e ospitato. In entrambi i casi, l'avvio del programma si verifica quando una funzione C designata viene chiamata dall'ambiente di esecuzione.

5.1.2.1 Ambiente indipendente

In un ambiente indipendente (in cui l'esecuzione del programma C può avvenire senza alcun vantaggio di un sistema operativo), il nome e il tipo della funzione chiamata all'avvio del programma sono definiti dall'implementazione.

5.1.2.2 Ambiente ospitato

Non è necessario fornire un ambiente ospitato, ma deve essere conforme alle seguenti specifiche, se presenti.

5.1.2.2.1 Avvio del programma

La funzione chiamata all'avvio del programma è denominata main . [...] Deve essere definito con un tipo di ritorno int e senza parametri [...] o con due parametri [...] o equivalenti o in qualche altro modo definito dall'implementazione.

Da questi, si osserva quanto segue:

  • Un programma C11 può avere un ambiente di esecuzione indipendente o ospitato ed essere valido.
  • Se ne ha una indipendente, non è necessario che esista una funzione principale.
  • Altrimenti, deve essercene uno con un valore di ritorno di tipo int .

In un ambiente di esecuzione indipendente, direi che si tratta di un programma valido che non consente l'avvio, perché non è presente alcuna funzione come richiesto in 5.1.2. In un ambiente di esecuzione ospitato, mentre il tuo codice introduce un oggetto denominato main , non può fornire un valore di ritorno, quindi direi che non è un programma valido in questo senso, sebbene si potrebbe anche argomentare come prima se il programma non lo è pensato per essere eseguito (su potrebbe voler fornire dati solo per esempio), quindi semplicemente non consente di fare proprio questo.

È "int main;" un programma C ++ valido?

Risposta breve (la mia opinione): solo se la tua implementazione utilizza un "ambiente di esecuzione indipendente".

Citazione da C ++ 14

3.6.1 Funzione principale

Un programma deve contenere una funzione globale chiamata main, che è l'inizio designato del programma. È definito dall'implementazione se un programma in un ambiente indipendente è necessario per definire una funzione principale. [...] Deve avere un tipo di ritorno di tipo int, ma per il resto il suo tipo è definito dall'implementazione. [...] Il nome main non è altrimenti riservato.

Qui, a differenza dello standard C11, si applicano meno restrizioni all'ambiente di esecuzione indipendente, poiché non viene menzionata alcuna funzione di avvio, mentre per un ambiente di esecuzione ospitato, il caso è praticamente lo stesso di C11.

Ancora una volta, direi che per il caso ospitato, il tuo codice non è un programma C ++ 14 valido, ma sono sicuro che sia per il caso indipendente.

Poiché la mia risposta considera solo l' ambiente di esecuzione , penso che entri in gioco la risposta di dasblinkenlicht, poiché la modifica del nome che si verifica nell'ambiente di traduzione avviene in anticipo. Qui, non sono così sicuro che le citazioni sopra siano osservate in modo così rigoroso.


4

Il mio punto, suppongo, è che penso davvero che questo dovrebbe essere un errore in un ambiente ospitato, eh?

L'errore è tuo. Non hai specificato una funzione denominata mainche restituisce un inte hai provato a utilizzare il tuo programma in un ambiente ospitato.

Supponiamo di avere un'unità di compilazione che definisce una variabile globale denominata main. Ciò potrebbe essere legale in un ambiente indipendente perché ciò che costituisce un programma è lasciato all'implementazione in ambienti indipendenti.

Supponiamo di avere un'altra unità di compilazione che definisce una funzione globale denominata mainche restituisce un inte non accetta argomenti. Questo è esattamente ciò di cui ha bisogno un programma in un ambiente ospitato.

Va tutto bene se usi solo la prima unità di compilazione in un ambiente indipendente e usi solo la seconda in un ambiente ospitato. Cosa succede se si utilizzano entrambi in un programma? In C ++, hai violato l'unica regola di definizione. Questo è un comportamento indefinito. In C, hai violato la regola che impone che tutti i riferimenti a un singolo simbolo devono essere coerenti; se non lo sono è un comportamento indefinito. Un comportamento indefinito è un "esci di prigione, gratis!" scheda agli sviluppatori di un'implementazione. Tutto ciò che un'implementazione fa in risposta a un comportamento indefinito è conforme allo standard. L'implementazione non deve mettere in guardia, per non parlare di rilevare, comportamenti indefiniti.

E se usi solo una di quelle unità di compilazione, ma usi quella sbagliata (che è quello che hai fatto)? In C, la situazione è chiara. La mancata definizione della funzione mainin uno dei due moduli standard in un ambiente ospitato è un comportamento indefinito. Supponi di non aver definito mainaffatto. Il compilatore / linker non deve dire nulla su questo errore. Il fatto che si lamentino è una gentilezza da parte loro. Che il programma C compilato e collegato senza errori è colpa tua, non del compilatore.

È un po 'meno chiaro in C ++ perché la mancata definizione della funzione mainin un ambiente ospitato è un errore piuttosto che un comportamento indefinito (in altre parole, deve essere diagnosticato). Tuttavia, l'unica regola di definizione in C ++ significa che i linker possono essere piuttosto stupidi. Il lavoro del linker è risolvere i riferimenti esterni e, grazie alla regola di una definizione, il linker non deve sapere cosa significano quei simboli. Hai fornito un simbolo denominato main, il linker si aspetta di vedere un simbolo denominato main, quindi tutto va bene per quanto riguarda il linker.


4

Per C finora è un comportamento definito dall'implementazione.

Come dice la ISO / IEC9899:

5.1.2.2.1 Avvio del programma

1 La funzione chiamata all'avvio del programma è denominata main. L'implementazione non dichiara alcun prototipo per questa funzione. Deve essere definito con un tipo di ritorno int e senza parametri:

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

o con due parametri (qui indicati come argc e argv, sebbene sia possibile utilizzare qualsiasi nome, poiché sono locali rispetto alla funzione in cui sono dichiarati):

int main(int argc, char *argv[]) { /* ... */ }

o equivalente; o in qualche altro modo definito dall'implementazione.


3

No, questo non è un programma valido.

Per C ++ questo è stato recentemente reso esplicitamente malformato dal rapporto sui difetti 1886: Language linkage for main () che dice:

Non sembra esserci alcuna restrizione nel dare a main () un collegamento linguistico esplicito, ma probabilmente dovrebbe essere mal formato o supportato in modo condizionale.

e parte della risoluzione includeva la seguente modifica:

Un programma che dichiara una variabile main nell'ambito globale o che dichiara il nome main con collegamento al linguaggio C (in qualsiasi spazio dei nomi) è mal formato.

Possiamo trovare questa formulazione nell'ultima bozza di standard C ++ N4527 che è la bozza C ++ 1z.

Le ultime versioni di clang e gcc ora rendono questo un errore ( guardalo dal vivo ):

error: main cannot be declared as global variable
int main;
^

Prima di questa segnalazione di difetto, si trattava di un comportamento non definito che non richiede una diagnosi. D'altra parte il codice mal formato richiede una diagnostica, il compilatore può renderlo un avviso o un errore.


Grazie per l'aggiornamento! È bello vedere che ora viene rilevato con la diagnostica del compilatore. Tuttavia, devo dire che trovo perplessi i cambiamenti nello standard C ++. (Per lo sfondo, vedere i commenti sopra relativi alla modifica del nome main().) Comprendo la logica per non consentire main()di avere una specifica di collegamento esplicita, ma non capisco che imponga che main()abbia un collegamento C ++ . Naturalmente la norma non lo fa direttamente l'indirizzo come gestire ABI leveraggio / nome mangling, ma in pratica (per esempio, con Itanium ABI) questa sarebbe mangle main()a _Z4mainv. Cosa mi sto perdendo?
Geoff Nixon

Penso che il commento di supercat lo copra. Se l'implementazione fa le sue cose prima di chiamare il main definito dall'utente, potrebbe invece scegliere facilmente di chiamare un nome alterato.
Shafik Yaghmour
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.