ทำไม +++++ b ไม่ทำงาน


89

รหัสนี้ให้ข้อผิดพลาดต่อไปนี้:

ข้อผิดพลาด: ต้องการ lvalue เป็นตัวถูกดำเนินการส่วนเพิ่ม

แต่ถ้าผมใส่ช่องว่างตลอดa++ +และ++bแล้วมันก็ทำงานได้ดี

ข้อผิดพลาดหมายถึงอะไรในตัวอย่างแรก


3
เป็นเรื่องน่าแปลกใจที่ไม่มีใครค้นพบว่านิพจน์ที่คุณถามถึงนั้นถูกใช้เป็นตัวอย่างในมาตรฐาน C99 และ C11 มันให้คำอธิบายที่ดีเช่นกัน ฉันมีสิ่งนั้นอยู่ในคำตอบของฉัน
Shafik Yaghmour

@ShafikYaghmour - ที่ 'ตัวอย่างที่ 2' ใน C11 §6.4คำศัพท์องค์ประกอบ¶6 ข้อความระบุว่า"ส่วนย่อยของโปรแกรมx+++++yถูกแยกวิเคราะห์x ++ ++ + yซึ่งละเมิดข้อ จำกัด ของตัวดำเนินการส่วนเพิ่มแม้ว่าการแยกวิเคราะห์x ++ + ++ yอาจให้นิพจน์ที่ถูกต้องก็ตาม
Jonathan Leffler

คำตอบ:


98

printf("%d",a+++++b);ถูกตีความว่าเป็น(a++)++ + bไปตามกฎ Maximal Munch ! .

++(postfix) ไม่ได้ประเมินเป็น an lvalueแต่ต้องการให้ตัวถูกดำเนินการเป็นlvalueไฟล์.

! 6.4 / 4 กล่าวว่าโทเค็นก่อนการประมวลผลถัดไปเป็นลำดับอักขระที่ยาวที่สุดที่สามารถประกอบเป็นโทเค็นก่อนการประมวลผล "


181

คอมไพเลอร์เขียนเป็นขั้นตอน ขั้นตอนแรกเรียกว่า lexer และเปลี่ยนอักขระให้เป็นโครงสร้างสัญลักษณ์ ดังนั้น "++" จึงกลายเป็นenum SYMBOL_PLUSPLUSไฟล์. ต่อมาขั้นตอนตัวแยกวิเคราะห์จะเปลี่ยนสิ่งนี้ให้เป็นโครงสร้างไวยากรณ์นามธรรม แต่ไม่สามารถเปลี่ยนสัญลักษณ์ได้ คุณสามารถส่งผลกระทบต่อ lexer ได้โดยการใส่ช่องว่าง (ซึ่งสัญลักษณ์สิ้นสุดเว้นแต่จะอยู่ในเครื่องหมายคำพูด)

คำศัพท์ปกติมีความโลภ (มีข้อยกเว้นบางประการ) ดังนั้นรหัสของคุณจึงถูกตีความว่า

อินพุตไปยังโปรแกรมแยกวิเคราะห์เป็นกระแสของสัญลักษณ์ดังนั้นรหัสของคุณจะเป็นดังนี้:

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

คือ

ซึ่งก็โอเค. ตัวอย่างอื่น ๆ ของคุณ


27
+1 คำอธิบายที่ดี ฉันต้อง nitpick แม้ว่า: มันถูกต้องตามหลักไวยากรณ์ แต่ก็มีข้อผิดพลาดทางความหมาย (พยายามเพิ่มค่า lvalue ที่เป็นผลมาจากa++)

7
a++ผลลัพธ์ใน rvalue
Femaref

9
ในบริบทของ lexers อัลกอริทึม 'greedy' มักเรียกว่า Maximal Munch ( en.wikipedia.org/wiki/Maximal_munch )
JoeG

14
ดี. หลายภาษามีกรณีมุมที่แปลกประหลาดคล้าย ๆ กันซึ่งต้องขอบคุณ lexing แบบโลภ นี่เป็นหนึ่งแปลกจริงๆที่ทำให้การแสดงออกอีกต่อไปทำให้ดีขึ้น: ใน VBScript x = 10&987&&654&&321เป็นสิ่งผิดกฎหมาย แต่พิกลพอx = 10&987&&654&&&321เป็นกฎหมาย
Eric Lippert

1
ไม่มีอะไรเกี่ยวข้องกับความโลภและทั้งหมดเกี่ยวข้องกับลำดับและความสำคัญ ++ สูงกว่าแล้ว + ดังนั้นสอง ++ จะถูกทำก่อน +++++ b จะเป็น + ++ ++ b และไม่ใช่ ++ ++ + b เครดิต @MByD สำหรับลิงค์

30

