มีอะไรผิดปกติกับรหัส 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

ฉบับที่ 2 ของหนังสือเล่มนี้มาจากปี 1988 และฉันค่อนข้างใหม่สำหรับ C อาจจะเกี่ยวข้องกับเวอร์ชันคอมไพเลอร์หรือบางทีฉันก็แค่พูดเรื่องไร้สาระ

ฉันเคยเห็นในรหัส C สมัยใหม่ว่ามีการใช้mainฟังก์ชันที่แตกต่างกัน:

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

นี่เป็นมาตรฐานใหม่หรือฉันยังสามารถใช้ main-type ได้หรือไม่


4
ไม่ใช่คำตอบ แต่เป็นโค้ดอีกชิ้นที่ต้องดูอย่าง|| c = '\t')ละเอียด ดูเหมือนว่าจะเหมือนกับรหัสอื่น ๆ ในบรรทัดนั้นหรือไม่
user7116

58
32 คะแนนโหวตสำหรับคำถามแก้จุดบกพร่อง + พิมพ์ผิด?!
Lightness Races ในวงโคจร

37
@ TomalakGeret'kal: คุณรู้ไหมของเก่ามีมูลค่ามากกว่า (ไวน์ภาพวาดรหัส C)
Sergio Tulentsev

16
@ César: ฉันค่อนข้างมีสิทธิ์ในการแสดงความคิดเห็นของฉันและฉันจะขอบคุณที่ไม่พยายามเซ็นเซอร์มัน ในขณะที่เกิดขึ้นใช่นี่ไม่ใช่เว็บไซต์สำหรับการดีบักโค้ดของคุณและแก้ไขข้อผิดพลาดในการพิมพ์ซึ่งเป็นปัญหา "แปลเป็นภาษาท้องถิ่น" ที่จะไม่ช่วยใครอีกต่อไป เป็นเว็บไซต์สำหรับคำถามเกี่ยวกับภาษาโปรแกรมไม่ใช่สำหรับการแก้จุดบกพร่องพื้นฐานและงานอ้างอิงสำหรับคุณ ระดับทักษะไม่เกี่ยวข้องโดยสิ้นเชิง อ่านคำถามที่พบบ่อยและอาจเป็นคำถามเมตานี้ด้วย
Lightness Races ใน Orbit

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
เพื่อความชัดเจนปัญหาไม่ได้อยู่ที่ระยะห่าง แต่เป็นอัฒภาค คุณไม่จำเป็นต้องใช้ในคำสั่งก่อนตัวประมวลผล
แดน

@ แดนขอบคุณสำหรับคำชี้แจง! และอัฒภาคก็เป็นปัญหา! ขอบคุณเพื่อน!
César

2
@ César: ยินดีต้อนรับ คำแนะนำในการค้ำยันจะช่วยให้คุณไม่เกิดปัญหาในอนาคตได้อย่างแน่นอนช่วยฉันได้!
user7116

5
@ César: คุณควรคุ้นเคยกับการใส่วงเล็บรอบ ๆ มาโครเนื่องจากโดยทั่วไปคุณต้องการให้ประเมินมาโครก่อน ในกรณีนี้จะไม่สำคัญเนื่องจากค่าเป็นโทเค็นเดียว แต่การไม่ใช้ parens อาจทำให้เกิดผลลัพธ์ที่ไม่คาดคิดเมื่อกำหนดนิพจน์
สไตล์

7
"ไม่จำเป็นต้องใช้"! = "ไม่ควรมี" อดีตเป็นจริงเสมอ ประเด็นหลังขึ้นอยู่กับบริบทและเป็นปัญหาที่เกี่ยวข้องมากกว่าในสถานการณ์นี้
Lightness Races ในวงโคจร

63

ปัญหาหลักของรหัสนี้คือไม่ใช่รหัสจาก K&R รวมถึงเครื่องหมายอัฒภาคหลังคำจำกัดความมาโครซึ่งไม่มีอยู่ในหนังสือเล่มนี้ซึ่งเนื่องจากคนอื่น ๆ ได้ชี้ให้เห็นถึงการเปลี่ยนแปลงความหมาย

ยกเว้นเมื่อทำการเปลี่ยนแปลงเพื่อพยายามทำความเข้าใจโค้ดคุณควรปล่อยให้มันอยู่คนเดียวจนกว่าคุณจะเข้าใจ คุณสามารถแก้ไขโค้ดที่คุณเข้าใจได้อย่างปลอดภัยเท่านั้น

