เหตุใดคอมไพเลอร์จึงไม่รายงานอัฒภาคที่หายไป


115

ฉันมีโปรแกรมง่ายๆนี้:

#include <stdio.h>

struct S
{
    int i;
};

void swap(struct S *a, struct S *b)
{
    struct S temp;
    temp = *a    /* Oops, missing a semicolon here... */
    *a = *b;
    *b = temp;
}

int main(void)
{
    struct S a = { 1 };
    struct S b = { 2 };

    swap(&a, &b);
}

ตามที่เห็น ในเช่น ideone.comสิ่งนี้ทำให้เกิดข้อผิดพลาด:

prog.c: In function 'swap':
prog.c:12:5: error: invalid operands to binary * (have 'struct S' and 'struct S *')
     *a = *b;
     ^

เหตุใดคอมไพเลอร์ไม่ตรวจพบอัฒภาคที่หายไป


หมายเหตุ: คำถามนี้และคำตอบได้รับแรงบันดาลใจจากคำถามนี้นี้ แม้ว่าจะมีคำถามอื่น ๆ ที่คล้ายกันนี้ แต่ฉันไม่พบสิ่งใดที่กล่าวถึงความจุรูปแบบอิสระของภาษา C ซึ่งเป็นสาเหตุของข้อผิดพลาดนี้และข้อผิดพลาดที่เกี่ยวข้อง


16
โพสต์นี้มีแรงบันดาลใจอะไร
ราหู

10
@TavianBarnes การค้นพบ คำถามอื่นไม่สามารถค้นพบได้เมื่อค้นหาปัญหาประเภทนี้ สามารถแก้ไขได้ด้วยวิธีนี้ แต่จะต้องมีการเปลี่ยนแปลงเล็กน้อยถึงมากทำให้ IMO เป็นคำถามที่แตกต่างไปจากเดิม
เพื่อนโปรแกรมเมอร์บางคน

4
@TavianBarnes: คำถามเดิมกำลังถามหาข้อผิดพลาด คำถามนี้ถามว่าทำไมคอมไพเลอร์ดูเหมือน (อย่างน้อย OP) จะรายงานตำแหน่งของข้อผิดพลาดผิด
TonyK

80
ชี้ให้ไตร่ตรอง: หากคอมไพเลอร์สามารถตรวจจับเซมิโคลอนที่ขาดหายไปได้อย่างเป็นระบบภาษาก็ไม่จำเป็นต้องเริ่มต้นด้วยเซมิโคลอน
Euro Micelli

5
งานคอมไพเลอร์คือการรายงานข้อผิดพลาด เป็นหน้าที่ของคุณที่จะต้องค้นหาสิ่งที่ต้องเปลี่ยนแปลงเพื่อแก้ไขข้อผิดพลาด
David Schwartz

คำตอบ:


213

C เป็นภาษารูปแบบอิสระ ซึ่งหมายความว่าคุณสามารถจัดรูปแบบได้หลายวิธีและยังคงเป็นโปรแกรมทางกฎหมาย

ตัวอย่างเช่นคำสั่งเช่น

a = b * c;

สามารถเขียนได้เช่น

a=b*c;

หรือชอบ

a
=
b
*
c
;

ดังนั้นเมื่อคอมไพเลอร์เห็นเส้น

temp = *a
*a = *b;

มันคิดว่ามันหมายถึง

temp = *a * a = *b;

นั่นไม่ใช่นิพจน์ที่ถูกต้องและคอมไพเลอร์จะบ่นเกี่ยวกับสิ่งนั้นแทนอัฒภาคที่ขาดหายไป สาเหตุที่ไม่ถูกต้องเนื่องจากaเป็นตัวชี้ไปยังโครงสร้างดังนั้นจึง*a * aพยายามคูณอินสแตนซ์โครงสร้าง ( *a) ด้วยตัวชี้ไปยังโครงสร้าง ( a)

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

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

และบางครั้งมันก็แย่ลงไปอีก: ถ้าคุณรวมไฟล์ส่วนหัวสองไฟล์ (หรือมากกว่า) และไฟล์แรกมีการประกาศที่ไม่สมบูรณ์ส่วนใหญ่อาจเป็นข้อผิดพลาดทางไวยากรณ์ที่ระบุไว้ในไฟล์ส่วนหัวที่สอง


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

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


16
ใน C ++ temp = *a * a = *b อาจเป็นนิพจน์ที่ถูกต้องหากoperator*มีการโอเวอร์โหลด (คำถามถูกแท็กเป็น“ C”)
dan04

13
@ dan04: ถ้ามีใครทำแบบนั้นจริง ... NOPE!
Kevin

2
+1 สำหรับคำแนะนำเกี่ยวกับ (a) เริ่มต้นด้วยข้อผิดพลาดที่รายงานครั้งแรก และ (b) มองย้อนกลับไปจากจุดที่รายงานข้อผิดพลาด คุณรู้ว่าคุณเป็นโปรแกรมเมอร์ตัวจริงเมื่อคุณดูบรรทัดโดยอัตโนมัติก่อนที่จะมีการรายงานข้อผิดพลาด :-)
TripeHound

@TripeHound โดยเฉพาะอย่างยิ่งเมื่อมีข้อผิดพลาดจำนวนมากหรือบรรทัดที่รวบรวมไว้ก่อนหน้านี้มีข้อผิดพลาด ...
Tin Wizard

1
ตามปกติแล้วเมตามีคนถามแล้ว - meta.stackoverflow.com/questions/266663/…
StoryTeller - Unslander Monica

27