lexer ใช้สิ่งที่โดยทั่วไปเรียกว่าอัลกอริทึม "Maximum munch" เพื่อสร้างโทเค็น นั่นหมายความว่าเมื่อมันอ่านอักขระมันจะอ่านอักขระไปเรื่อย ๆ จนกว่าจะพบบางสิ่งที่ไม่สามารถเป็นส่วนหนึ่งของโทเค็นเดียวกันกับสิ่งที่มีอยู่แล้ว (เช่นถ้ามันกำลังอ่านตัวเลขดังนั้นสิ่งที่มันมีก็คือตัวเลขถ้ามันเจอAก็รู้ดีว่าไม่สามารถเป็นส่วนหนึ่งของจำนวน. จึงหยุดและใบAในบัฟเฟอร์การป้อนข้อมูลเพื่อใช้เป็นจุดเริ่มต้นของโทเค็นถัดไป) จากนั้นจะส่งคืนโทเค็นนั้นไปยังตัวแยกวิเคราะห์

ในกรณีนี้หมายความว่า+++++ได้รับ lexed a ++ ++ + bเป็น เนื่องจากการเพิ่มหลังครั้งแรกให้ค่า r ค่าที่สองจึงไม่สามารถนำไปใช้กับมันได้และคอมไพเลอร์ให้ข้อผิดพลาด

เพียงแค่ FWIW ใน C ++ คุณสามารถโอเวอร์โหลดoperator++เพื่อให้ได้ค่า lvalue ซึ่งทำให้สิ่งนี้ใช้ได้ ตัวอย่างเช่น:

คอมไพล์และรัน (แม้ว่าจะไม่ทำอะไรเลย) ด้วยคอมไพเลอร์ C ++ ที่ฉันมีสะดวก (VC ++, g ++, Comeau)


1
"เช่นถ้ามันได้รับการอ่านตัวเลขดังนั้นสิ่งที่มันมีเป็นจำนวนถ้าพบข้อก็รู้ดีว่าไม่สามารถเป็นส่วนหนึ่งของหมายเลข" 16FAเป็นเลขฐานสิบหกสมบูรณ์ดีจำนวนที่มีเอ
orlp

1
@nightcracker: ใช่ แต่หากไม่มี0xจุดเริ่มต้นก็จะยังคงปฏิบัติ16ตามด้วยFAไม่ใช่เลขฐานสิบหกเดียว
Jerry Coffin

@Jerry Coffin: คุณไม่ได้บอกว่า0xไม่ใช่ส่วนหนึ่งของจำนวน
orlp

@nightcracker: ไม่ฉันไม่ได้ - เนื่องจากคนส่วนใหญ่ไม่พิจารณาxตัวเลขมันดูเหมือนไม่จำเป็นเลย
Jerry Coffin

14

ตัวอย่างที่แน่นอนนี้ครอบคลุมอยู่ในร่างมาตรฐาน C99 ( รายละเอียดเดียวกันใน C11 ) หัวข้อ6.4 องค์ประกอบของคำศัพท์ย่อหน้าที่ 4ซึ่งกล่าวว่า:

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

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

ย่อหน้ายังมีสองตัวอย่างที่สองเป็นคำถามที่ตรงกันทุกประการสำหรับคุณและเป็นดังนี้

ตัวอย่าง 2 ส่วนของโปรแกรม x +++++ y ถูกแยกวิเคราะห์เป็น x ++ ++ + y ซึ่งละเมิดข้อ จำกัด ของตัวดำเนินการที่เพิ่มขึ้นแม้ว่าการแยกวิเคราะห์ x ++ + ++ y อาจให้นิพจน์ที่ถูกต้อง

ซึ่งบอกเราว่า:

จะถูกแยกวิเคราะห์เป็น:

ซึ่งละเมิดข้อ จำกัด ในการเพิ่มโพสต์เนื่องจากผลลัพธ์ของการเพิ่มโพสต์ครั้งแรกคือค่า rvalue และการเพิ่มโพสต์ต้องใช้ค่า lvalue สิ่งนี้กล่าวถึงในส่วนตัวดำเนินการ6.5.2.4 เพิ่มและลด Postfixซึ่งระบุว่า ( เน้นของฉัน ):

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

และ

ผลลัพธ์ของตัวดำเนินการ postfix ++ คือค่าของตัวถูกดำเนินการ

หนังสือC ++ Gotchasยังครอบคลุมกรณีนี้ในGotcha #17 Maximal Munch Problemsซึ่งเป็นปัญหาเดียวกันในC ++เช่นกันและยังให้ตัวอย่างบางส่วน อธิบายว่าเมื่อจัดการกับชุดอักขระต่อไปนี้:

เครื่องวิเคราะห์คำศัพท์สามารถทำหนึ่งในสามสิ่ง:

  • รักษามันเป็นสามราชสกุล: -, >และ*
  • ถือว่าเป็นโทเค็นสองอัน: ->และ*
  • ถือว่าเป็นโทเค็นเดียว: ->*

สูงสุดขบฉันกฎช่วยให้สามารถหลีกเลี่ยงความคลุมเครือเหล่านี้ ผู้เขียนชี้ให้เห็นว่า ( ในบริบท C ++ ):

แก้ปัญหาได้มากกว่าที่เป็นสาเหตุ แต่ในสองสถานการณ์ที่พบบ่อยมันน่ารำคาญ