นี่อาจเป็นเพียงการพิมพ์ผิดในส่วนของคุณ แต่มันแสดงให้เห็นถึงความจำเป็นในการทำความเข้าใจและใส่ใจในรายละเอียดเมื่อเขียนโปรแกรม


9
คำแนะนำของคุณไม่ได้สร้างสรรค์มากสำหรับคนที่เรียนรู้การเขียนโปรแกรม การแก้ไขโค้ดเป็นวิธีที่คุณเข้าใจรายละเอียดของการเขียนโปรแกรมอย่างแม่นยำ
user7116

12
@sixlettervariables: และเมื่อทำเช่นนั้นคุณควรทราบว่าคุณได้ทำการเปลี่ยนแปลงอะไรและทำการเปลี่ยนแปลงน้อยที่สุดเท่าที่จะทำได้ หาก OP ทำการเปลี่ยนแปลงโดยเจตนาและทำการเปลี่ยนแปลงน้อยที่สุดเท่าที่จะเป็นไปได้เขาอาจจะไม่ได้ถามคำถามนี้เพราะมันจะชัดเจนสำหรับเขาว่าเกิดอะไรขึ้น เขาจะเปลี่ยนมาโครสำหรับ IN โดยไม่มีข้อผิดพลาดจากนั้นมาโครสำหรับ OUT ที่มีข้อผิดพลาดสองข้อซึ่งครั้งที่สองจะบ่นเกี่ยวกับอัฒภาคที่เขาเพิ่งเพิ่มเข้าไป
jmoreno

5
ดูเหมือนว่าถ้าคุณไม่ทำผิดพลาดในการใส่เครื่องหมายอัฒภาคที่ส่วนท้ายของคำสั่งพรีโปรเซสเซอร์คุณอาจไม่รู้ว่าคุณจะไม่รวมไว้ คุณสามารถนำไปใช้ตามมูลค่าคุณสามารถอ่านรหัสจำนวนมากและสังเกตว่าดูเหมือนจะไม่เคยอยู่ที่นั่น หรือ OP อาจทำให้สับสนโดยการรวมไว้ถามเกี่ยวกับข้อผิดพลาด "แปลกประหลาด" และค้นหาว่า: อ๊ะไม่จำเป็นต้องใช้อัฒภาคสำหรับคำสั่งพรีโปรเซสเซอร์! นี่คือการเขียนโปรแกรมไม่ใช่ตอนของ Scared Straight
user7116

14
@sixlettervariables: ใช่ แต่เมื่อโค้ดใช้ไม่ได้ขั้นตอนแรกที่ชัดเจนคือไปที่ "โอ้ตกลงแล้วสิ่งที่ฉันเปลี่ยนไปโดยไม่มีเหตุผลใด ๆ จากรหัสที่เขียนในหนังสือโดยผู้ประดิษฐ์ C อาจเป็น ปัญหาตอนนั้นฉันจะเลิกทำ "
Lightness Races ใน Orbit


34

ไม่ควรมีอัฒภาคหลังมาโคร

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

และมันควรจะเป็น

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

ขอบคุณอัฒภาคเป็นปัญหา อันที่ 2 พิมพ์ผิด!
César

21
ครั้งต่อไปโปรดวางที่แน่นอนรหัสที่คุณใช้โดยตรงจากโปรแกรมแก้ไขข้อความของคุณ
Lightness Races ในวงโคจร

@ TomalakGeret'kal ดีฉันไม่ได้และฉันจะ แต่คุณพบได้อย่างไร?
onemach

1
@onemach: คุณบอกว่า;นี่เป็นการพิมพ์ผิดที่ไม่มีผลกับปัญหาซึ่งหมายถึงการพิมพ์ผิดในคำถามของคุณแทนที่จะเป็นรหัสที่คุณใช้จริง
Lightness Races ใน Orbit

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 ++ โดยปกติ makefiles จะมีเป้าหมายให้หยุดหลังจากประมวลผลล่วงหน้า

สำหรับการอ้างอิงอย่างรวดเร็ว: คำถาม SO ครอบคลุมตัวเลือก - ฉันจะดูซอร์สไฟล์ C / C ++ ได้อย่างไรหลังจากประมวลผลล่วงหน้าใน Visual Studio . มันเริ่มต้นด้วย VC ++ แต่ยังมีตัวเลือก GCC กล่าวถึงลงมาด้านล่าง


