Cosa c’è di sbagliato in questo codice C del 1988?

Sto cercando di compilare questo pezzo di codice dal libro “The C Programming Language” (K & R). È una versione ridotta del programma UNIX wc :

 #include  #define IN 1; /* inside a word */ #define OUT 0; /* outside a word */ /* count lines, words and characters in input */ main() { int c, nl, nw, nc, state; state = OUT; nl = nw = nc = 0; while ((c = getchar()) != EOF) { ++nc; if (c == '\n') ++nl; if (c == ' ' || c == '\n' || c == '\t') state = OUT; else if (state == OUT) { state = IN; ++nw; } } printf("%d %d %d\n", nl, nw, nc); } 

E sto ricevendo il seguente errore:

 $ gcc wc.c wc.c: In function 'main': wc.c:18: error: 'else' without a previous 'if' wc.c:18: error: expected ')' before ';' token 

La seconda edizione di questo libro è del 1988 e sono abbastanza nuova per C. Forse ha a che fare con la versione del compilatore o forse sto solo dicendo un’assurdità.

Ho visto nel codice C moderno un uso diverso della funzione main :

 int main() { /* code */ return 0; } 

Si tratta di un nuovo standard o posso ancora utilizzare un main senza tipo?

Il tuo problema è con le definizioni del preprocessore di IN e OUT :

 #define IN 1; /* inside a word */ #define OUT 0; /* outside a word */ 

Nota come hai un punto e virgola finale in ognuno di questi. Quando il preprocessore li espande, il tuo codice avrà un aspetto approssimativo:

  if (c == ' ' || c == '\n' || c == '\t') state = 0;; /* <--PROBLEM #1 */ else if (state == 0;) { /* <--PROBLEM #2 */ state = 1;; 

Quel secondo punto e virgola fa sì che il else non abbia precedenti if come corrispondenza, perché non stai usando le parentesi. Quindi, rimuovere il punto e virgola dalle definizioni del preprocessore di IN e OUT .

La lezione appresa qui è che le istruzioni del preprocessore non devono finire con un punto e virgola.

Inoltre, dovresti sempre usare le parentesi!

  if (c == ' ' || c == '\n' || c == '\t') { state = OUT; } else if (state == OUT) { state = IN; ++nw; } 

Non vi è alcuna ambiguità sospensiva nel codice precedente.

Il problema principale con questo codice è che non è il codice di K & R. Include il punto e virgola dopo le definizioni dei macro, che non erano presenti nel libro, che, come altri hanno sottolineato, ne modificano il significato.

Tranne quando si effettua una modifica nel tentativo di comprendere il codice, si dovrebbe lasciarlo da solo fino a quando non lo si capisce. Puoi solo modificare in modo sicuro il codice che hai capito.

Probabilmente questo è solo un errore di battitura da parte tua, ma illustra la necessità di comprensione e attenzione ai dettagli durante la programmazione.

Non ci dovrebbero essere punti e virgola dopo i macro,

 #define IN 1 /* inside a word */ #define OUT 0 /* outside a word */ 

e probabilmente dovrebbe essere

 if (c == ' ' || c == '\n' || c == '\t') 

Le definizioni di IN e OUT dovrebbero assomigliare a questo:

 #define IN 1 /* inside a word */ #define OUT 0 /* outside a word */ 

I punti e virgola stavano causando il problema! La spiegazione è semplice: sia IN che OUT sono direttive del preprocessore, essenzialmente il compilatore sostituirà tutte le occorrenze di IN con un 1 e tutte le occorrenze di OUT con uno 0 nel codice sorgente.

Poiché il codice originale aveva un punto e virgola dopo il 1 e lo 0, quando IN e OUT sono stati sostituiti nel codice, il punto e virgola in più dopo il numero ha prodotto codice non valido, ad esempio questa riga:

 else if (state == OUT) 

Finito in questo modo:

 else if (state == 0;) 

Ma quello che volevi era questo:

 else if (state == 0) 

Soluzione: rimuovere il punto e virgola dopo i numeri nella definizione originale.

Non è esattamente un problema, ma anche la dichiarazione di main() è datata, dovrebbe essere come qualcosa del genere.

 int main(int argc, char** argv) { ... return 0; } 

Il compilatore assumerà un valore di ritorno int per una funzione senza uno, e sono sicuro che il compilatore / linker funzionerà attorno alla mancanza di dichiarazione per argc / argv e alla mancanza di valore di ritorno, ma dovrebbero esserci.

Come vedi c’è stato un problema nelle macro.

GCC ha l’opzione per fermarsi dopo la pre-elaborazione. (-E) Questa opzione è utile per vedere il risultato della pre-elaborazione. In effetti la tecnica è importante se si lavora con codice di grandi dimensioni in c / c ++. In genere i makefile avranno l’objective di fermarsi dopo la pre-elaborazione.

Per una rapida consultazione : la domanda SO copre le opzioni: come vedo un file sorgente C / C ++ dopo la preelaborazione in Visual Studio? . Inizia con vc ++, ma ha anche le opzioni gcc menzionate di seguito .

Prova ad aggiungere delle parentesi esplicite attorno ai blocchi di codice. Lo stile K & R può essere ambiguo.

Guarda la riga 18. Il compilatore ti dice dove si trova il problema.

  if (c == '\n') { ++nl; } if (c == ' ' || c == '\n' || c == '\t') { // You're missing an "=" here; should be "==" state = OUT; } else if (state == OUT) { state = IN; ++nw; } 

Un modo semplice è usare parentesi come {} per ogni if e else :

 if (c == '\n'){ ++nl; } if (c == ' ' || c == '\n' || c == '\t') { state = OUT; } else if (state == OUT) { state = IN; ++nw; } 

Come hanno sottolineato altre risposte, il problema è in #define e punto e virgola. Per minimizzare questi problemi, preferisco sempre definire costanti di numeri come const int :

 const int IN = 1; const int OUT = 0; 

In questo modo ti libererai di molti problemi e possibili problemi. È limitato da solo due cose:

  1. Il tuo compilatore deve supportare const , che nel 1988 non era generalmente vero, ma ora è supportato da tutti i compilatori comunemente usati. (AFAIK il const è “preso in prestito” dal C ++).

  2. Non è ansible utilizzare queste costanti in alcuni punti speciali in cui è necessaria una costante simile a una stringa. Ma penso che il tuo programma non sia quel caso.