Was ist los mit diesem 1988 C-Code?

94

Ich versuche, diesen Code aus dem Buch "The C Programming Language" (K & R) zu kompilieren. Es ist eine reine Version des UNIX-Programms 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);
}

Und ich bekomme folgenden Fehler:

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

Die 2. Ausgabe dieses Buches stammt aus dem Jahr 1988 und ich bin ziemlich neu in C. Vielleicht hat es mit der Compiler-Version zu tun, oder vielleicht spreche ich nur Unsinn.

Ich habe im modernen C-Code eine andere Verwendung der mainFunktion gesehen:

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

Ist dies ein neuer Standard oder kann ich trotzdem eine typlose Hauptleitung verwenden?

César
quelle
4
Keine Antwort, sondern ein weiterer Code, den man sich genauer ansehen sollte || c = '\t'). Scheint das der gleiche zu sein wie der andere Code in dieser Zeile?
user7116
58
32 Upvotes für eine Debugging + Tippfehler Frage?!
Leichtigkeitsrennen im Orbit
37
@ TomalakGeret'kal: Sie wissen, altes Zeug wird mehr geschätzt (Wein, Gemälde, C-Code)
Sergio Tulentsev
16
@ César: Ich habe das Recht, meine Meinung zu äußern, und ich danke Ihnen, dass Sie nicht versuchen, sie zu zensieren. Ja, dies ist keine Website zum Debuggen Ihres Codes und zum Lösen Ihrer Tippfehler. Hierbei handelt es sich um "lokalisierte" Probleme, die niemand anderem helfen werden. Es ist eine Website für Fragen zu Programmiersprachen , nicht für grundlegende Debugging- und Nachschlagewerke. Die Fähigkeitsstufe ist völlig irrelevant. Lesen Sie die FAQ und vielleicht auch diese Meta-Frage .
Leichtigkeitsrennen im Orbit
11
@ TomalakGeret'kal natürlich kannst du deine Meinung äußern und ich werde deinen Kommentar nicht zensieren, obwohl er nicht konstruktiv ist. Ich habe die FAQ bereits gelesen. Ich bin ein begeisterter Programmierer , der nach einem tatsächlichen Problem
César

Antworten:

247

Ihr Problem liegt in Ihren Präprozessordefinitionen von INund OUT:

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

Beachten Sie, wie Sie in jedem von ihnen ein abschließendes Semikolon haben. Wenn der Präprozessor sie erweitert, sieht Ihr Code ungefähr so ​​aus:

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

Dieses zweite Semikolon bewirkt else, dass das nicht ifals Übereinstimmung vorhanden ist, da Sie keine geschweiften Klammern verwenden. Entfernen Sie also die Semikolons aus den Präprozessordefinitionen von INund OUT.

Die hier gewonnene Erkenntnis ist, dass Präprozessoranweisungen nicht mit einem Semikolon enden müssen.

Außerdem sollten Sie immer Zahnspangen verwenden!

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

elseDer obige Code enthält keine hängenden Mehrdeutigkeiten.

user7116
quelle
8
Aus Gründen der Klarheit ist das Problem nicht der Abstand, sondern die Semikolons. Sie benötigen sie nicht in Präprozessoranweisungen.
Dan
@ Dan danke für die Klarstellung! Und die Semikolons waren in der Tat das Problem! Danke Leute!
César
2
@ César: Gern geschehen. Der spannende Vorschlag wird Sie hoffentlich in Zukunft aus Ärger heraushalten, hat mir sicherlich geholfen!
user7116
5
@ César: Es ist auch eine gute Idee, sich daran zu gewöhnen, Makros in Klammern zu setzen, da das Makro im Allgemeinen zuerst ausgewertet werden soll. In diesem Fall spielt es keine Rolle, da der Wert ein einzelnes Token ist. Das Weglassen von Parens kann jedoch zu unerwarteten Ergebnissen beim Definieren eines Ausdrucks führen.
Styfle
7
"brauche sie nicht"! = "sollte sie nicht haben". Ersteres ist immer wahr; Letzteres ist kontextabhängig und in diesem Szenario das relevantere Problem.
Leichtigkeitsrennen im Orbit
63

Das Hauptproblem bei diesem Code ist, dass es sich nicht um den Code von K & R handelt. Es enthält Semikolons nach den Makrodefinitionen, die im Buch nicht vorhanden waren und die, wie andere hervorgehoben haben, die Bedeutung ändern.

