Có gì sai với mã C 1988 này?


94

Tôi đang cố gắng biên dịch đoạn mã này từ cuốn sách "Ngôn ngữ lập trình C" (K & R). Đây là phiên bản cơ bản của chương trình 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);
}

Và tôi gặp lỗi sau:

$ gcc wc.c 
wc.c: In function main’:
wc.c:18: error: else without a previous if
wc.c:18: error: expected ‘)’ before ‘;’ token

Ấn bản thứ 2 của cuốn sách này là từ năm 1988 và tôi còn khá mới đối với C. Có lẽ nó liên quan đến phiên bản biên dịch hoặc có lẽ tôi chỉ đang nói những điều vô nghĩa.

Tôi đã thấy trong mã C hiện đại một cách sử dụng khác của mainhàm:

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

Đây có phải là một tiêu chuẩn mới hay tôi vẫn có thể sử dụng một main không loại?


4
Không phải là một câu trả lời, mà là một đoạn mã khác để xem xét kỹ hơn , || c = '\t'). Điều đó có vẻ giống với mã khác trên dòng đó?
user7116 27/12/11

58
32 phiếu ủng hộ cho câu hỏi gỡ lỗi + lỗi chính tả ?!
Các cuộc đua ánh sáng trong quỹ đạo

37
@ TomalakGeret'kal: bạn biết đấy, đồ cũ được đánh giá cao hơn (rượu vang, tranh, mã C)
Sergio Tishedsev 27/12/11

16
@ César: Tôi hoàn toàn có quyền bày tỏ ý kiến ​​của mình, và tôi sẽ cảm ơn bạn đã không cố gắng kiểm duyệt nó. Khi nó xảy ra, vâng, đây không phải là một trang web để gỡ lỗi mã của bạn và giải quyết các lỗi đánh máy của bạn, đó là những vấn đề "bản địa hóa" sẽ không bao giờ giúp ích cho bất kỳ ai khác. Đó là một trang web dành cho các câu hỏi về ngôn ngữ lập trình , không phải để thực hiện công việc gỡ lỗi và tham khảo cơ bản cho bạn. Cấp độ kỹ năng hoàn toàn không liên quan. Đọc Câu hỏi thường gặp, và có lẽ cả câu hỏi meta này .
Lightness Races ở Orbit

11
@ TomalakGeret'kal tất nhiên bạn có thể bày tỏ ý kiến ​​của mình và tôi sẽ không kiểm duyệt bình luận của bạn mặc dù là không mang tính xây dựng. Tôi đã đọc Câu hỏi thường gặp. Tôi là một lập trình viên đam mê hỏi về một vấn đề thực tế mà tôi phải đối mặt
César

Câu trả lời:


247

Vấn đề của bạn là với các định nghĩa về bộ tiền xử lý của bạn INOUT:

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

