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


94

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

#include <stdio.h>

#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 ricevo 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 nuovo per C. Forse ha a che fare con la versione del compilatore o forse sto solo dicendo sciocchezze.

Ho visto nel codice C moderno un uso diverso della mainfunzione:

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

È un nuovo standard o posso ancora utilizzare un main senza tipo?


4
Non una risposta, ma un altro pezzo di codice a guardare più da vicino, || c = '\t'). Sembra lo stesso dell'altro codice su quella riga?
user7116

58
32 voti positivi per una domanda di debug + errore di battitura ?!
Gare di leggerezza in orbita

37
@ TomalakGeret'kal: sai, le cose vecchie sono più apprezzate (vino, dipinti, codice C)
Sergio Tulentsev

16
@ César: ho il diritto di esprimere la mia opinione e ti ringrazio per non cercare di censurarla. In effetti, sì, questo non è un sito web per eseguire il debug del codice e risolvere i tuoi errori tipografici, che sono problemi "localizzati" che non aiuteranno mai nessun altro. È un sito Web per domande sui linguaggi di programmazione , non per eseguire il debug di base e il lavoro di riferimento per te. Il livello di abilità è completamente irrilevante. Leggi le FAQ e forse anche questa meta domanda .
Gare di leggerezza in orbita il

11
@ TomalakGeret'kal ovviamente puoi esprimere la tua opinione e non censurerò il tuo commento nonostante sia poco costruttivo. Ho già letto le FAQ. Sono un programmatore entusiasta che mi
César

Risposte:


247

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

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

Nota come hai un punto e virgola finale in ciascuno di questi. Quando il preprocessore li espande, il tuo codice apparirà più o meno come:

    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 elsenon abbia precedenti ifcome corrispondenza, perché non stai usando le parentesi graffe. Quindi, rimuovere i punti e virgola dalle definizioni del preprocessore di INe OUT.

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

Inoltre, dovresti sempre usare le parentesi graffe!

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

Non ci sono elseambiguità sospese nel codice precedente.


8
Per chiarezza, il problema non è la spaziatura, sono i punti e virgola. Non sono necessari nelle istruzioni del preprocessore.
Dan

@ Dan grazie per il chiarimento! E il punto e virgola era davvero il problema! Grazie ragazzi!
César

2
@ César: sei il benvenuto. Si spera che il suggerimento incoraggiante ti tenga fuori dai guai in futuro, sicuramente mi ha aiutato!
user7116

5
@ César: è anche una buona idea abituarsi a mettere parentesi attorno alle macro poiché generalmente si desidera che la macro venga valutata per prima. In questo caso non ha importanza poiché il valore è un singolo token, ma tralasciare le parentesi può portare a risultati imprevisti durante la definizione di un'espressione.
stile

7
"non ne ho bisogno"! = "non dovresti averli". il primo è sempre vero; il secondo dipende dal contesto ed è la questione più pertinente in questo scenario.
Gare di leggerezza in orbita il

63

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

Tranne quando si effettua una modifica nel tentativo di comprendere il codice, è necessario lasciarlo in pace finché non lo si capisce. Puoi modificare in sicurezza solo il codice che conosci.

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


9
Il tuo consiglio non è particolarmente costruttivo per qualcuno che impara a programmare. Modificare il codice è esattamente il modo in cui comprendi i dettagli della programmazione.
user7116

12
@sixlettervariables: e quando lo fai, dovresti sapere quali modifiche hai apportato e apportare il minor numero di modifiche possibile. Se l'OP avesse apportato le modifiche deliberatamente e fatto il minor numero di modifiche possibile, probabilmente non avrebbe posto questa domanda, poiché gli sarebbe stato chiaro cosa stava succedendo. Avrebbe cambiato la macro per IN, senza errori e poi la macro per OUT con i due errori, il secondo dei quali lamenterebbe il punto e virgola che aveva appena aggiunto.
jmoreno

5
Sembra che, a meno che tu non commetta l'errore di includere un punto e virgola alla fine di una riga di direttiva del preprocessore, probabilmente non sapresti di non includerli. Potresti prenderlo al valore nominale, potresti leggere un sacco di codice e notare che non sembrano mai essere lì. Oppure, l'OP potrebbe sbagliare includendoli, chiedere dell'errore "bizzarro" e scoprire: oops, nessun punto e virgola richiesto per le direttive del preprocessore! Questa è programmazione, non un episodio di Scared Straight.
user7116

14
@sixlettervariables: Sì, ma quando il codice non funziona, il primo ovvio passo è andare "oh, ok, allora quello che ho cambiato senza alcun motivo dal codice scritto in un libro dall'inventore di C, è stato probabilmente il problema. Allora lo annullerò. "
Gare di leggerezza in orbita


