Що не так із цим кодом 1988 року С?


94

Я намагаюся зібрати цей фрагмент коду з книги "Мова програмування на С" (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. Можливо, це пов’язано з версією компілятора, а може, я просто говорю дурниці.

Я бачив у сучасному коді С різне використання mainфункції:

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

Це новий стандарт чи я все-таки можу використовувати безтиповий основний?


4
Чи не відповідь, а інший шматок коду , щоб подивитися на найближче, || c = '\t'). Чи здається це однаковим з іншим кодом у цьому рядку?
user7116

58
32 голоси за питання налагодження + друкарська помилка ?!
Гонки легкості на орбіті

37
@ TomalakGeret'kal: ти знаєш, старі речі цінують більше (вино, картини, код С)
Серхіо Туленцев

16
@ Сезар: Я цілком маю право висловити свою думку, і я буду дякувати вам, що не намагаєтесь цензурувати її. Так трапляється, так, це не веб-сайт для налагодження вашого коду та вирішення ваших друкарських помилок, які є "локалізованими" проблемами, які ніколи нікому не допоможуть. Це веб-сайт для запитань про мови програмування , а не для того, щоб виконати за вас основну налагоджувальну роботу та довідкову роботу. Рівень майстерності абсолютно не має значення. Прочитайте поширені запитання та, можливо, також це мета питання .
Гонки легкості на орбіті

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вище коді немає жодної двозначності.


8
Для наочності проблема полягає не в інтервалі, а в крапці з комою. Вони вам не потрібні в операторах препроцесора.
Dan

@Dan дякую за роз'яснення! І крапка з комою справді була проблемою! Спасибі, хлопці!
Сезар,

2
@ Сезар: ласкаво просимо. Сподіваємось, ця пропозиція допоможе уберегти вас від неприємностей у майбутньому, безумовно, мені допомогла!
user7116

5
@ Сезар: Також непогано звикнути ставити дужки навколо макросів, оскільки ти, як правило, хочеш, щоб макрос оцінювався спочатку. У цьому випадку це не має значення, оскільки значення - це один маркер, але залишення парен може призвести до несподіваних результатів при визначенні виразу.
styfle

7
"не потрібні"! = "їх не повинно бути". перше - це завжди правда; остання залежить від контексту і є найбільш актуальною проблемою в цьому сценарії.
Гонки легкості на орбіті

63

Основна проблема цього коду полягає в тому, що це не код від K&R. Він включає крапку з комою після визначень макросів, яких у книзі не було, що, як зазначали інші, змінює значення.

За винятком змін під час спроби зрозуміти код, вам слід залишити його в спокої, поки ви його не зрозумієте. Ви можете лише безпечно змінити код, який ви розумієте.

Це, мабуть, була просто друкарська помилка з вашого боку, але вона ілюструє необхідність розуміння та уваги до деталей при програмуванні.


9
Ваша порада не дуже конструктивна для тих, хто вчиться програмувати. Модифікація коду - це саме те, як ви розумієте деталі програмування.
user7116

12
@sixlettervariables: І, роблячи це, ви повинні знати, які зміни ви вносили, і вносити якомога менше змін. Якби ОП вніс зміни навмисно і вніс якомога менше змін, він, мабуть, не став би цього питання, оскільки йому було б зрозуміло, що відбувається. Він змінив би макрос для IN, без помилок, а потім макрос для OUT з двома помилками, друга з яких скаржилася б на крапку з комою, яку він щойно додав.
jmoreno

5
Здається, якщо ви не помилитесь, включивши крапку з комою в кінці рядка директиви препроцесора, ви, ймовірно, не знаєте, що не повинні їх включати. Ви можете взяти це за номінал, ви могли прочитати багато коду і помітити, що їх ніколи там немає. Або OP може зіпсувати, включивши їх, запитати про "химерну" помилку і з'ясувати: ой, для директив препроцесора не потрібні крапки з комою! Це програмування, а не епізод Scared Straight.
user7116

14
@sixlettervariables: Так, але коли код не працює, очевидним першим кроком є ​​перехід "о, добре, тоді те, що я без будь-якої причини змінив із коду, написаного у книзі винахідником С, було, мабуть, випуск. Тоді я просто скасую це ".
Гонки легкості на орбіті


34

Після макросів не повинно бути крапки з комою,

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

і це, мабуть, має бути

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

Дякую, проблема була в крапках з комою. Другий був друкарською помилкою!
Сезар,

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 Deep C Secrets Пітера ван дер Ліндена.
Білл

Я ніколи не казав, що це було. Мені просто прокоментували, що, щоб привести це у відповідність із тим, як сьогодні все робиться, це головне слід змінити.
Білл

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

2
Дякую! Власне, код працював без фігурних дужок у другому if :)
Сесар

5
+1. Не просто двозначно, але дещо небезпечно. Коли (якщо) ви додасте рядок до свого ifблоку пізніше, якщо ви забудете додати фігурні дужки, оскільки ваш блок тепер більше одного рядка, може знадобитися деякий час для налагодження цієї помилки ...
The111

8
@ The111 зі мною ніколи, ніколи не траплялося. Я досі не вірю, що це справжня проблема. Я використовую стиль без фігурних дужок більше десяти років, жодного разу не забув додати фігурні дужки, коли розширюю тіло блоку.
Конрад Рудольф

1
@ The111: У цьому випадку у кількох співавторів знадобилося кілька хвилин: 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.
Michael Burr
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.