Außer wenn Sie Änderungen vornehmen, um den Code zu verstehen, sollten Sie ihn in Ruhe lassen, bis Sie ihn verstanden haben. Sie können Code, den Sie verstehen, nur sicher ändern.

Dies war wahrscheinlich nur ein Tippfehler von Ihrer Seite, aber es zeigt die Notwendigkeit des Verstehens und der Aufmerksamkeit für Details beim Programmieren.

jmoreno
quelle
9
Ihr Rat ist für jemanden, der das Programmieren lernt, nicht besonders konstruktiv. Durch das Ändern von Code verstehen Sie genau die Details der Programmierung.
user7116
12
@sixlettervariables: Und wenn Sie dies tun, sollten Sie wissen, welche Änderungen Sie vorgenommen haben, und so wenig Änderungen wie möglich vornehmen. Wenn das OP die Änderungen absichtlich vorgenommen und so wenig wie möglich geändert hätte, hätte er diese Frage wahrscheinlich nicht gestellt, da ihm klar gewesen wäre, was los war. Er hätte das Makro für IN ohne Fehler und dann das Makro für OUT mit den beiden Fehlern geändert, von denen der zweite sich über das Semikolon beschweren würde, das er gerade hinzugefügt hatte.
jmoreno
5
Es scheint, als würden Sie wahrscheinlich nicht wissen, dass Sie sie nicht einfügen sollen, wenn Sie nicht den Fehler machen, ein Semikolon am Ende einer Präprozessor-Direktivenzeile einzufügen. Sie könnten es zum Nennwert nehmen, Sie könnten viel Code lesen und feststellen, dass sie niemals da zu sein scheinen. Oder das OP könnte es vermasseln, indem es sie einbezieht, nach dem "bizarren" Fehler fragt und herausfindet: Hoppla, für Präprozessor-Direktiven sind keine Semikolons erforderlich! Dies ist Programmierung, keine Episode von Scared Straight.
user7116
14
@sixlettervariables: Ja, aber wenn der Code nicht funktioniert, ist der offensichtliche erste Schritt zu gehen: "Oh, ok, dann war das, was ich ohne Grund geändert habe, wahrscheinlich der Code, den der Erfinder von C in einem Buch geschrieben hat Ich werde das dann einfach rückgängig machen. "
Leichtigkeitsrennen im Orbit
3
Lassen Sie uns diese Diskussion im Chat fortsetzen
user7116
34

Nach den Makros sollten keine Semikolons stehen.

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

und es sollte wahrscheinlich sein

if (c == ' ' || c == '\n' || c == '\t')
onemach
quelle
Danke, die Semikolons waren das Problem. Der 2. war ein Tippfehler!
César
21
Fügen Sie beim nächsten Mal den genauen Code, den Sie verwenden, direkt aus Ihrem Texteditor ein.
Leichtigkeitsrennen im Orbit
@ TomalakGeret'kal Nun, ich habe es nicht getan und ich werde es tun, aber wie hast du es gefunden?
Onemach
1
@onemach: Sie sagten, dies ;sei ein Tippfehler, der das Problem nicht beeinträchtige. Dies bedeutet einen Tippfehler in Ihrer Frage und nicht in dem Code, den Sie tatsächlich verwendet haben.
Leichtigkeitsrennen im Orbit
24

Die Definitionen von IN und OUT sollten folgendermaßen aussehen:

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

Die Semikolons verursachten das Problem! Die Erklärung ist einfach: Sowohl IN als auch OUT sind Präprozessoranweisungen. Im Wesentlichen ersetzt der Compiler alle Vorkommen von IN durch eine 1 und alle Vorkommen von OUT durch eine 0 im Quellcode.

Da der ursprüngliche Code nach der 1 und der 0 ein Semikolon hatte, erzeugte das zusätzliche Semikolon nach der Nummer beim Ersetzen von IN und OUT im Code einen ungültigen Code, zum Beispiel diese Zeile:

else if (state == OUT)

Am Ende sah es so aus:

else if (state == 0;)

Aber was Sie wollten, war Folgendes:

else if (state == 0)

Lösung: Entfernen Sie das Semikolon nach den Zahlen in der ursprünglichen Definition.

Óscar López
quelle
8

Wie Sie sehen, gab es ein Problem mit Makros.