34

Non dovrebbero esserci punti e virgola dopo le macro,

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

e probabilmente dovrebbe esserlo

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

Grazie, il punto e virgola era il problema. Il secondo è stato un errore di battitura!
César

21
La prossima volta incolla il codice esatto che utilizzi, direttamente dal tuo editor di testo.
Gare di leggerezza in orbita il

@ TomalakGeret'kal beh non l'ho fatto e lo farò, ma come l'hai trovato?
onemach

1
@onemach: hai detto che ;era un errore di battitura che non ha influenzato il problema, il che significa un errore di battitura nella tua domanda piuttosto che nel codice che hai effettivamente usato.
Gare di leggerezza in orbita

24

Le definizioni di IN e OUT dovrebbero assomigliare a questa:

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

Il punto e virgola stava 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 l'1 e lo 0, quando IN e OUT venivano sostituiti nel codice, il punto e virgola in più dopo il numero produceva un codice non valido, ad esempio questa riga:

else if (state == OUT)

Finì per assomigliare a questo:

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.


8

Come vedi c'era un problema nelle macro.

GCC ha l'opzione per l' arresto dopo la pre-elaborazione.(-E) Questa opzione è utile per vedere il risultato della pre-elaborazione. In effetti la tecnica è importante se stai lavorando con una grande base di codice in c / c ++. Tipicamente i makefile avranno un obiettivo da fermare dopo la pre-elaborazione.

Per riferimento rapido: la domanda SO copre le opzioni: come faccio a visualizzare un file sorgente C / C ++ dopo la pre-elaborazione in Visual Studio? . Inizia con vc ++, ma ha anche le opzioni gcc menzionate di seguito .


7

Non è esattamente un problema, ma anche la dichiarazione di main()è datata, dovrebbe essere 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 aggirerà la mancanza di dichiarazione per argc / argv e la mancanza di valore di ritorno, ma dovrebbero essere lì.


3
Questo è un buon libro, uno degli unici due libri che valgono la pena su C per quanto ne so. Sono abbastanza sicuro che le edizioni più recenti siano compatibili con ANSI C (probabilmente pre C99 ANSI C). L'altro libro degno di nota su C è Expert C Programming Deep C Secrets di Peter van der Linden.
Bill

Non ho mai detto che lo fosse. Mi è stato semplicemente detto che per allinearlo al modo in cui si fanno le cose oggi, quella principale dovrebbe essere cambiata.
Bill

4

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

Guarda la riga 18. Il compilatore ti sta dicendo dov'è 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;
    }

2
Grazie! In realtà, il codice ha funzionato senza le parentesi graffe nel secondo se :)
César

5
+1. Non solo ambiguo ma alquanto pericoloso. Quando (se) aggiungi una riga al tuo ifblocco in seguito, se dimentichi di aggiungere le parentesi graffe perché il tuo blocco ora è più di una riga, può volerci un po 'per eseguire il debug di
quell'errore

8
@ The111 Non mi è mai successo. Continuo a non credere che questo sia un vero problema. Uso lo stile senza parentesi graffe da oltre un decennio, non ho mai dimenticato una volta di aggiungere le parentesi graffe durante l'espansione del corpo di un blocco.
Konrad Rudolph,

1
@ The111: In questo caso alcuni collaboratori di SO hanno impiegato una manciata di minuti: P E se sei un programmatore capace di aggiungere dichiarazioni a una ifclausola e "dimenticare" di aggiornare le parentesi graffe, allora, beh, non lo sei un ottimo programmatore.
Gare di leggerezza in orbita

3

Un modo semplice è usare parentesi come {} per ciascuno ife else:

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

2

Come sottolineato da altre risposte, il problema è in #definee punto e virgola. Per ridurre al minimo questi problemi preferisco sempre definire le costanti numeriche come const int:

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

In questo modo elimini molti problemi e possibili problemi. È limitato da due sole cose:

  1. Il tuo compilatore deve supportare const, cosa che nel 1988 non era generalmente vera, ma ora è supportata da tutti i compilatori comunemente usati. (AFAIK il constè "preso in prestito" da C ++.)

  2. Non puoi usare queste costanti in alcuni punti speciali in cui avresti bisogno di una costante simile a una stringa. Ma penso che il tuo programma non sia questo il caso.


Un'alternativa che preferisco sono le enumerazioni: possono essere utilizzate in luoghi speciali (come le dichiarazioni di array) che const intnon possono in C.
Michael Burr
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.