เหตุใด c = ++ (a + b) จึงให้ข้อผิดพลาดในการคอมไพล์


111

หลังจากการค้นคว้าผมอ่านว่าผู้ประกอบการที่เพิ่มขึ้นนั้นจะต้องถูกดำเนินการที่จะมีการปรับแก้ข้อมูลของวัตถุ: https://en.wikipedia.org/wiki/Increment_and_decrement_operators

จากนี้ฉันเดาว่ามันทำให้เกิดข้อผิดพลาดในการคอมไพล์เนื่องจาก(a+b)เป็นจำนวนเต็มชั่วคราวดังนั้นจึงไม่สามารถแก้ไขได้

ความเข้าใจนี้ถูกต้องหรือไม่? นี่เป็นครั้งแรกที่ฉันพยายามค้นคว้าปัญหาดังนั้นหากมีสิ่งที่ควรค้นหาโปรดแนะนำ


35
นั่นไม่เลวในแง่ของการวิจัย คุณมาถูกทางแล้ว
StoryTeller - Unslander Monica

35
คุณคาดหวังว่าการแสดงออกจะเป็นอย่างไร?
qrdl

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

10
คุณต้องการให้ 1 กระจายระหว่าง a และ b อย่างไร "ดัชนีอาร์เรย์ควรเริ่มต้นที่ 0 หรือ 1 หรือไม่การประนีประนอม 0.5 ของฉันถูกปฏิเสธโดยที่ฉันคิดว่าการพิจารณาอย่างเหมาะสม" - Stan Kelly-Bootle
Andrew Morton

5
ฉันคิดว่าคำถามที่ตามมาคือเหตุใดคุณจึงต้องการทำเช่นนี้เมื่อc = a + b + 1ทำให้เจตนาของคุณชัดเจนขึ้นและพิมพ์สั้นลงด้วย ตัวดำเนินการเพิ่ม / ลดทำสองสิ่ง 1. พวกเขาและอาร์กิวเมนต์ของพวกเขาสร้างนิพจน์ (ที่สามารถใช้ได้เช่นในการวนซ้ำ) 2. พวกเขาแก้ไขอาร์กิวเมนต์ ในตัวอย่างของคุณคุณกำลังใช้คุณสมบัติ 1. แต่ไม่ใช่คุณสมบัติ 2 เนื่องจากคุณทิ้งอาร์กิวเมนต์ที่แก้ไขแล้ว ถ้าคุณไม่ต้องการคุณสมบัติ 2 และต้องการเพียงแค่นิพจน์คุณสามารถเขียนนิพจน์ได้เช่น x + 1 แทน x ++
Trevor

คำตอบ:


117

มันเป็นเพียงกฎนั่นคือทั้งหมดและอาจอยู่ที่ (1) ทำให้ง่ายต่อการเขียนคอมไพเลอร์ C และ (2) ไม่มีใครเชื่อมั่นว่าคณะกรรมการมาตรฐาน C จะผ่อนคลายมัน

ทางการพูดคุณสามารถเขียน++fooถ้าสามารถปรากฏบนด้านซ้ายมือของการแสดงออกที่ได้รับมอบหมายเช่นfoo foo = barเนื่องจากคุณเขียนไม่ได้a + b = barคุณจึงเขียนไม่ได้++(a + b)เช่นกัน

ไม่มีเหตุผลที่แท้จริงว่าทำไมเป็นa + bไม่ได้ผลในการชั่วคราวซึ่งสามารถทำงานและผลจากการที่เป็นคุณค่าของการแสดงออก++++(a + b)


4
ฉันคิดว่าจุด (1) กระทบเล็บบนหัว เพียงแค่ดูที่กฎสำหรับการทำให้เป็นจริงชั่วคราวใน C ++ สามารถทำให้ท้องของคน ๆ หนึ่ง (แต่ก็มีประสิทธิภาพ แต่ต้องพูดอย่างนั้น)
StoryTeller - Unslander Monica

4
@StoryTeller: อันที่จริงไม่เหมือนกับภาษา C ++ อันเป็นที่รักของเรา C ยังคงรวบรวมไว้ในแอสเซมบลีค่อนข้างเล็กน้อย
Bathsheba

29
นี่คือเหตุผลที่แท้จริง IMHO: มันจะเป็นความสับสนที่น่ากลัวหาก++บางครั้งมีผลข้างเคียงจากการแก้ไขบางสิ่งบางอย่างและบางครั้งก็ไม่เป็นเช่นนั้น
aschepler

