Co jest nie tak z tym kodem C z 1988 roku?

94

Próbuję skompilować ten fragment kodu z książki „Język programowania C” (K&R). Jest to podstawowa wersja programu 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);
}

Otrzymuję następujący błąd:

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

Drugie wydanie tej książki pochodzi z 1988 roku i jestem całkiem nowy w C. Może ma to coś wspólnego z wersją kompilatora, a może po prostu mówię bzdury.

Widziałem we współczesnym kodzie C inne użycie mainfunkcji:

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

Czy to nowy standard, czy nadal mogę używać głównego typu bez typu?

César
źródło
4
Nie odpowiedź, ale kolejny fragment kodu, któremu należy się bliżej przyjrzeć || c = '\t'). Czy to wygląda tak samo jak inny kod w tej linii?
user7116
58
32 głosy za za debugowanie + literówka ?!
Wyścigi lekkości na orbicie
37
@ TomalakGeret'kal: wiesz, stare rzeczy są bardziej cenione (wino, obrazy, kod C)
Sergio Tulentsev
16
@ César: Mam pełne prawo wyrazić swoją opinię i dziękuję, że nie próbujesz jej cenzurować. Tak się składa, że ​​tak, to nie jest witryna do debugowania kodu i rozwiązywania błędów typograficznych, które są „zlokalizowanymi” problemami, które nigdy nikomu nie pomogą. To witryna internetowa zawierająca pytania dotyczące języków programowania , a nie do wykonywania podstawowych czynności związanych z debugowaniem i referencjami. Poziom umiejętności jest całkowicie nieistotny. Przeczytaj FAQ, a być może także to meta pytanie .
Wyścigi lekkości na orbicie
11
@ TomalakGeret'kal oczywiście możesz wyrazić swoją opinię i nie będę cenzurować Twojego komentarza, mimo że jest on niekonstruktywny. Przeczytałem już FAQ. Jestem zapalonym programistą pytającym o rzeczywisty problem, z którym się zmagam
César

Odpowiedzi:

247

Twój problem dotyczy definicji preprocesora INi OUT:

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

Zwróć uwagę, że w każdym z nich znajduje się końcowy średnik. Gdy preprocesor je rozwinie, Twój kod będzie wyglądał mniej więcej tak:

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

Ten drugi średnik powoduje, że elsenie ma poprzedniej wartości ifjako dopasowania, ponieważ nie używasz nawiasów klamrowych. Dlatego usuń średniki z definicji preprocesora INi OUT.

Wyciągnięta tutaj lekcja jest taka, że instrukcje preprocesora nie muszą kończyć się średnikiem.

Zawsze powinieneś używać szelek!

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

W elsepowyższym kodzie nie ma zawieszania się - niejednoznaczności.

user7116
źródło
8
Dla jasności problemem nie są odstępy, ale średniki. Nie potrzebujesz ich w instrukcjach preprocesora.
Dan,
@Dan dzięki za wyjaśnienie! I rzeczywiście problemem były średniki! Dzięki chłopaki!
César
2
@ César: nie ma za co. Mam nadzieję, że ta orzeźwiająca sugestia uchroni Cię przed kłopotami w przyszłości, z pewnością mi pomogła!
user7116
5
@ César: Dobrym pomysłem jest również przyzwyczajenie się do umieszczania nawiasów wokół makr, ponieważ zazwyczaj chcesz, aby makro było oceniane jako pierwsze. W tym przypadku nie ma to znaczenia, ponieważ wartość jest pojedynczym tokenem, ale pominięcie parenów może prowadzić do nieoczekiwanych wyników podczas definiowania wyrażenia.
styfle
7
„nie potrzebuję ich”! = „nie powinno ich mieć”. to pierwsze jest zawsze prawdziwe; ta ostatnia jest zależna od kontekstu i jest bardziej istotną kwestią w tym scenariuszu.
Wyścigi lekkości na orbicie
63

