这个1988年的C代码有什么问题?
我试图从“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; }
这是一个新的标准,还是我仍然可以使用一个无types的主?
你的问题是你的预处理器定义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
含义。
这个代码的主要问题是它不是来自K&R的代码。 它包含macros定义之后的分号,这些在本书中不存在,正如其他人指出的那样改变了含义。
除了在尝试理解代码时做出改变之外,您应该保持独立,直到您理解为止。 你只能安全地修改你理解的代码。
这可能只是你的一个错字,但它确实说明了在编程时需要理解和关注细节。
macros后面不应该有分号,
#define IN 1 /* inside a word */ #define OUT 0 /* outside a word */
它应该可能是
if (c == ' ' || c == '\n' || c == '\t')
IN和OUT的定义应该如下所示:
#define IN 1 /* inside a word */ #define OUT 0 /* outside a word */
分号引起了问题! 解释很简单:IN和OUT都是预处理指令,本质上编译器将用1代替IN的所有出现,并且所有出现的OUT在源代码中用0代替。
由于原来的代码在1和0之后有一个分号,所以在代码中IN和OUT被replace之后,数字后面的额外分号会产生无效代码,例如:
else if (state == OUT)
结束了看起来像这样:
else if (state == 0;)
但是你想要的是这样的:
else if (state == 0)
解决scheme:删除原始定义中的数字后面的分号。
不完全是一个问题,但main()
的声明也是过时的,应该是这样的。
int main(int argc, char** argv) { ... return 0; }
编译器将假定一个函数没有一个int的返回值,并且我确定编译器/链接器会在argc / argv缺less声明和缺less返回值的情况下工作,但是它们应该在那里。
正如你所看到的,macros中有一个问题。
GCC可以select在预处理后停止。 (-E)该选项用于查看预处理的结果。 实际上,如果您正在使用c / c ++中的大型代码库,该技术是非常重要的。 通常makefile会在预处理之后停止。
对于快速参考:SO问题涵盖了选项 – 如何在Visual Studio中预处理后看到C / C ++源文件? 。 它从vc ++开始,但也有下面提到的gcc选项 。
尝试围绕代码块添加明确的大括号。 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; }
一个简单的方法是为每个if
和else
使用括号{}:
if (c == '\n'){ ++nl; } if (c == ' ' || c == '\n' || c == '\t') { state = OUT; } else if (state == OUT) { state = IN; ++nw; }
正如其他答案指出的,问题是在#定义和分号。 为了最大限度地减less这些问题,我总是喜欢将常量定义为const int
:
const int IN = 1; const int OUT = 0;
这样你就摆脱了许多问题和可能的问题。 它受到两件事的限制:
-
你的编译器必须支持
const
(在1988年并不是这样),但是现在它被所有常用的编译器支持。 (AFAIKconst
是从C ++“借来的”。) -
你不能在一些需要类string常量的地方使用这些常量。 但是我认为你的计划并不是这种情况。