이 1988 C 코드의 문제점은 무엇입니까?


94

저는 "The C Programming Language"(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 코드)
Sergio

16
@ César : 저는 제 의견을 표현할 권리가 있으며 검열하지 않도록 고맙습니다. 그렇습니다.이 웹 사이트는 코드를 디버깅하고 다른 사람에게 절대 도움이되지 않는 "현지화 된"문제인 인쇄상의 오류를 해결하기위한 웹 사이트가 아닙니다. 기본 디버깅 및 참조 작업을 수행하는 것이 아니라 프로그래밍 언어에 대한 질문을 위한 웹 사이트입니다 . 기술 수준은 전혀 관련이 없습니다. FAQ 와이 메타 질문을 읽어보십시오 .
궤도의 가벼운 경주

11
@ TomalakGeret'kal 물론 당신은 당신의 의견을 표현할 수 있으며, 나는 비 구조적 임에도 불구하고 당신의 코멘트를 검열하지 않을 것입니다. 이미 FAQ를 읽었습니다. 저는 제가 직면실제 문제 에 대해 묻는 열성적인 프로그래머입니다
César

답변:


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
@ César : 천만에요. 브레이싱 제안은 미래에 당신을 문제에서 벗어나게 해줄 것이며, 확실히 저를 도왔습니다!
user7116

5
@ César : 일반적으로 매크로를 먼저 평가하기를 원하기 때문에 매크로를 괄호로 묶는 데 익숙해지는 것도 좋은 생각입니다. 이 경우 값이 단일 토큰이므로 문제가되지 않지만 괄호를 생략하면 식을 정의 할 때 예기치 않은 결과가 발생할 수 있습니다.
styfle

7
"그것이 필요하지 않습니다"! = "그것이 없어야합니다". 전자는 항상 사실입니다. 후자는 상황에 따라 다르며이 시나리오에서 더 적절한 문제입니다.
궤도의 가벼운 경주

63

이 코드의 주요 문제점 은 K & R의 코드 가 아니라는 것입니다 . 이 책에는없는 매크로 정의 뒤에 세미콜론이 포함되어 있으며 다른 사람들이 지적했듯이 의미를 변경합니다.

코드를 이해하기 위해 변경하는 경우를 제외하고는 코드를 이해할 때까지 그대로 두어야합니다. 이해하는 코드 만 안전하게 수정할 수 있습니다.

이것은 아마도 여러분의 오타 일 뿐이지 만 프로그래밍 할 때 세부 사항에 대한 이해와주의의 필요성을 보여줍니다.


9
당신의 조언은 프로그램을 배우는 누군가에게별로 건설적이지 않습니다. 코드 수정은 프로그래밍의 세부 사항을 정확히 이해하는 방법입니다.
user7116 2011

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

감사합니다. 세미콜론이 문제였습니다. 두 번째는 오타였습니다!
세자르

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

컴파일러는 함수가없는 함수에 대해 int 반환 값을 가정하고 컴파일러 / 링커가 argc / argv에 대한 선언 부족과 반환 값 부족을 해결할 것이라고 확신하지만 거기에 있어야합니다.


3
그것은 좋은 책입니다. 제가 아는 한 C에 관한 책 두 권 중 하나입니다. 나는 최신 버전이 ANSI C를 준수한다고 확신합니다 (아마도 C99 ANSI C 이전). C에 관한 다른 책은 Peter van der Linden의 Expert C Programming Deep C Secrets입니다.
Bill

나는 그렇게 말하지 않았다. 나는 그것을 오늘날의 일이 행해지는 방식과 일치 시키려면 그 메인을 변경해야한다고 단순히 언급했다.
Bill

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 :이 경우에는 소수의 기여자에게 몇 분이 걸렸습니다. : 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

다른 답변이 지적했듯이 문제는 in #define및 세미콜론입니다. 이러한 문제를 최소화하기 위해 항상 숫자 상수를 다음과 같이 정의하는 것을 선호합니다 const int.

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

이렇게하면 많은 문제와 가능한 문제를 제거 할 수 있습니다. 다음 두 가지로 제한됩니다.

  1. 컴파일러는 지원해야합니다 const. 1988 년에는 일반적으로 사실이 아니었지만 이제는 일반적으로 사용되는 모든 컴파일러에서 지원됩니다. (AFAIK는 constC ++에서 "빌려온"입니다.)

  2. 문자열과 같은 상수가 필요한 특수한 장소에서는 이러한 상수를 사용할 수 없습니다. 하지만 당신의 프로그램은 그렇지 않다고 생각합니다.


내가 선호하는 대안은 열거 형 const int입니다. C 에서는 사용할 수없는 특별한 장소 (배열 선언과 같은)에서 사용할 수 있습니다.
Michael Burr
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.