Что не так с кодом C 1988 года?

94

Я пытаюсь скомпилировать этот фрагмент кода из книги «Язык программирования C» (K&R). Это простая версия программы 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);
}

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

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

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

Сезар
источник
4
Не ответ, а еще один фрагмент кода, на который стоит присмотреться || c = '\t'). Это похоже на другой код в этой строке?
user7116
58
32 голоса за отладку + вопрос об опечатке ?!
Гонки легкости на орбите
37
@ TomalakGeret'kal: знаете, старое ценится дороже (вино, картины, код C)
Серджио Тюленцев
16
@ César: Я вполне вправе выражать свое мнение, и я благодарю вас, чтобы вы не пытались подвергать его цензуре. Как это бывает, да, это не веб-сайт для отладки вашего кода и устранения типографских ошибок, которые являются «локализованными» проблемами, которые никогда никому не помогут. Это веб-сайт для вопросов о языках программирования , а не для выполнения базовой отладки и справочной работы за вас. Уровень мастерства совершенно не важен. Прочтите FAQ и, возможно, этот мета-вопрос .
Гонки легкости на орбите
11
@ TomalakGeret'kal, конечно, вы можете высказать свое мнение, и я не буду подвергать цензуре ваш комментарий, несмотря на его неконструктивность. Я уже прочитал FAQ. Я программист-энтузиаст, спрашивающий об актуальной проблеме, с которой я столкнулся
Сезар

Ответы:

247

Ваша проблема связана с определениями препроцессора 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выше коде нет неоднозначности.

user7116
источник
8
Для наглядности проблема не в пробелах, а в точках с запятой. Они вам не нужны в инструкциях препроцессора.
Дэн
@Dan, спасибо за разъяснения! И проблема была в точках с запятой! Спасибо, парни!
César
2
@ Сезар: пожалуйста. Мы надеемся, что это предложение избавит вас от неприятностей в будущем, безусловно, помогло мне!
user7116
5
@ César: Также неплохо привыкнуть к макросам в круглые скобки, так как обычно вы хотите, чтобы макрос был вычислен первым. В этом случае это не имеет значения, поскольку значение представляет собой одиночный токен, но отсутствие скобок может привести к неожиданным результатам при определении выражения.
Styfle
7
"не нужны"! = "не должны быть". первое всегда верно; последнее зависит от контекста и является более актуальной проблемой в этом сценарии.
светлота Гонки в Orbit
63

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

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

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

Jmoreno
источник
9
Ваш совет не очень конструктивен для того, кто учится программировать. Изменение кода - это именно то, как вы понимаете детали программирования.
user7116
12
@sixlettervariables: И при этом вы должны знать, какие изменения вы внесли, и вносить как можно меньше изменений. Если бы OP внес изменения намеренно и внес бы как можно меньше изменений, он, вероятно, не стал бы задавать этот вопрос, поскольку ему было бы ясно, что происходит. Он бы изменил макрос для IN без ошибок, а затем макрос для OUT с двумя ошибками, вторая из которых жаловалась на точку с запятой, которую он только что добавил.
jmoreno
5
Похоже, что если вы не сделаете ошибку, включив точку с запятой в конце строки директивы препроцессора, вы, скорее всего, не будете знать, что не включаете их. Вы можете принять это за чистую монету, вы можете прочитать много кода и заметить, что их никогда не было. Или OP может испортить их, включив их, спросив о "странной" ошибке и узнав: ой, точки с запятой не требуются для директив препроцессора! Это программирование, а не эпизод Scared Straight.
user7116
14
@sixlettervariables: Да, но когда код не работает, очевидный первый шаг - это сказать: «О, хорошо, тогда то, что я изменил без какой-либо причины из кода, написанного в книге изобретателя C, вероятно, было проблема. Я просто отменю это ".
Гонки за легкостью на орбите
34

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

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

и это, вероятно, должно быть

if (c == ' ' || c == '\n' || c == '\t')
Onemach
источник
Спасибо, проблема была в точках с запятой. Второй - опечатка!
César
21
В следующий раз вставьте точный код, который вы используете, прямо из текстового редактора.
Гонки за легкостью на орбите
@ TomalakGeret'kal ну я не делал и буду, но как ты нашел?
onemach
1
@onemach: Вы сказали, что это ;была опечатка, которая не повлияла на проблему, что означает опечатку в вашем вопросе, а не в коде, который вы фактически использовали.
Гонки за легкостью на орбите
24

Определения IN и OUT должны выглядеть так:

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

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

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

else if (state == OUT)

В итоге получилось так:

else if (state == 0;)

Но вы хотели вот этого:

else if (state == 0)

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

Оскар Лопес
источник
8

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

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

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

Джаян
источник
7

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

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

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

Билл
источник
3
Это хорошая книга - насколько мне известно, одна из двух стоящих книг по Си. Я почти уверен, что более новые версии совместимы с ANSI C (вероятно, до C99 ANSI C). Другая полезная книга по C - это «Секреты глубинного программирования на языке C» Питера ван дер Линдена.
Билл
Я никогда не говорил, что это было. Мне просто сказали, что для того, чтобы привести это в соответствие с тем, как это делается сегодня, нужно изменить это главное.
Билл
4

Попробуйте добавить явные скобки вокруг блоков кода. Стиль 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;
    }
Duffymo
источник
2
Спасибо! На самом деле, код работал без фигурных скобок во втором if :)
César
5
+1. Не просто двусмысленно, но несколько опасно. Когда (если) вы ifпозже добавите строку в свой блок, если вы забудете добавить фигурные скобки, потому что ваш блок теперь состоит из более чем одной строки, может потребоваться время для отладки этой ошибки ...
The111
8
@ The111 Со мной никогда не случалось. Я до сих пор не верю, что это настоящая проблема. Я использую стиль без скобок более десяти лет, я ни разу не забыл добавить скобки при расширении тела блока.
Конрад Рудольф
1
@ The111: В этом случае нескольким участникам SO потребовалось несколько минут: P И если вы программист, который может добавлять операторы в ifпредложение и «забывать» обновлять фигурные скобки, тогда, ну, вы не очень хороший программист.
Гонки легкости на орбите
3

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

if (c == '\n'){
    ++nl;
}
if (c == ' ' || c == '\n' || c == '\t')
{
    state = OUT;
}
else if (state == OUT) {
    state = IN;
    ++nw;
}
Науман Халид
источник
2

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

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

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

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

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

Аль Кепп
источник
Альтернативой, которую я предпочитаю, являются перечисления - их можно использовать в особых местах (например, объявления массивов), которые const intнельзя использовать в C.
Майкл Берр,