Что случилось с этим кодом C 1988 года?

Я пытаюсь скомпилировать этот фрагмент кода из книги "Язык программирования C" (K и R). Это версия версии UNIX

#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);
}

И я получаю следующую ошибку:

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

Второе издание этой книги с 1988 года, и я довольно новичок в C. Возможно, это связано с версией компилятора или, может быть, я просто говорю глупости.

Я видел в современном C-коде другое использование функции main:

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

Является ли это новым стандартом или могу ли я использовать основной тип?

Ответ 1

Ваша проблема связана с вашими определениями препроцессора IN и OUT:

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

Обратите внимание, как у вас есть конечная точка с запятой в каждом из них. Когда препроцессор расширяет их, ваш код будет выглядеть примерно так:

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

Эта вторая точка с запятой заставляет else не иметь предыдущего if в качестве соответствия, потому что вы не используете фигурные скобки. Итак, удалите точки с запятой из определений препроцессора IN и OUT.

Урок, извлеченный здесь, состоит в том, что инструкции препроцессора не должны заканчиваться точкой с запятой.

Кроме того, вы всегда должны использовать фигурные скобки!

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

В вышеприведенном коде не существует неопределенной погрешности else.

Ответ 2

Основная проблема с этим кодом заключается в том, что это не код из K & R. Он содержит точки с запятой после определений макросов, которых нет в книге, которые, как указывали другие, меняют смысл.

За исключением случаев, когда вы пытаетесь понять код, вы должны оставить его в покое, пока не поймете это. Вы можете только безопасно изменять код, который понимаете.

Вероятно, это была опечатка с вашей стороны, но она иллюстрирует необходимость понимания и внимания к деталям при программировании.

Ответ 3

После макросов не должно быть точек с запятой,

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

и, вероятно, это будет

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

Ответ 4

Определения IN и OUT должны выглядеть следующим образом:

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

Точки с запятой вызывают проблему! Объяснение простое: как IN, так и OUT являются препроцессорными директивами, по сути, компилятор заменит все вхождения IN одним и всеми вхождениями OUT с 0 в исходном коде.

Поскольку исходный код имел точку с запятой после 1 и 0, когда IN и OUT были заменены в коде, дополнительная точка с запятой после номера вызывала неверный код, например, эту строку:

else if (state == OUT)

Завершен следующим образом:

else if (state == 0;)

Но вы хотели, чтобы это было:

else if (state == 0)

Решение: удалите точку с запятой после чисел в исходном определении.

Ответ 5

Не совсем проблема, но декларация main() также датирована, она должна быть чем-то подобной.

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

Компилятор примет значение int для функции w/o, и я уверен, что компилятор/компоновщик будет работать вокруг отсутствия объявления для argc/argv и отсутствия возвращаемого значения, но они должны быть есть.

Ответ 6

Как вы видите, в макросах возникла проблема.

GCC имеет опцию остановка после предварительной обработки. (-E) Эта опция полезна, чтобы увидеть результат предварительной обработки. На самом деле техника важна, если вы работаете с большой базой кода в c/С++. Обычно make файлы будут иметь цель для остановки после предварительной обработки.

Для быстрой справки: вопрос SO охватывает варианты - Как увидеть исходный файл C/С++ после предварительной обработки в Visual Studio?. Он начинается с vС++, но также имеет параметры gcc, указанные ниже.

Ответ 7

Попробуйте добавить явные скобки вокруг кодовых блоков. Стиль K & R может быть неоднозначным.

Посмотрите на строку 18. Компилятор сообщает вам, где проблема.

    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;
    }

Ответ 8

Простым способом является использование скобок типа {} для каждого if и else:

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

Ответ 9

Как указывалось в других ответах, проблема заключается в #define и точках с запятой. Чтобы свести к минимуму эти проблемы, я всегда предпочитаю определять числовые константы как const int:

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

Таким образом вы избавляетесь от многих проблем и возможных проблем. Он ограничен только двумя вещами:

  • Ваш компилятор должен поддерживать const - который в 1988 году, как правило, не был прав, но теперь он поддерживается всеми обычно используемыми компиляторами. (AFAIK const "заимствован" из С++.)

  • Вы не можете использовать эти константы в некоторых специальных местах, где вам понадобится строка-подобная константа. Но я думаю, что ваша программа не в этом.