Głównym problemem przy tego kodu jest to, że nie kod z K i R. Zawiera średniki po definicjach makr, których nie było w książce, co, jak zauważyli inni, zmienia znaczenie.

Z wyjątkiem sytuacji, gdy wprowadzasz zmiany w celu zrozumienia kodu, powinieneś zostawić to w spokoju, dopóki go nie zrozumiesz. Możesz bezpiecznie modyfikować tylko kod, który rozumiesz.

Prawdopodobnie była to tylko literówka z Twojej strony, ale ilustruje potrzebę zrozumienia i zwracania uwagi na szczegóły podczas programowania.

jmoreno
źródło
9
Twoja rada nie jest zbyt konstruktywna dla kogoś, kto uczy się programowania. Modyfikowanie kodu to właśnie sposób rozumienia szczegółów programowania.
user7116
12
@sixlettervariables: Robiąc to, powinieneś wiedzieć, jakie zmiany wprowadziłeś i wprowadzić jak najmniej zmian. Gdyby PO celowo wprowadził zmiany i dokonał jak najmniejszej liczby zmian, prawdopodobnie nie zadałby tego pytania, ponieważ byłoby dla niego jasne, co się dzieje. Zmieniłby makro na IN, bez błędów, a następnie makro na OUT z dwoma błędami, z których drugi byłby narzekaniem na średnik, który właśnie dodał.
jmoreno
5
Wygląda na to, że jeśli nie pomylisz się, dodając średnik na końcu wiersza dyrektywy preprocesora, prawdopodobnie nie będziesz wiedział, że ich nie uwzględnisz. Możesz wziąć to za dobrą monetę, możesz przeczytać dużo kodu i zauważyć, że wydaje się, że nigdy go tam nie ma. Lub OP może zepsuć je, włączając je, zapytać o „dziwaczny” błąd i dowiedzieć się: Ups, w dyrektywach preprocesora nie są wymagane średniki! To jest programowanie, a nie odcinek Scared Straight.
user7116
14
@sixlettervariables: Tak, ale kiedy kod nie działa, oczywistym pierwszym krokiem jest pójście "o, ok, to co zmieniłem bez żadnego powodu z kodu napisanego w książce wynalazcy C, to prawdopodobnie problem. Po prostu to cofnę ”.
Wyścigi lekkości na orbicie,
34

Po makrach nie powinno być średników,

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

i prawdopodobnie powinno być

if (c == ' ' || c == '\n' || c == '\t')
onemach
źródło
Dzięki, problem stanowiły średniki. Drugi był literówką!
César
21
Następnym razem wklej dokładnie kod, którego używasz, bezpośrednio z edytora tekstu.
Wyścigi lekkości na orbicie
@ TomalakGeret'kal dobrze nie zrobiłem i będę, ale jak znalazłeś?
onemach
1
@onemach: Powiedziałeś, że ;to literówka, która nie wpłynęła na problem, co oznacza literówkę w Twoim pytaniu, a nie w kodzie, którego faktycznie użyłeś.
Wyścigi lekkości na orbicie
24

Definicje IN i OUT powinny wyglądać następująco:

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

Problem był spowodowany średnikami! Wyjaśnienie jest proste: zarówno IN, jak i OUT są dyrektywami preprocesora, zasadniczo kompilator zamieni wszystkie wystąpienia IN na 1, a wszystkie wystąpienia OUT na 0 w kodzie źródłowym.

Ponieważ oryginalny kod miał średnik po 1 i 0, gdy IN i OUT zostały zastąpione w kodzie, dodatkowy średnik po numerze generował nieprawidłowy kod, na przykład ten wiersz:

else if (state == OUT)

Skończyło się tak:

else if (state == 0;)

Ale chciałeś tego:

else if (state == 0)

Rozwiązanie: usuń średnik po liczbach w oryginalnej definicji.

Óscar López
źródło
8

Jak widać, wystąpił problem w makrach.