GCC hat die Option , nach der Vorverarbeitung anzuhalten. (-E) Diese Option ist nützlich, um das Ergebnis der Vorverarbeitung anzuzeigen. Tatsächlich ist die Technik wichtig, wenn Sie mit einer großen Codebasis in c / c ++ arbeiten. In der Regel haben Makefiles ein Ziel, das nach der Vorverarbeitung gestoppt werden soll.

Zum schnellen Nachschlagen: Die SO-Frage behandelt die Optionen: Wie wird eine C / C ++ - Quelldatei nach der Vorverarbeitung in Visual Studio angezeigt? . Es beginnt mit vc ++, hat aber auch die unten genannten gcc-Optionen .

Jayan
quelle
7

Nicht gerade ein Problem, aber die Erklärung von main()ist auch datiert, es sollte so etwas sein.

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

Der Compiler wird einen int-Rückgabewert für eine Funktion ohne einen annehmen, und ich bin sicher, dass der Compiler / Linker das Fehlen einer Deklaration für argc / argv und das Fehlen eines Rückgabewerts umgehen wird, aber sie sollten vorhanden sein.

Rechnung
quelle
3
Das ist ein gutes Buch - meines Wissens eines der beiden wertvollsten Bücher über C. Ich bin mir ziemlich sicher, dass neuere Editionen ANSI C-kompatibel sind (wahrscheinlich vor ANS C C vor C99). Das andere wertvolle Buch über C ist Expert C Programming Deep C Secrets von Peter van der Linden.
Bill
Ich habe es nie gesagt. Ich wurde lediglich kommentiert, dass, um es mit der heutigen Arbeitsweise in Einklang zu bringen, diese Hauptsache geändert werden sollte.
Bill
4

Fügen Sie explizite Klammern um Codeblöcke ein. Der K & R-Stil kann mehrdeutig sein.

Schauen Sie sich Zeile 18 an. Der Compiler teilt Ihnen mit, wo das Problem liegt.

    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;
    }
Duffymo
quelle
2
Vielen Dank! Eigentlich funktionierte der Code ohne die geschweiften Klammern im zweiten if :)
César
5
+1. Nicht nur mehrdeutig, sondern auch etwas gefährlich. Wenn Sie später eine Zeile zu Ihrem ifBlock hinzufügen und vergessen, die geschweiften Klammern hinzuzufügen, da Ihr Block jetzt mehr als eine Zeile enthält, kann es eine Weile dauern, bis dieser Fehler
behoben ist
8
@ The111 Mir ist noch nie etwas passiert. Ich glaube immer noch nicht, dass dies ein echtes Problem ist. Ich verwende seit über einem Jahrzehnt den Stil ohne Klammern. Ich habe nie vergessen, die Klammern beim Erweitern des Blockkörpers hinzuzufügen.
Konrad Rudolph
1
@ The111: In diesem Fall haben einige SO-Mitarbeiter eine Handvoll Minuten gebraucht: P Und wenn Sie ein Programmierer sind, der in der Lage ist, einer ifKlausel Anweisungen hinzuzufügen und die Klammern zu "vergessen", dann sind Sie es nicht ein sehr guter Programmierer.
Leichtigkeitsrennen im Orbit
3

Eine einfache Möglichkeit besteht darin, Klammern wie {} für jedes zu verwenden ifund else:

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

Wie andere Antworten betonten, liegt das Problem in #defineund Semikolons. Um diese Probleme zu minimieren, definiere ich Zahlenkonstanten immer wie folgt const int:

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

Auf diese Weise werden Sie viele Probleme und mögliche Probleme los. Es ist nur durch zwei Dinge begrenzt:

  1. Ihr Compiler muss unterstützen const- was 1988 im Allgemeinen nicht der Fall war, aber jetzt wird es von allen häufig verwendeten Compilern unterstützt. (AFAIK das constist von C ++ "entlehnt".)

  2. Sie können diese Konstanten nicht an bestimmten Stellen verwenden, an denen Sie eine stringähnliche Konstante benötigen würden. Aber ich denke, Ihr Programm ist nicht so.

Al Kepp
quelle
Eine Alternative, die ich bevorzuge, sind Aufzählungen - sie können an speziellen Stellen (wie Array-Deklarationen) verwendet werden, const intdie in C nicht möglich sind.
Michael Burr