ตัวอย่างแรกจะเป็นเทมเพลตที่มีอาร์กิวเมนต์ของเทมเพลตเป็นเทมเพลต ( ซึ่งได้รับการแก้ไขใน C ++ 11 ) เช่น

ซึ่งแปลความหมายของวงเล็บมุมปิดเป็นตัวดำเนินการกะดังนั้นจึงต้องมีช่องว่างเพื่อแยกแยะ:

กรณีที่สองเกี่ยวข้องกับอาร์กิวเมนต์เริ่มต้นสำหรับพอยน์เตอร์ตัวอย่างเช่น:

จะถูกตีความว่าเป็น*=ตัวดำเนินการกำหนดวิธีแก้ปัญหาในกรณีนี้คือการตั้งชื่อพารามิเตอร์ในการประกาศ


คุณรู้หรือไม่ว่าส่วนใดของ C ++ 11 กล่าวว่ากฎการเคี้ยวสูงสุด 2.2.3, 2.5.3 น่าสนใจ แต่ไม่ชัดเจนเท่า C. >>กฎถูกถามที่: stackoverflow.com/questions/15785496/…
Ciro Santilli 郝海东冠状病六四事件法轮功

1
@CiroSantilli 巴拿馬文件六四事件法轮功ดูคำตอบนี้ได้ที่นี่
Shafik Yaghmour

ขอบคุณมากเป็นหนึ่งในส่วนที่ฉันชี้ไป พรุ่งนี้ฉันจะโหวตให้คุณเมื่อหมวกของฉันหลุด ;-)
Ciro Santilli 郝海东冠状病六四事件法轮功

12

คอมไพเลอร์ของคุณพยายามอย่างยิ่งที่จะแยกวิเคราะห์a+++++bและตีความว่าเป็น(a++)++ +bไฟล์. ตอนนี้ผลลัพธ์ของ post-Increment ( a++) ไม่ใช่lvalueกล่าวคือไม่สามารถโพสต์เพิ่มได้อีก

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



7

เพราะมันทำให้เกิดพฤติกรรมที่ไม่ได้กำหนด

อันไหน?

ใช่ทั้งคุณและผู้รวบรวมไม่รู้

แก้ไข:

เหตุผลที่แท้จริงคือเหตุผลที่คนอื่นพูด:

(a++)++ + bจะได้รับการตีความว่าเป็น

แต่การเพิ่มโพสต์ต้องใช้ lvalue (ซึ่งเป็นตัวแปรที่มีชื่อ) แต่ (a ++) ส่งกลับค่า rvalue ซึ่งไม่สามารถเพิ่มได้จึงนำไปสู่ข้อความแสดงข้อผิดพลาดที่คุณได้รับ

ขอบคุณคนอื่น ๆ ที่ชี้ให้เห็นสิ่งนี้


5
คุณสามารถพูดเหมือนกันสำหรับ +++ b - (a ++) + b และ a + (++ b) มีผลลัพธ์ที่แตกต่างกัน
Michael Chinen

4
จริงๆแล้ว postfix ++ มีลำดับความสำคัญสูงกว่าคำนำหน้า ++ ดังนั้นa+++bเสมอa++ + b
MByD

4
ฉันไม่คิดว่านี่เป็นคำตอบที่ถูก แต่ฉันคิดผิด ฉันคิดว่าตัวอักษรกำหนดให้เป็นa++ ++ +bซึ่งไม่สามารถแยกวิเคราะห์ได้
Lou Franco

2
ฉันไม่เห็นด้วยกับคำตอบนี้ 'พฤติกรรมที่ไม่ได้กำหนด' ค่อนข้างแตกต่างจากความไม่ชัดเจนของโทเค็น และฉันไม่คิดว่าปัญหาก็เช่นกัน
Jim Blackler

2
"มิฉะนั้น +++++ ขจะประเมินถึง ((A ++) ++) + B" ... มุมมองของฉันตอนนี้คือa+++++b ไม่(a++)++)+bประเมิน แน่นอนสำหรับ GCC หากคุณใส่วงเล็บเหล่านั้นและสร้างใหม่ข้อความแสดงข้อผิดพลาดจะไม่เปลี่ยนแปลง
Jim Blackler

5

ฉันคิดว่าคอมไพเลอร์เห็นว่าเป็น

c = ((a ++) ++) + b

++จะต้องมีค่าที่สามารถแก้ไขได้ a คือค่าที่สามารถแก้ไขได้ a++อย่างไรก็ตามเป็น 'rvalue' ซึ่งไม่สามารถแก้ไขได้

โดยวิธีการที่ผิดพลาดที่ผมเห็นใน GCC C จะเหมือนกัน lvalue required as increment operandแต่ต่างกันคำพูด:


0

ทำตามคำสั่ง precesion นี้

1. ++ (เพิ่มก่อน)

2. + - (การบวกหรือการลบ)

3. "x" + "y" เพิ่มทั้งสองลำดับ

int a = 5,b = 2; printf("%d",a++ + ++b); //a is 5 since it is post increment b is 3 pre increment return 0; //it is 5+3=8

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