这个1988 C代码有什么问题?

我试图从书中编写这段代码" The C Programming Language" (K& R)。 它是UNIX程序main的简单版本:

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

这是一个新标准还是我仍然可以使用无类型的主要?

César asked 2019-08-10T21:11:39Z
9个解决方案
247 votes

您的问题是您的预处理器定义elseif

#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匹配,因为您没有使用大括号。 因此,从预处理器定义INOUT中删除分号。

这里学到的教训是预处理器语句不必以分号结尾。

此外,你应该总是使用大括号!

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

上面的代码中没有悬挂-else歧义。

user7116 answered 2019-08-10T21:12:27Z
63 votes

这段代码的主要问题是它不是K&amp; R的代码。 它包括宏定义后的分号,这些分号在本书中没有出现,正如其他人指出的那样,它改变了含义。

除非在尝试理解代码时进行更改,否则在理解之前应该不管它。 您只能安全地修改您理解的代码。

这可能只是你的一个错字,但它确实说明了在编程时理解和关注细节的必要性。

jmoreno answered 2019-08-10T21:13:06Z
34 votes

宏之后不应该有任何分号,

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

它可能应该是

if (c == ' ' || c == '\n' || c == '\t')
onemach answered 2019-08-10T21:13:38Z
24 votes

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)

解决方案:删除原始定义中的数字后面的分号。

Óscar López answered 2019-08-10T21:14:39Z
8 votes

如您所见,宏中存在问题。

GCC可以选择在预处理后停止。 (-E)此选项对于查看预处理的结果很有用。 实际上,如果您使用c / c ++中的大型代码库,该技术是一个重要的技术。 通常,makefile将具有在预处理之后停止的目标。

有关快速参考:SO问题涵盖了选项 - 如何在Visual Studio中进行预处理后查看C / C ++源文件? 它从vc ++开始,但也有下面提到的gcc选项。

Jayan answered 2019-08-10T21:15:19Z
7 votes

不完全是一个问题,但main()的声明也是过时的,它应该是这样的东西。

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

编译器将假定函数的int返回值为w / o,并且我确定编译器/链接器将解决缺少argc / argv的声明和缺少返回值,但它们应该在那里。

Bill answered 2019-08-10T21:15:52Z
5 votes

尝试在代码块周围添加显式大括号。 K&amp; 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 answered 2019-08-10T21:16:24Z
3 votes

一个简单的方法是为每个ifelse使用{}之类的括号:

if (c == '\n'){
    ++nl;
}
if (c == ' ' || c == '\n' || c == '\t')
{
    state = OUT;
}
else if (state == OUT) {
    state = IN;
    ++nw;
}
Nauman Khalid answered 2019-08-10T21:16:50Z
2 votes

正如其他答案所指出的那样,问题在于const和分号。 为了最大限度地减少这些问题,我总是希望将数字常量定义为const

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

这样你就摆脱了许多问题和可能出现的问题。 它受到两件事的限制:

  1. 你的编译器必须支持const - 它在1988年一般都不正确,但现在它得到了所有常用编译器的支持。 (AFAIK const是&#34;借用&#34;来自C ++。)

  2. 你可以在一些需要类似字符串的常量的特殊地方使用这些常量。 但我认为你的计划就是这种情况。

Al Kepp answered 2019-08-10T21:17:38Z
translate from https://stackoverflow.com:/questions/8640818/whats-wrong-with-this-1988-c-code