5
@dng: อันที่จริงมันคือ; นั่นเป็นเหตุผลที่มีการนำคำว่า lvalue และ rvalue มาใช้แม้ว่าในปัจจุบันจะมีความซับซ้อนมากกว่านั้นก็ตาม (โดยเฉพาะใน C ++) ตัวอย่างเช่นค่าคงที่ไม่สามารถเป็นค่า lvalue ได้เช่น 5 = a ไม่สมเหตุสมผล
Bathsheba

6
@Bathsheba นั่นอธิบายว่าทำไม 5 ++ จึงทำให้เกิดข้อผิดพลาดในการคอมไพล์ด้วย
dng

40

มาตรฐาน C11 ระบุไว้ในหัวข้อ 6.5.3.1

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

และ "lvalue ที่ปรับเปลี่ยนได้" ได้อธิบายไว้ในหัวข้อ 6.3.2.1 ส่วนย่อย 1

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

ดังนั้นจึง(a+b)ไม่ใช่ lvalue ที่ปรับเปลี่ยนได้ดังนั้นจึงไม่มีสิทธิ์สำหรับตัวดำเนินการส่วนเพิ่มคำนำหน้า


1
ข้อสรุปของคุณจากคำจำกัดความเหล่านี้ขาดหายไป ... คุณต้องการบอกว่า (a + b) ไม่สามารถกำหนดวัตถุได้ แต่ย่อหน้าเหล่านี้ไม่อนุญาตให้ทำเช่นนั้น
hkBst

21

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


12

ฉันคิดว่าคุณตอบคำถามของคุณเองเป็นส่วนใหญ่ ฉันอาจทำการเปลี่ยนแปลงเล็กน้อยกับวลีของคุณและแทนที่ "ตัวแปรชั่วคราว" ด้วย "rvalue" ตามที่ C.Gibbons กล่าวถึง