Chú ý cách bạn có dấu chấm phẩy ở cuối mỗi cái. Khi bộ tiền xử lý mở rộng chúng, mã của bạn sẽ trông giống như sau:

    if (c == ' ' || c == '\n' || c == '\t')
        state = 0;; /* <--PROBLEM #1 */
    else if (state == 0;) { /* <--PROBLEM #2 */
        state = 1;;

Dấu chấm phẩy thứ hai đó khiến elsecho không có trước đó iflà so khớp, bởi vì bạn không sử dụng dấu ngoặc nhọn. Vì vậy, hãy xóa dấu chấm phẩy khỏi các định nghĩa tiền xử lý của INOUT.

Bài học rút ra ở đây là các câu lệnh tiền xử lý không nhất thiết phải kết thúc bằng dấu chấm phẩy.

Ngoài ra, bạn nên sử dụng luôn niềng răng!

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

Không có elsesự mơ hồ nào trong đoạn mã trên.


8
Để rõ ràng, vấn đề không phải là khoảng cách, mà là dấu chấm phẩy. Bạn không cần chúng trong các câu lệnh tiền xử lý.
Dan

@Dan cảm ơn vì đã làm rõ! Và dấu chấm phẩy thực sự là vấn đề! Cảm ơn các bạn!
César

2
@ César: không có chi. Gợi ý về niềng răng hy vọng sẽ giúp bạn thoát khỏi khó khăn trong tương lai, chắc chắn đã giúp tôi!
user7116 27/12/11

5
@ César: Bạn cũng nên làm quen với việc đặt dấu ngoặc đơn xung quanh macro vì bạn thường muốn macro được đánh giá trước. Trong trường hợp này, điều đó không quan trọng vì giá trị là một mã thông báo duy nhất, nhưng việc bỏ đi các parens có thể dẫn đến kết quả không mong muốn khi xác định một biểu thức.
súng trường

7
"don't need them"! = "shouldn’t have them". cái trước luôn đúng; thứ hai phụ thuộc vào ngữ cảnh và là vấn đề thích hợp hơn trong kịch bản này.
Các cuộc đua ánh sáng trong quỹ đạo

63

Vấn đề chính với mã này là nó không phải là mã từ K&R. Nó bao gồm dấu chấm phẩy sau các định nghĩa macro, không có trong sách, như những người khác đã chỉ ra làm thay đổi ý nghĩa.

Ngoại trừ khi thực hiện một thay đổi để cố gắng hiểu mã, bạn nên để nó một mình cho đến khi bạn hiểu nó. Bạn chỉ có thể sửa đổi mã một cách an toàn mà bạn hiểu.

Đây có thể chỉ là lỗi đánh máy của bạn, nhưng nó cho thấy sự cần thiết phải hiểu và chú ý đến các chi tiết khi lập trình.


9
Lời khuyên của bạn không mang tính xây dựng cho những người đang học lập trình. Sửa đổi mã chính là cách bạn hiểu chi tiết của lập trình.
user7116 27/12/11

12
@sixlettervariables: Và khi làm như vậy, bạn nên biết mình đã thực hiện những thay đổi nào và thực hiện ít thay đổi nhất có thể. Nếu OP cố tình thực hiện các thay đổi và thực hiện càng ít thay đổi càng tốt, thì có lẽ anh ta đã không đặt câu hỏi này, vì anh ta đã rõ chuyện gì đang xảy ra. Anh ta sẽ thay đổi macro cho IN, không có lỗi và sau đó macro cho OUT với hai lỗi, lỗi thứ hai sẽ phàn nàn về dấu chấm phẩy mà anh ta vừa thêm vào.
jmoreno 27/12/11

5
Có vẻ như trừ khi bạn mắc phải sai lầm khi đưa dấu chấm phẩy vào cuối dòng chỉ thị tiền xử lý, bạn có thể sẽ không biết rằng bạn không bao gồm chúng. Bạn có thể lấy nó theo mệnh giá, bạn có thể đọc rất nhiều mã và nhận thấy chúng dường như không bao giờ ở đó. Hoặc, OP có thể gây rối bằng cách bao gồm chúng, hỏi về lỗi "kỳ lạ" và tìm ra: rất tiếc, không cần dấu chấm phẩy cho các lệnh tiền xử lý! Đây là chương trình, không phải là một tập của Scared Straight.
user7116 27/12/11

14
@sixlettervariables: Có, nhưng khi mã không hoạt động, bước đầu tiên rõ ràng là "ồ, được rồi, sau đó những gì tôi đã thay đổi mà không có bất kỳ lý do gì từ mã được viết trong một cuốn sách của nhà phát minh C, có thể là vấn đề. Tôi sẽ hoàn tác điều đó sau đó. "
Các cuộc đua ánh sáng trong quỹ đạo


34

Không được có bất kỳ dấu chấm phẩy nào sau macro,

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

và nó có lẽ nên

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

Cảm ơn, dấu chấm phẩy là vấn đề. Cái thứ 2 là một lỗi đánh máy!
César

21
Lần tới, vui lòng dán mã chính xác mà bạn sử dụng, trực tiếp từ trình soạn thảo văn bản của bạn.
Các cuộc đua ánh sáng trong quỹ đạo

@ TomalakGeret'kal tôi đã không làm và tôi sẽ làm, nhưng làm thế nào bạn tìm thấy?
onemach

1
@onemach: Bạn nói đó ;là lỗi đánh máy không ảnh hưởng đến vấn đề, có nghĩa là lỗi đánh máy trong câu hỏi của bạn chứ không phải trong mã bạn thực sự sử dụng.
Các cuộc đua ánh sáng trong quỹ đạo

24

Định nghĩa của IN và OUT sẽ giống như sau:

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

Dấu chấm phẩy gây ra sự cố! Giải thích rất đơn giản: cả IN và OUT đều là chỉ thị tiền xử lý, về cơ bản trình biên dịch sẽ thay thế tất cả các lần xuất hiện của IN bằng 1 và tất cả các lần xuất hiện của OUT bằng 0 trong mã nguồn.

Vì mã gốc có dấu chấm phẩy sau số 1 và số 0, khi IN và OUT được thay thế trong mã, dấu chấm phẩy thừa sau số tạo ra mã không hợp lệ, ví dụ như dòng này:

else if (state == OUT)

Kết thúc trông như thế này:

else if (state == 0;)

Nhưng những gì bạn muốn là:

else if (state == 0)

Giải pháp: bỏ dấu chấm phẩy sau các số trong định nghĩa ban đầu.


8

Như bạn thấy đã có sự cố trong macro.

GCC có tùy chọn dừng sau khi xử lý trước. (-E) Tùy chọn này hữu ích để xem kết quả của quá trình xử lý trước. Trên thực tế, kỹ thuật này là một kỹ thuật quan trọng nếu bạn đang làm việc với cơ sở mã lớn trong c / c ++. Thông thường, các tệp makefiles sẽ có mục tiêu dừng lại sau khi xử lý trước.

Để tham khảo nhanh: Câu hỏi SO bao gồm các tùy chọn - Làm cách nào để xem tệp nguồn C / C ++ sau khi xử lý trước trong Visual Studio? . Nó bắt đầu với vc ++, nhưng cũng có các tùy chọn gcc được đề cập bên dưới .


7

Không chính xác là một vấn đề, nhưng tuyên bố của main()cũng được ghi ngày tháng, nó phải là một cái gì đó như thế này.

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

Trình biên dịch sẽ giả định một giá trị trả về int cho một hàm w / o, và tôi chắc chắn rằng trình biên dịch / liên kết sẽ giải quyết việc thiếu khai báo cho argc / argv và thiếu giá trị trả về, nhưng chúng sẽ ở đó.


3
Đó là một cuốn sách hay - một trong hai cuốn sách đáng giá duy nhất trong khi những cuốn sách về C theo như tôi biết. Tôi khá chắc chắn rằng các phiên bản mới hơn tuân thủ ANSI C (có thể là ANSI C trước C99). Cuốn sách đáng giá khác về C là Bí mật chuyên sâu về lập trình C của Peter van der Linden.
Hóa đơn

Tôi chưa bao giờ nói nó được. Tôi chỉ đơn giản nhận xét rằng để làm cho nó phù hợp với cách mọi thứ được thực hiện ngày nay, chính đó nên được thay đổi.
Hóa đơn

4

Thử thêm dấu ngoặc nhọn xung quanh các khối mã. Kiểu K&R có thể không rõ ràng.

Nhìn vào dòng 18. Trình biên dịch đang cho bạn biết vấn đề nằm ở đâu.

    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
Cảm ơn! Trên thực tế, mã đã hoạt động mà không có dấu ngoặc nhọn trong lần thứ hai nếu :)
César