GCC ma opcję zatrzymania po wstępnym przetworzeniu. (-E) Ta opcja jest przydatna, aby zobaczyć wynik przetwarzania wstępnego. W rzeczywistości technika ta jest ważna, jeśli pracujesz z dużą bazą kodu w języku c / c ++. Zazwyczaj pliki makefile będą miały cel zatrzymania po wstępnym przetworzeniu.

Dla skróconej informacji: pytanie SO dotyczy opcji - Jak wyświetlić plik źródłowy C / C ++ po wstępnym przetworzeniu w programie Visual Studio? . Zaczyna się od vc ++, ale ma również opcje gcc wymienione poniżej .

Jayan
źródło
7

Nie do końca problem, ale deklaracja main()też jest datowana, powinno być coś takiego.

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

Kompilator przyjmie wartość zwracaną int dla funkcji bez jednej i jestem pewien, że kompilator / konsolidator obejdzie brak deklaracji dla argc / argv i brak wartości zwracanej, ale powinny tam być.

Rachunek
źródło
3
To dobra książka - o ile wiem, jedna z dwóch wartych uwagi książek na temat C. Jestem prawie pewien, że nowsze wersje są zgodne z ANSI C (prawdopodobnie przed C99 ANSI C). Inną wartą uwagi książką na temat C jest Expert C Programming Deep C Secrets autorstwa Petera van der Lindena.
Bill
Nigdy nie powiedziałem, że tak. Po prostu powiedziano mi, że aby dostosować to do obecnego stanu rzeczy, należy zmienić tę zasadę.
Bill
4

Spróbuj dodać wyraźne nawiasy klamrowe wokół bloków kodu. Styl K&R może być niejednoznaczny.

Spójrz na wiersz 18. Kompilator mówi ci, gdzie jest problem.

    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
źródło
2
Dzięki! Właściwie kod działał bez nawiasów klamrowych w drugim if :)
César
5
+1. Nie tylko niejednoznaczne, ale nieco niebezpieczne. Kiedy (jeśli) dodasz linię do swojego ifbloku później, jeśli zapomnisz dodać nawiasy klamrowe, ponieważ Twój blok zawiera teraz więcej niż jedną linię, debugowanie tego błędu może zająć trochę czasu ...
The111
8
@ The111 Nigdy, przenigdy nie zdarzyło mi się. Nadal nie wierzę, że to prawdziwy problem. Używam stylu bez klamry od ponad dekady, ani razu nie zapomniałem dodać nawiasów podczas rozszerzania bryły bloku.
Konrad Rudolph
1
@ The111: W tym przypadku kilku współautorom SO zajęło kilka minut: P A jeśli jesteś programistą, który potrafi dodawać instrukcje do ifklauzuli i „zapominać” o aktualizowaniu nawiasów klamrowych, to nie jesteś bardzo dobry programista.
Wyścigi lekkości na orbicie
3

Prostym sposobem jest użycie nawiasów, takich jak {}, dla każdego ifi else:

if (c == '\n'){
    ++nl;
}
if (c == ' ' || c == '\n' || c == '\t')
{
    state = OUT;
}
else if (state == OUT) {
    state = IN;
    ++nw;
}
Nauman Khalid
źródło
2

Jak wskazywały inne odpowiedzi, problem znajduje się w #defineśrednikach. Aby zminimalizować te problemy, zawsze wolę definiować stałe liczbowe jako const int:

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

W ten sposób pozbywasz się wielu problemów i możliwych problemów. Ograniczają go tylko dwie rzeczy:

  1. Twój kompilator musi obsługiwać const- co w 1988 roku generalnie nie było prawdą, ale teraz jest obsługiwany przez wszystkie powszechnie używane kompilatory. (AFAIK constjest „zapożyczony” z C ++.)

  2. Nie możesz użyć tych stałych w niektórych specjalnych miejscach, w których potrzebujesz stałej podobnej do łańcucha. Ale myślę, że twój program nie jest taki.

Al Kepp
źródło
Alternatywą Wolę to teksty stałe - mogą być stosowane w miejscach szczególnych (np deklaracji tablicy), że const intnie można w C
Michael Burr