ตัวแปรเงื่อนไขอาร์กิวเมนต์ตัวแปรชั่วคราวและอื่น ๆ จะชัดเจนยิ่งขึ้นเมื่อคุณเรียนรู้เกี่ยวกับโมเดลหน่วยความจำของ C (ดูเหมือนภาพรวมที่ดี: https://www.geeksforgeeks.org/memory-layout-of-c-program/ )

คำว่า "rvalue" อาจดูไม่ชัดเจนเมื่อคุณเพิ่งเริ่มต้นดังนั้นฉันหวังว่าสิ่งต่อไปนี้จะช่วยในการพัฒนาสัญชาตญาณเกี่ยวกับเรื่องนี้

Lvalue / rvalue กำลังพูดถึงด้านต่างๆของเครื่องหมายเท่ากับ (ตัวดำเนินการกำหนดค่า): lvalue = ด้านซ้ายมือ (ตัวพิมพ์เล็ก L ไม่ใช่ "one") rvalue = ด้านขวามือ

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

x = y * 3

ในการประกอบ pseudocode อาจมีลักษณะคล้ายกับตัวอย่างของเล่นนี้:

load register A with the value at memory address y
load register B with a value of 3
multiply register A and B, saving the result in A
write register A to memory address x

ตัวดำเนินการ ++ (และของมัน - คู่กัน) จำเป็นต้องมี "ที่ใดที่หนึ่ง" เพื่อแก้ไขโดยพื้นฐานแล้วอะไรก็ตามที่สามารถทำงานเป็นค่า lvalue ได้

การทำความเข้าใจโมเดลหน่วยความจำ C จะเป็นประโยชน์เพราะคุณจะได้รับความคิดที่ดีขึ้นในหัวของคุณเกี่ยวกับการส่งผ่านอาร์กิวเมนต์ไปยังฟังก์ชันและ (ในที่สุด) วิธีทำงานกับการจัดสรรหน่วยความจำแบบไดนามิกเช่นฟังก์ชัน malloc () ด้วยเหตุผลที่คล้ายกันคุณอาจศึกษาการเขียนโปรแกรมแอสเซมบลีอย่างง่ายในบางจุดเพื่อให้เข้าใจได้ดีขึ้นว่าคอมไพเลอร์กำลังทำอะไรอยู่ นอกจากนี้ถ้าคุณกำลังใช้gccที่-Sตัวเลือก "หยุดชะงักหลังจากที่ขั้นตอนของการรวบรวมที่เหมาะสม; ไม่ประกอบ." น่าสนใจ (แม้ว่าฉันจะแนะนำให้ลองใช้กับส่วนรหัสขนาดเล็ก )

นอกเหนือจากนี้: คำสั่ง ++ มีมาตั้งแต่ปี 2512 (แม้ว่าจะเริ่มต้นในรุ่นก่อนของ C แต่ B):

(ของ Ken Thompson) สังเกตว่าการแปล ++ x มีขนาดเล็กกว่า x = x + 1 "

หลังจากอ้างอิงวิกิพีเดียนั้นจะนำคุณไปสู่การเขียนที่น่าสนใจโดย Dennis Ritchie (ตัว "R" ใน "K&R C") เกี่ยวกับประวัติศาสตร์ของภาษาซีโดยลิงก์ที่นี่เพื่อความสะดวก: http://www.bell-labs.com/ usr / dmr / www / chist.htmlซึ่งคุณสามารถค้นหา "++"


6

เหตุผลก็คือมาตรฐานกำหนดให้ตัวถูกดำเนินการเป็น lvalue นิพจน์(a+b)ไม่ใช่ lvalue ดังนั้นจึงไม่อนุญาตให้ใช้ตัวดำเนินการส่วนเพิ่ม

ตอนนี้อาจมีคนพูดว่า"ตกลงนั่นเป็นเหตุผลจริงๆ แต่จริงๆแล้วไม่มีเหตุผล * จริง * นอกเหนือไปจากนั้น"แต่โชคไม่ดีที่ข้อความเฉพาะของวิธีการทำงานของตัวดำเนินการนั้นจำเป็นต้องเป็นเช่นนั้น

นิพจน์ ++ E เทียบเท่ากับ (E + = 1)

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

ตอนนี้คำจำกัดความสามารถอ้างอิงได้เล็กน้อย (ฉันคิดว่ามันไม่ใช่ C แต่เป็นมรดกตกทอดของ B) แต่การทำเช่นนั้นโดยพื้นฐานแล้วจะเปลี่ยนภาษาเป็นภาษาที่ไม่สามารถใช้ร่วมกับเวอร์ชันก่อนหน้าได้อีกต่อไป เนื่องจากผลประโยชน์ที่เป็นไปได้นั้นค่อนข้างน้อย แต่ผลกระทบที่เป็นไปได้นั้นมีมากซึ่งไม่เคยเกิดขึ้นและอาจจะไม่มีวันเกิดขึ้น

หากคุณพิจารณา C ++ เพิ่มเติมจาก C (คำถามถูกแท็ก C แต่มีการอภิปรายเกี่ยวกับตัวดำเนินการมากเกินไป) เรื่องราวจะซับซ้อนยิ่งขึ้น ในภาษา C มันยากที่จะจินตนาการว่าอาจเป็นเช่นนั้นได้ แต่ใน C ++ ผลลัพธ์ของ(a+b)มันอาจเป็นสิ่งที่คุณไม่สามารถเพิ่มได้เลยหรือการเพิ่มขึ้นอาจมีผลข้างเคียงที่สำคัญมาก (ไม่ใช่แค่การเพิ่ม 1) คอมไพเลอร์ต้องสามารถรับมือกับสิ่งนั้นได้และวินิจฉัยกรณีที่มีปัญหาเมื่อเกิดขึ้น ใน lvalue นั่นยังคงเป็นเรื่องเล็กน้อยในการตรวจสอบ ไม่ใช่เช่นนั้นสำหรับการแสดงออกที่ไร้สาระใด ๆ ภายในวงเล็บที่คุณโยนสิ่งที่น่าสงสาร
นี่ไม่ใช่เหตุผลที่แท้จริงว่าทำไมถึงทำไม่ได้ ทำได้ แต่แน่นอนว่าให้คำอธิบายว่าทำไมคนที่นำสิ่งนี้ไปใช้ไม่ได้มีความสุขอย่างแน่นอนที่จะเพิ่มคุณสมบัติดังกล่าวซึ่งสัญญาว่าจะเป็นประโยชน์ต่อคนจำนวนน้อยมาก



3

++ พยายามให้ค่าแก่ตัวแปรดั้งเดิมและเนื่องจาก (a + b) เป็นค่าชั่วคราวจึงไม่สามารถดำเนินการได้ และเป็นกฎของหลักการเขียนโปรแกรม C เพื่อให้การเขียนโปรแกรมง่ายขึ้น แค่นั้นแหละ.


2

เมื่อนิพจน์ ++ (a + b) ดำเนินการตัวอย่างเช่น:

int a, b;
a = 10;
b = 20;
/* NOTE :
 //step 1: expression need to solve first to perform ++ operation over operand
   ++ ( exp );
// in your case 
   ++ ( 10 + 20 );
// step 2: result of that inc by one 
   ++ ( 30 );
// here, you're applying ++ operator over constant value and it's invalid use of ++ operator 
*/
++(a+b);
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.