5
+1. Không chỉ mơ hồ mà có phần nguy hiểm. Khi (nếu) bạn thêm một dòng vào của bạn ifkhối sau này, nếu bạn quên thêm niềng răng vì khối của bạn bây giờ là hơn một dòng, nó có thể mất một thời gian để gỡ lỗi mà lỗi ...
The111

8
@ The111 Chưa bao giờ, chưa từng xảy ra với tôi. Tôi vẫn không tin rằng đây là một vấn đề thực sự. Tôi đã sử dụng kiểu không dấu ngoặc nhọn trong hơn một thập kỷ, tôi chưa bao giờ quên thêm dấu ngoặc nhọn khi mở rộng phần thân của một khối.
Konrad Rudolph

1
@ The111: Trong trường hợp này, phải mất một vài người đóng góp SO trong vài phút: P Và nếu bạn là một lập trình viên có khả năng thêm câu lệnh vào một ifmệnh đề và "quên" cập nhật dấu ngoặc nhọn thì bạn không phải một lập trình viên rất giỏi.
Các cuộc đua ánh sáng trong quỹ đạo

3

Một cách đơn giản là sử dụng các dấu ngoặc như {} cho mỗi ifelse:

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

2

Như các câu trả lời khác đã chỉ ra, vấn đề nằm ở #definedấu chấm phẩy và. Để giảm thiểu những vấn đề này, tôi luôn muốn xác định các hằng số dưới dạng const int:

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

Bằng cách này bạn sẽ thoát khỏi nhiều rắc rối và các vấn đề có thể xảy ra. Nó bị giới hạn bởi hai điều:

  1. Trình biên dịch của bạn phải hỗ trợ const- điều này nói chung là không đúng vào năm 1988, nhưng bây giờ nó được hỗ trợ bởi tất cả các trình biên dịch thường dùng. (AFAIK constlà "mượn" từ C ++.)

  2. Bạn không thể sử dụng các hằng số này ở một số nơi đặc biệt mà bạn cần một hằng số giống như chuỗi. Nhưng tôi nghĩ chương trình của bạn không phải là trường hợp đó.


Một thay thế tôi thích là sự đếm - chúng có thể được sử dụng ở các vị trí đặc biệt (như khai báo mảng) mà const intkhông có thể trong C.
Michael Burr
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.