この1988 Cコードの何が問題になっていますか?


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

この本の第2版は1988年のもので、私はCにかなり慣れていません。おそらく、コンパイラバージョンに関係しているのかもしれません。

最近のCコードでは、main関数の別の使用法を見てきました。

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

これは新しい規格ですか、それともタイプのないメインを使用できますか?


4
答えではありませんが、より詳細に調べる別のコード|| c = '\t')です。それはその行の他のコードと同じように見えますか?
user7116 2011

58
デバッグ+タイプミスの質問に対する32の賛成票?!
オービットのライトネスレース2011

37
@ TomalakGeret'kal:ご存知のように、古いものはより価値があります(ワイン、絵画、Cコード)
Sergio Tulentsev

16
@César:私は私の意見を表明する権利のかなりの範囲内にあり、それを検閲しようとしないように感謝します。確かに、これはコードをデバッグし、入力ミスを解決するためのWebサイトではありません。これは、他の人を助けることのない「ローカライズされた」問題です。これは、プログラミング言語に関する質問の Webサイトであり、基本的なデバッグや参照作業を行うためのものではありません。スキルレベルは完全に無関係です。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;;

この2番目のセミコロンは、中括弧を使用していないため、として一致するelse以前のifものはありません。したがって、INおよびのプリプロセッサ定義からセミコロンを削除しOUTます。

ここで学んだ教訓は、プリプロセッサステートメントがセミコロンで終わる必要がないことです。

また、常に中括弧を使用する必要があります!

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

else上記のコードにぶら下がり曖昧性はありません。


8
明確にするために、問題はスペースではなくセミコロンです。プリプロセッサステートメントでそれらを必要としません。
Dan

@Dan明確化に感謝!そして、セミコロンは確かに問題でした!みんなありがとう!
セザール

2
@César:どういたしまして。筋かいの提案は、うまくいけば、将来的にトラブルからあなたを守ります、確かに私を助けました!
user7116 2011

5
@César:通常は最初にマクロを評価する必要があるため、マクロを括弧で囲むことに慣れることもお勧めします。この場合、値は単一のトークンであるため問題ではありませんが、括弧を省略すると、式を定義するときに予期しない結果が生じる可能性があります。
スタイル

7
"それらを必要としない"!= "それらを持っているべきではない"。前者は常に真実です。後者はコンテキスト依存であり、このシナリオではより適切な問題です。
オービットの軽量レースは

63

このコードの主な問題は、K&Rのコードではないことです。それは本に存在しなかったマクロ定義の後にセミコロンを含み、他の人が指摘したように意味を変えます。

コードを理解するために変更を加える場合を除いて、理解するまでそのままにしておく必要があります。理解できるコードのみを安全に変更できます。

これはおそらくあなたのタイプミスにすぎませんが、プログラミングの際に詳細を理解し注意する必要性を示しています。


9
あなたのアドバイスは、プログラムを学ぶ人にとって、ひどく建設的なものではありません。コードの変更は、プログラミングの詳細を正確に理解する方法です。
user7116

12
@sixlettervariables:そして、そうするとき、あなたはあなたが行ったどんな変更を知っていて、そしてできるだけ少ない変更を可能にするべきです。OPが意図的に変更を行い、可能な限り少ない変更を行った場合、何が起こっているのか明確だったので、彼はおそらくこの質問をしなかったでしょう。エラーのないINのマクロを変更してから、2つのエラーのあるOUTのマクロを変更したとします。2番目のエラーは、追加したセミコロンについて文句を言うことになります。
jmoreno 2011

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')

おかげで、セミコロンが問題でした。2つ目はタイプミスでした!
セザール

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 ++で大規模なコードベースを使用している場合に重要です。通常、メイクファイルには、前処理後に停止するターゲットがあります。

クイックリファレンス:SOの質問はオプションをカバーしています-Visual Studioでの前処理後にC / C ++ソースファイルを表示するにはどうすればよいですか?。それはvc ++で始まりますが、以下に述べるgccオプションもあります


7

正確には問題ではありませんが、の宣言にmain()も日付が付けられています。これはこのようなものでなければなりません。

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

コンパイラーは、1つはない関数のint戻り値を想定します。コンパイラー/リンカーは、argc / argvの宣言がないことと戻り値がないことを回避できると確信していますが、それらは存在するはずです。


3
それは良い本です-私が知る限り、Cに関する本の中で2冊だけの価値がある本の1つです。新しいエディションはANSI Cに準拠していると確信しています(おそらくC99より前のANSI C)。Cに関する本のもう1つの価値は、Peter van der LindenによるExpert C Programming 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
ありがとう!実際には、コードは:)もし第二に括弧なしで働い
セザール

5
+1。あいまいなだけでなく、多少危険です。if後でブロックに行を追加する場合(ブロックが複数行になっているために中括弧を追加し忘れた場合)、そのエラーのデバッグに時間がかかることがあります...
The111

8
@ The111決して、決して起こらなかった。これが本当の問題だとはまだ信じていません。ブレースレススタイルを10年以上使用していますが、ブロックの本体を拡張するときにブレースを追加するのを忘れたことはありません。
Konrad Rudolph

1
@ The111:この場合、数人のSO寄稿者が数分を要しました:Pそして、if句にステートメントを追加し、中括弧を更新することを「忘れる」ことができるプログラマーである場合は、そうではありません。非常に優れたプログラマーです。
オービットのライトネスレース

3

簡単な方法は、それぞれに対して、{}のようなブラケットを使用することであるifelse

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

2

他の答えが指摘したように、問題は#defineセミコロンにあります。これらの問題を最小限に抑えるために、私は常に数値定数をaとして定義することを好みconst intます:

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

このようにして、多くの問題と考えられる問題を取り除くことができます。これは、次の2つの制限があります。

  1. お使いのコンパイラはサポートする必要がありますconst-これは1988年には一般的には真実ではありませんでしたが、今では一般的に使用されているすべてのコンパイラでサポートされています。(AFAIKはconstC ++から「借用」されています。)

  2. これらの定数は、文字列のような定数が必要になる特別な場所では使用できません。しかし、あなたのプログラムはそうではないと思います。


私が好む選択肢が列挙型である-彼らは(配列宣言のような)特別な場所で使用することができconst intないC.でできる
マイケル・バリ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.