เหตุใดคอมไพเลอร์ไม่ตรวจพบอัฒภาคที่หายไป

มีสามสิ่งที่ต้องจำ

  1. ปลายบรรทัดใน C เป็นเพียงช่องว่างธรรมดา
  2. *ใน C สามารถเป็นได้ทั้งยูนารีและตัวดำเนินการไบนารี ในฐานะที่เป็นตัวดำเนินการยูนารีหมายถึง "dereference" ในฐานะตัวดำเนินการไบนารีหมายถึง "คูณ"
  3. ความแตกต่างระหว่างตัวดำเนินการยูนารีและตัวดำเนินการไบนารีนั้นพิจารณาจากบริบทที่เห็น

ผลลัพธ์ของข้อเท็จจริงทั้งสองนี้คือเมื่อเราแยกวิเคราะห์

 temp = *a    /* Oops, missing a semicolon here... */
 *a = *b;

ตัวแรกและตัวสุดท้าย*ตีความว่ายูนารี แต่ตัวที่สอง*ถูกตีความว่าเป็นไบนารี จากมุมมองของไวยากรณ์สิ่งนี้ดูใช้ได้

หลังจากแยกวิเคราะห์เมื่อคอมไพเลอร์พยายามตีความตัวดำเนินการในบริบทของชนิดตัวถูกดำเนินการที่เห็นข้อผิดพลาด


4

คำตอบที่ดีข้างต้น แต่ฉันจะอธิบายอย่างละเอียด

temp = *a *a = *b;

นี่เป็นกรณีx = y = z;ที่ทั้งสองxและyถูกกำหนดค่าของzมีการกำหนดค่าของ

สิ่งที่คุณกำลังพูดคือ the contents of address (a times a) become equal to the contents of b, as does tempสิ่งที่คุณกำลังพูดคือ

ในระยะสั้น*a *a = <any integer value>เป็นคำสั่งที่ถูกต้อง ตามที่ระบุไว้ก่อนหน้านี้ตัวแรกจะ*หักล้างตัวชี้ในขณะที่ค่าที่สองคูณสองค่า


3
การอ้างอิงจะมีความสำคัญเป็นอันดับแรกดังนั้นจึงเป็น (เนื้อหาของที่อยู่ a) ครั้ง (ตัวชี้ไปที่ a) คุณสามารถบอกได้เนื่องจากข้อผิดพลาดในการคอมไพล์ระบุว่า "ตัวถูกดำเนินการเป็นไบนารีไม่ถูกต้อง * (มี 'struct S' และ 'struct S *') ซึ่งเป็นสองประเภทนี้
dascandy

ฉันเขียนโค้ดก่อน C99 ดังนั้นจึงไม่มีบูล :-) แต่คุณทำได้ดี (+1) แม้ว่าลำดับการมอบหมายงานไม่ได้เป็นคำตอบของฉันจริงๆ
Mawg กล่าวคืนสถานะโมนิกา

1
แต่ในกรณีyนี้ไม่ใช่ตัวแปร แต่เป็นนิพจน์*a *aและคุณไม่สามารถกำหนดให้กับผลลัพธ์ของการคูณได้
Barmar

@Barmar จริง ๆ แต่คอมไพเลอร์ไม่ได้ไปไกลขนาดนั้นได้ตัดสินใจแล้วว่าตัวถูกดำเนินการกับ "ไบนารี *" ไม่ถูกต้องก่อนที่จะดูที่ตัวดำเนินการกำหนด
plugwash

3

คอมไพเลอร์ส่วนใหญ่แยกวิเคราะห์ไฟล์ต้นฉบับตามลำดับและรายงานบรรทัดที่พบว่ามีบางอย่างผิดปกติ 12 บรรทัดแรกของโปรแกรม C ของคุณอาจเป็นจุดเริ่มต้นของโปรแกรม C ที่ถูกต้อง (ปราศจากข้อผิดพลาด) 13 บรรทัดแรกของโปรแกรมของคุณไม่ได้ คอมไพเลอร์บางตัวจะจดบันทึกตำแหน่งของสิ่งที่พบซึ่งไม่ใช่ข้อผิดพลาดในตัวมันเองและในกรณีส่วนใหญ่จะไม่ทำให้เกิดข้อผิดพลาดในโค้ดในภายหลัง แต่อาจใช้ร่วมกับอย่างอื่นไม่ได้ ตัวอย่างเช่น:

int foo;
...
float foo;

การประกาศint foo;ด้วยตัวเองจะดีอย่างสมบูรณ์ float foo;ในทำนองเดียวกันการประกาศ คอมไพเลอร์บางตัวอาจบันทึกหมายเลขบรรทัดที่การประกาศครั้งแรกปรากฏขึ้นและเชื่อมโยงข้อความแสดงข้อมูลกับบรรทัดนั้นเพื่อช่วยให้โปรแกรมเมอร์ระบุกรณีที่คำจำกัดความก่อนหน้านี้เป็นข้อผิดพลาด คอมไพเลอร์อาจเก็บหมายเลขบรรทัดที่เกี่ยวข้องกับบางอย่างเช่น a doซึ่งสามารถรายงานได้หากการเชื่อมโยงwhileไม่ปรากฏในตำแหน่งที่ถูกต้อง สำหรับกรณีที่ตำแหน่งที่เป็นไปได้ของปัญหาจะอยู่ก่อนบรรทัดที่พบข้อผิดพลาดในทันทีอย่างไรก็ตามคอมไพเลอร์โดยทั่วไปไม่ต้องกังวลกับการเพิ่มรายงานพิเศษสำหรับตำแหน่ง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.