7

ไม่ใช่ปัญหาอย่างแน่นอน แต่การประกาศmain()ลงวันที่ด้วยก็ควรเป็นเช่นนี้

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

คอมไพเลอร์จะถือว่าค่าส่งคืน int สำหรับฟังก์ชันโดยไม่มีหนึ่งและฉันแน่ใจว่าคอมไพเลอร์ / ตัวเชื่อมโยงจะหลีกเลี่ยงการไม่มีการประกาศสำหรับ argc / argv และการไม่มีค่าส่งคืน แต่ควรอยู่ที่นั่น


3
นั่นเป็นหนังสือที่ดี - หนึ่งในสองเล่มที่คุ้มค่าในขณะที่หนังสือเกี่ยวกับ C เท่าที่ฉันรู้ ฉันค่อนข้างมั่นใจว่ารุ่นที่ใหม่กว่านั้นเป็นไปตามมาตรฐาน ANSI C (อาจจะก่อน C99 ANSI C) มูลค่าอื่น ๆ ในขณะที่หนังสือเกี่ยวกับ C คือ Expert C Programming Deep C Secrets โดย Peter van der Linden
บิล

ฉันไม่เคยบอกว่ามันเป็น ฉันได้รับความเห็นเพียงว่าเพื่อให้สอดคล้องกับวิธีการทำสิ่งต่างๆในวันนี้หลักนั้นควรจะเปลี่ยน
บิล

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
ขอบคุณ! อันที่จริงรหัสใช้งานได้โดยไม่มีวงเล็บปีกกาในครั้งที่สอง
César

5
+1. ไม่ใช่แค่คลุมเครือ แต่ค่อนข้างอันตราย เมื่อ (ถ้า) คุณเพิ่มบรรทัดในifบล็อกของคุณในภายหลังหากคุณลืมเพิ่มวงเล็บปีกกาเนื่องจากตอนนี้บล็อกของคุณมีมากกว่าหนึ่งบรรทัดอาจใช้เวลาสักครู่ในการแก้ไขข้อผิดพลาดนั้น ...
The111

8
@ The111 ไม่เคยเกิดขึ้นกับฉัน ฉันยังไม่เชื่อว่านี่เป็นปัญหาจริงๆ ฉันใช้รูปแบบที่ไม่มีรั้งมานานกว่าทศวรรษแล้วฉันไม่เคยลืมที่จะเพิ่มวงเล็บปีกกาเมื่อขยายส่วนของบล็อก
คอนราดรูดอล์ฟ

1
@ The111: ในกรณีนี้ผู้ให้ข้อมูล SO ใช้เวลาเพียงไม่กี่นาที: P และถ้าคุณเป็นโปรแกรมเมอร์ที่สามารถเพิ่มข้อความลงในifอนุประโยคและ "ลืม" ในการอัปเดตเครื่องมือจัดฟันได้คุณก็จะไม่ โปรแกรมเมอร์ที่ดีมาก
Lightness Races ในวงโคจร

3

วิธีง่ายๆคือใช้วงเล็บเช่น {} สำหรับแต่ละอัน ifและelse:

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

2

ดังที่คำตอบอื่น ๆ ชี้ให้เห็นปัญหาอยู่ใน#defineและอัฒภาค เพื่อลดปัญหาเหล่านี้ฉันมักจะกำหนดค่าคงที่ของตัวเลขเป็นconst int:

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

วิธีนี้จะช่วยขจัดปัญหาต่างๆและปัญหาที่อาจเกิดขึ้นได้ ถูก จำกัด ด้วยสองสิ่ง:

  1. คอมไพเลอร์ของคุณต้องรองรับconstซึ่งโดยทั่วไปในปี 1988 ไม่เป็นความจริง แต่ตอนนี้คอมไพเลอร์ที่ใช้กันทั่วไปรองรับทั้งหมด (AFAIK constคือ "ยืม" จาก C ++)

  2. คุณไม่สามารถใช้ค่าคงที่เหล่านี้ในสถานที่พิเศษบางแห่งที่คุณต้องการค่าคงที่เหมือนสตริง แต่ฉันคิดว่าโปรแกรมของคุณไม่เป็นอย่างนั้น


อีกทางเลือกหนึ่งที่ฉันชอบคือ enums - สามารถใช้ในสถานที่พิเศษ (เช่นการประกาศอาร์เรย์) ที่const intไม่สามารถใน C.
Michael Burr
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.