ทำไม x = x ++ ไม่ได้กำหนด?


19

มันไม่ได้กำหนดเพราะมันจะแก้ไขxสองครั้งระหว่างจุดลำดับ มาตรฐานบอกว่ามันไม่ได้กำหนดดังนั้นมันจึงไม่ได้กำหนด
เท่าที่ฉันรู้

แต่ทำไม

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

สมมติว่าเราต้องสร้าง C ใหม่ในวันนี้ ฉันอยากจะแนะนำกฎง่ายๆสำหรับการแสดงออกเช่นx=x++ซึ่งดูเหมือนว่าฉันจะทำงานได้ดีกว่ากฎที่มีอยู่
ฉันต้องการรับความคิดเห็นของคุณเกี่ยวกับกฎที่แนะนำเมื่อเปรียบเทียบกับกฎที่มีอยู่หรือคำแนะนำอื่น ๆ

กฎที่แนะนำ:

  1. ระหว่างจุดลำดับลำดับของการประเมินผลจะไม่ได้รับการกำหนด
  2. ผลข้างเคียงเกิดขึ้นทันที

ไม่มีพฤติกรรมที่ไม่ได้กำหนดที่เกี่ยวข้อง นิพจน์ประเมินค่านี้หรือว่า แต่จะไม่ฟอร์แมตฮาร์ดดิสก์ของคุณอย่างแน่นอน (ฉันไม่เคยเห็นการใช้งานที่x=x++ฟอร์แมตฮาร์ดดิสก์มาก่อน)

ตัวอย่างนิพจน์

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

  2. x++ + ++x- ที่เพิ่มสองครั้งเพื่อประเมินx แม้ว่าอาจประเมินทั้งสองด้านก่อนผลลัพธ์ก็คือ(ด้านซ้ายก่อน) หรือ(ด้านขวาก่อน)2*x+2
    x + (x+2)(x+1) + (x+1)

  3. x = x + (x=3)- ไม่ได้, xการตั้งค่าเป็นอย่างใดอย่างหนึ่งหรือx+3 ถ้าด้านขวาได้รับการประเมินครั้งแรกก็ นอกจากนี้ยังเป็นไปได้ว่ามีการประเมินครั้งแรกจึงเป็น ในกรณีใดกรณีหนึ่งการมอบหมายจะเกิดขึ้นทันทีเมื่อประเมินผลดังนั้นค่าที่เก็บไว้จะถูกเขียนทับโดยการมอบหมายอื่น ๆ6
    x+3x=33+3x=3x=3

  4. x+=(x=3)- กำหนดไว้อย่างดีตั้งค่าxเป็น 6
    คุณสามารถยืนยันว่านี่เป็นเพียงการจดชวเลขสำหรับการแสดงออกข้างต้น
    แต่ฉันบอกว่า+=จะต้องดำเนินการหลังจากx=3และไม่ได้อยู่ในสองส่วน (อ่านxประเมินx=3เพิ่มและเก็บค่าใหม่)

ข้อดีคืออะไร

ความคิดเห็นบางอย่างยกประเด็นที่ดีนี้
ฉันไม่คิดว่านิพจน์เช่นx=x++ควรใช้ในรหัสปกติ
ที่จริงแล้วฉันเข้มงวดกว่านั้นมาก - ฉันคิดว่าการใช้งานที่ดีเพียงอย่างเดียวสำหรับx++ในx++;คนเดียว

อย่างไรก็ตามฉันคิดว่ากฎของภาษาต้องเรียบง่ายที่สุดเท่าที่จะเป็นไปได้ มิฉะนั้นโปรแกรมเมอร์ก็ไม่เข้าใจ กฎห้ามเปลี่ยนตัวแปรสองครั้งระหว่างจุดลำดับแน่นอนว่าโปรแกรมเมอร์ส่วนใหญ่ไม่เข้าใจ

กฎพื้นฐานมาก ๆ คือ:
ถ้า A ถูกต้องและ B นั้นถูกต้องและรวมเข้าด้วยกันอย่างถูกต้องผลลัพธ์จะใช้ได้
xเป็นค่า L ที่ถูกต้องx++เป็นนิพจน์ที่ถูกต้องและ=เป็นวิธีที่ถูกต้องในการรวมค่า L และนิพจน์ดังนั้นทำไมx=x++จึงไม่ถูกกฎหมาย
มาตรฐาน C สร้างข้อยกเว้นที่นี่และข้อยกเว้นนี้ทำให้กฎมีความซับซ้อน คุณสามารถค้นหาstackoverflow.comและดูว่าข้อยกเว้นนี้ทำให้ผู้คนสับสนมากแค่ไหน
ดังนั้นฉันพูด - กำจัดความสับสนนี้

=== สรุปคำตอบ ===

  1. ทำไมทำอย่างนั้น?
    ฉันพยายามอธิบายในหัวข้อข้างต้น - ฉันต้องการให้กฎ C เป็นเรื่องง่าย

  2. ศักยภาพในการปรับให้เหมาะสม:
    สิ่งนี้ใช้เสรีภาพบางส่วนจากคอมไพเลอร์ แต่ฉันไม่เห็นสิ่งใดที่ทำให้ฉันมั่นใจว่าอาจมีความสำคัญ
    การปรับให้เหมาะสมส่วนใหญ่ยังคงสามารถทำได้ ตัวอย่างเช่นa=3;b=5;สามารถจัดลำดับใหม่แม้ว่ามาตรฐานจะระบุคำสั่งซื้อก็ตาม นิพจน์เช่นa=b[i++]ยังสามารถปรับให้เหมาะสมในทำนองเดียวกัน

  3. คุณไม่สามารถเปลี่ยนมาตรฐานที่มีอยู่
    ฉันยอมรับฉันทำไม่ได้ ฉันไม่เคยคิดว่าฉันจะสามารถไปข้างหน้าและเปลี่ยนมาตรฐานและคอมไพเลอร์ได้ ฉันแค่อยากจะคิดว่าถ้าสิ่งต่าง ๆ สามารถทำได้แตกต่างกัน


10
ทำไมสิ่งนี้จึงสำคัญสำหรับคุณ มันควรจะถูกกำหนดและถ้าเป็นเช่นนั้นทำไม? การกำหนดxให้ตัวเองมีไม่มากนักและหากคุณต้องการเพิ่มxคุณสามารถพูดได้ว่าx++;- ไม่จำเป็นต้องมอบหมาย ฉันว่ามันไม่ควรนิยามเพราะมันยากที่จะจำสิ่งที่ควรจะเกิดขึ้น
คาเลบ

4
ในความคิดของฉันนี่เป็นคำถามที่ดี ("ผู้ชายบางคนมองสิ่งต่าง ๆ อย่างที่พวกเขาเป็นและถามว่าทำไมฉันถึงฝันถึงสิ่งที่ไม่เคยเป็นมาก่อนและถามว่าทำไมไม่") มัน (ในความคิดของฉัน) คำถามเกี่ยวกับการออกแบบภาษาอย่างหมดจดโดยใช้ไวยากรณ์ C เป็นตัวอย่างไม่ใช่คำถามเกี่ยวกับไวยากรณ์ C ส่วนตัวแล้วคิดว่าเหตุผลที่เราไม่ได้กำหนดพฤติกรรมสำหรับนิพจน์เช่น x ++ + ++ x หรือ x = x ++ นั้นเป็นเพราะมีความเป็นไปได้ที่พวกมันจะถูกอ่านผิด
Jamie Taylor

5
@ugoren: ทำไมคุณต้องทำนายผลลัพธ์ ไม่มีใครในใจที่ถูกต้องจะเขียนโค้ดแบบนั้น (ดังที่ได้กล่าวไว้หลายครั้ง) แม้ว่าคุณจะเขียนโค้ดแบบนี้มันจะถูกปฏิเสธในการตรวจสอบโค้ดครั้งแรก ดังนั้นไม่จำเป็นต้องกำหนดพฤติกรรมและให้โอกาสที่ดีที่สุดในการปรับให้เหมาะสมที่สุด ในทุกตัวอย่างที่คุณเสนอฉันจะยิงใครสักคนถ้าพวกเขาเพิ่มมันลงในฐานรหัส
Martin York

3
ฉันจะพบคำถามที่น่าสนใจเพิ่มเติมว่าทำไมไม่มีข้อผิดพลาดในการเขียนนี้ แน่นอนว่าคอมไพเลอร์สามารถตรวจพบว่ามันเป็นพฤติกรรมที่ไม่ได้กำหนดและดังนั้นจึงไม่อาจเป็นสิ่งที่ผู้ใช้ต้องการจริง ๆ ดังนั้นทำไมจึงไม่มีข้อผิดพลาด? ฉันเข้าใจบางกรณีของ bahavour ที่ไม่ได้กำหนดนั้นยากที่จะตรวจพบ แต่นี่ไม่ใช่ t
JohnB

3
" กฎห้ามเปลี่ยนตัวแปรสองครั้งระหว่างคะแนนตามลำดับเป็นกฎที่โปรแกรมเมอร์ส่วนใหญ่ไม่เข้าใจ " - คุณมีหลักฐานใด ๆ หรือไม่? คำถามส่วนใหญ่ที่ฉันเคยเห็นถูกถามเพราะโปรแกรมเมอร์ไม่ทราบเกี่ยวกับกฎ มีหลักฐานอะไรบ้างที่พวกเขาส่วนใหญ่ยังไม่เข้าใจหลังจากอธิบายแล้ว?
รักษาความปลอดภัย

คำตอบ:


24

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

y = x++ + ++x;

อ่านได้มากกว่า

y = 2*x + 2;
x += 2;

ระบุว่าการเปลี่ยนแปลงดังกล่าวเป็นพื้นฐานอย่างมากและทำลายฐานรหัสที่มีอยู่


1
ฉันเพิ่มส่วน "ทำไม" ในคำถามของฉัน ฉันไม่แนะนำให้ใช้การแสดงออกเหล่านี้ แต่ฉันสนใจที่จะมีกฎง่ายๆเพื่อบอกความหมายของการแสดงออก
ugoren

นอกจากนี้การเปลี่ยนแปลงนี้จะไม่ทำลายรหัสที่มีอยู่เว้นแต่ว่าจะเรียกใช้การทำงานที่ไม่ได้กำหนด ช่วยแก้ให้ด้วยนะถ้าฉันผิด.
ugoren

3
คำตอบเชิงปรัชญาที่มากขึ้น: ปัจจุบันยังไม่ได้กำหนด หากโปรแกรมเมอร์ไม่ใช้มันคุณไม่จำเป็นต้องเข้าใจนิพจน์ดังกล่าวเพราะไม่ควรมีรหัสใด ๆ หากมีความต้องการให้คุณเข้าใจพวกเขาเห็นได้ชัดว่าจะต้องมีรหัสจำนวนมากออกมาซึ่งขึ้นอยู่กับพฤติกรรมที่ไม่ได้กำหนด ;)
รักษาความปลอดภัย

1
โดยนิยามแล้วจะไม่ทำลาย codebase ใด ๆ ที่มีอยู่เพื่อกำหนดพฤติกรรม หากพวกเขามี UB พวกเขาถูกทำลายแล้ว
DeadMG

1
@ugoren: ส่วน "ทำไม" ของคุณยังไม่ตอบคำถามเชิงปฏิบัติ: เหตุใดคุณจึงต้องการให้นิพจน์ประหลาดนี้ในรหัสของคุณ หากคุณไม่สามารถหาคำตอบที่น่าเชื่อถือได้การสนทนาทั้งหมดก็เป็นสิ่งที่สงสัย
Mike Baranczak

20

อาร์กิวเมนต์ที่ทำให้พฤติกรรมที่ไม่ได้กำหนดนี้ช่วยให้การเพิ่มประสิทธิภาพที่ดีขึ้นไม่ได้อ่อนแอในวันนี้ ที่จริงแล้ววันนี้มันแข็งแกร่งกว่าเดิมมากเมื่อ C เป็นใหม่

เมื่อ C คือใหม่เครื่องจักรที่สามารถใช้ประโยชน์จากสิ่งนี้เพื่อการปรับให้เหมาะสมที่สุดนั้นส่วนใหญ่เป็นแบบจำลองทางทฤษฎี ผู้คนได้พูดคุยเกี่ยวกับความเป็นไปได้ของการสร้างซีพียูที่คอมไพเลอร์จะสั่ง CPU เกี่ยวกับคำสั่งที่สามารถ / ควรดำเนินการควบคู่กับคำแนะนำอื่น ๆ พวกเขาชี้ให้เห็นถึงความจริงที่ว่าการอนุญาตให้สิ่งนี้มีพฤติกรรมที่ไม่ได้กำหนดนั้นหมายความว่าบนซีพียูดังกล่าวหากมีอยู่จริงคุณสามารถกำหนดส่วน "เพิ่ม" ของคำสั่งให้ดำเนินการควบคู่กับส่วนที่เหลือของคำสั่ง ขณะที่พวกเขาพูดถูกเกี่ยวกับทฤษฏีนั้นในเวลานั้นมีฮาร์ดแวร์เล็กน้อยที่สามารถใช้ประโยชน์จากความเป็นไปได้นี้

นั่นไม่ได้เป็นเพียงแค่ทฤษฎีอีกต่อไป ขณะนี้มีฮาร์ดแวร์ในการผลิตและการใช้งานกว้าง (เช่น Itanium, VLIW DSPs) ที่สามารถจริงๆใช้ประโยชน์จากนี้ พวกเขาจริงๆทำช่วยให้คอมไพเลอร์ในการสร้างกระแสการเรียนการสอนที่ระบุว่าคำแนะนำ X, Y และ Z ทั้งหมดจะสามารถดำเนินการในแบบคู่ขนาน นี่ไม่ใช่โมเดลเชิงทฤษฎีอีกต่อไป - เป็นฮาร์ดแวร์จริงที่ใช้งานจริงและใช้งานได้จริง

IMO การทำให้พฤติกรรมที่กำหนดไว้นี้ใกล้เคียงกับ"วิธีแก้ปัญหา" ที่แย่ที่สุดที่เป็นไปได้ของปัญหา คุณไม่ควรใช้นิพจน์เช่นนี้อย่างชัดเจน สำหรับโค้ดส่วนใหญ่แล้วพฤติกรรมในอุดมคติจะทำให้คอมไพเลอร์ปฏิเสธการแสดงออกดังกล่าวอย่างสิ้นเชิง ในขณะนั้นคอมไพเลอร์ C ไม่ได้ทำการวิเคราะห์การไหลที่จำเป็นในการตรวจสอบว่าขึ้นอยู่กับ แม้ในเวลามาตรฐาน C ดั้งเดิมมันก็ยังไม่ธรรมดา

ฉันไม่แน่ใจว่ามันจะเป็นที่ยอมรับของชุมชนในวันนี้ - ในขณะที่คอมไพเลอร์หลายคนสามารถทำการวิเคราะห์การไหลแบบนั้นพวกเขามักจะทำเมื่อคุณขอเพิ่มประสิทธิภาพ ฉันสงสัยว่าโปรแกรมเมอร์ส่วนใหญ่ต้องการความคิดในการชะลอ "debug" builds เพียงเพื่อความสามารถในการปฏิเสธรหัสที่พวกเขา (มีสติ) จะไม่เขียนในตอนแรก

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

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

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


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

บางทีฉันอาจจะพลาดบางสิ่งบางอย่าง - ถ้าไม่มีใครควรเขียนโค้ดเช่นนี้ทำไมต้องคำนึงถึงการปรับให้เหมาะสม
ugoren

1
@ugoren: การเขียนโค้ดเช่นa=b[i++];(ตัวอย่างหนึ่ง) นั้นดีและการเพิ่มประสิทธิภาพเป็นสิ่งที่ดี อย่างไรก็ตามฉันไม่เห็นจุดที่ทำร้ายโค้ดที่สมเหตุสมผลเช่นนั้นบางสิ่งเช่น++i++นั้นจึงมีความหมายที่กำหนดไว้
Jerry Coffin

2
@ugoren ปัญหาคือหนึ่งในการวินิจฉัย จุดประสงค์เพียงอย่างเดียวของการไม่แสดงออกอย่างไม่อนุญาตทันทีเช่น++i++นั้นคือโดยทั่วไปว่าเป็นการยากที่จะแยกความแตกต่างจากการแสดงออกที่ถูกต้องกับผลข้างเคียง (เช่นa=b[i++]) มันอาจดูง่ายพอสำหรับเราถ้าฉันจำDragon Bookได้ถูกต้องแล้วมันก็เป็นปัญหาที่ยากมาก นั่นเป็นสาเหตุที่พฤติกรรมนี้เป็น UB แทนที่จะเป็นสิ่งต้องห้าม
Konrad Rudolph

1
ฉันไม่เชื่อว่าประสิทธิภาพเป็นข้อโต้แย้งที่ถูกต้อง ฉันพยายามที่จะเชื่อว่ากรณีนี้เป็นเรื่องธรรมดาพอโดยพิจารณาถึงความแตกต่างที่น้อยมากและการดำเนินการที่รวดเร็วมากในทั้งสองกรณีเพื่อให้ประสิทธิภาพลดลงเล็กน้อยเพื่อให้สังเกตได้ - ไม่ต้องพูดถึงว่าในโปรเซสเซอร์และสถาปัตยกรรมจำนวนมาก
DeadMG

9

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

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

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


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

5

อันดับแรกเรามาดูความหมายของพฤติกรรมที่ไม่ได้กำหนด:

3.4.3

1 พฤติกรรม
พฤติกรรมที่ไม่สามารถแก้ไขได้เมื่อใช้โปรแกรมที่ไม่สามารถทำการแปลได้หรือข้อมูลที่ผิดพลาดหรือข้อมูลที่ผิดพลาดซึ่งมาตรฐานสากลนี้ไม่มีข้อกำหนด

2 หมายเหตุพฤติกรรมที่ไม่สามารถคาดเดาได้มีตั้งแต่การเพิกเฉยต่อสถานการณ์ทั้งหมดกับผลลัพธ์ที่ไม่สามารถคาดเดาได้ ลักษณะที่เป็นเอกสารของสภาพแวดล้อม (ไม่ว่าจะมีหรือไม่มีการออกข้อความวินิจฉัย) เพื่อยกเลิกการแปลหรือการดำเนินการ (ด้วยการออกข้อความวินิจฉัย)

3 ตัวอย่างตัวอยางของพฤติกรรมที่ไมไดใชงานคือพฤติกรรมของจํานวนเต็ม

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

รากของปัญหาภายใต้การสนทนาเป็นประโยคต่อไปนี้:

6.5 นิพจน์

...
3 การจัดกลุ่มของตัวดำเนินการและตัวถูกดำเนินการถูกระบุโดยไวยากรณ์ 74) ยกเว้นที่ระบุไว้ ed ไป (สำหรับฟังก์ชั่นการโทร(), &&, ||, ?:และผู้ประกอบการจุลภาค), คำสั่งของการประเมินผลของ subexpressions และคำสั่งในการที่ผลข้างเคียงที่เกิดขึ้นมีทั้งเอ็ด Fi unspeci

เน้นการเพิ่ม

ได้รับการแสดงออกเช่น

x = a++ * --b / (c + ++d);

subexpressions a++, --b, cและ++dอาจได้รับการประเมินในลำดับใด นอกจากนี้ผลข้างเคียงของa++, --bและ++dอาจถูกนำไปใช้ที่จุดใด ๆ ก่อนหน้าจุดลำดับถัดไป (IOW แม้ว่าa++จะได้รับการประเมินมาก่อน--bก็ไม่รับประกันว่าaจะได้รับการอัพเดต ก่อนที่จะ--bถูกประเมิน) เช่นเดียวกับคนอื่น ๆ ได้กล่าวเหตุผลสำหรับพฤติกรรมนี้คือการให้อิสระในการดำเนินการจัดลำดับการดำเนินงานในลักษณะที่เหมาะสม

ด้วยเหตุนี้การแสดงออกเช่นนี้

x = x++
y = i++ * i++
a[i] = i++
*p++ = -*p    // this one bit me just yesterday

เป็นต้นจะให้ผลลัพธ์ที่แตกต่างกันสำหรับการใช้งานที่แตกต่างกัน (หรือสำหรับการใช้งานเดียวกันกับการตั้งค่าการเพิ่มประสิทธิภาพที่แตกต่างกันหรือขึ้นอยู่กับรหัสล้อมรอบ ฯลฯ )

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

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

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


1
คำอธิบายที่ดีมากของปัญหา ฉันไม่เห็นด้วยเกี่ยวกับการทำลายแอปพลิเคชันรุ่นเก่า - วิธีที่พฤติกรรมที่ไม่ได้กำหนด / ไม่ระบุถูกนำมาใช้ในบางครั้งการเปลี่ยนแปลงระหว่างเวอร์ชันคอมไพเลอร์โดยไม่มีการเปลี่ยนแปลงใด ๆ ในมาตรฐาน ฉันไม่แนะนำให้เปลี่ยนพฤติกรรมที่กำหนดไว้
ugoren

4

ก่อนอื่นคุณต้องเข้าใจว่าไม่ใช่แค่ x = x ++ ที่ไม่ได้กำหนด ไม่มีใครสนใจเกี่ยวกับ x = x ++ เนื่องจากไม่ว่าคุณจะให้คำจำกัดความกับสิ่งใดก็ตาม สิ่งที่ไม่ได้กำหนดเป็นเหมือน "a = b ++ โดยที่ a และ b เกิดขึ้นเหมือนกัน" - เช่น

void f(int *a, int *b) {
    *a = (*b)++;
}
int i;
f(&i, &i);

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

load r1 = *b
copy r2 = r1
increment r1
store *b = r1
store *a = r2

หรือ

load r1 = *b
store *a = r1
increment r1
store *b = r1

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


คุณแสดงให้เห็นถึงกรณีที่คำแนะนำของฉันส่งผลให้การทำงานของเครื่องจักรมากขึ้น แต่มันก็ไม่สำคัญสำหรับฉัน และคอมไพเลอร์ยังคงมีเสรีภาพบาง - ข้อกำหนดเพียงอย่างเดียวจริงฉันจะเพิ่มคือการเก็บก่อนb a
ugoren

3

มรดก

สมมติฐานที่ว่า C สามารถนำไปสร้างใหม่ในวันนี้ไม่สามารถถือได้ มีรหัส C หลายบรรทัดที่ถูกสร้างขึ้นและใช้งานทุกวันการเปลี่ยนกฎของเกมในระหว่างการเล่นนั้นผิด

แน่นอนคุณสามารถประดิษฐ์ภาษาใหม่พูดC + =ด้วยกฎของคุณ แต่นั่นจะไม่ใช่ซี


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

2

การประกาศว่ามีการกำหนดบางอย่างไว้จะไม่เปลี่ยนคอมไพเลอร์ที่มีอยู่ให้เป็นไปตามคำจำกัดความของคุณ นั่นเป็นเรื่องจริงโดยเฉพาะอย่างยิ่งในกรณีของข้อสันนิษฐานที่อาจอาศัยอยู่อย่างชัดเจนหรือโดยปริยายในหลาย ๆ ที่

ปัญหาหลักของข้อสันนิษฐานนั้นไม่ได้เกิดขึ้นx = x++;(คอมไพเลอร์สามารถตรวจสอบได้ง่ายและควรเตือน) ด้วย*p1 = (*p2)++และเทียบเท่า ( p1[i] = p2[j]++;เมื่อ p1 และ p2 เป็นพารามิเตอร์ของฟังก์ชั่น) ที่คอมไพเลอร์ไม่สามารถรู้ได้ง่ายว่าp1 == p2(ใน C99 restrictถูกเพิ่มเพื่อกระจายความเป็นไปได้ของการสมมติว่า p1! = p2 ระหว่างจุดลำดับดังนั้นจึงถือว่าความเป็นไปได้ในการเพิ่มประสิทธิภาพมีความสำคัญ)


p1[i]=p2[j]++ผมไม่เห็นว่าคำแนะนำของฉันเปลี่ยนแปลงอะไรในเรื่องที่เกี่ยวกับ หากคอมไพเลอร์ไม่สามารถใช้นามแฝงได้ก็ไม่มีปัญหา หากไม่สามารถทำได้จะต้องดำเนินการตามหนังสือ - เพิ่มp2[j]ก่อนเก็บในp1[i]ภายหลัง ยกเว้นโอกาสในการเพิ่มประสิทธิภาพที่สูญเสียไปซึ่งดูเหมือนไม่สำคัญฉันไม่เห็นปัญหาใด ๆ
ugoren

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

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

ปัญหาไม่ใช่ว่าเราต้องเปลี่ยนคอมไพเลอร์เกี่ยวกับการเปลี่ยนแปลงในภาษาที่ต้องการมันก็คือการเปลี่ยนแปลงนั้นแพร่หลายและหายาก วิธีการปฏิบัติมากที่สุดอาจจะมีการเปลี่ยนแปลงรูปแบบกลางที่เพิ่มประสิทธิภาพการทำงานเช่นทำท่าว่าx = x++;ยังไม่ได้รับการเขียน แต่t = x; x++; x = t;หรือx=x; x++;หรือสิ่งที่คุณต้องการให้เป็นความหมาย ( แต่สิ่งที่เกี่ยวกับการวินิจฉัย?) สำหรับภาษาใหม่ให้เลื่อนออกไปข้างๆ
AProgrammer

ฉันไม่รู้โครงสร้างคอมไพเลอร์มากเกินไป ถ้าฉันต้องการเปลี่ยนคอมไพเลอร์ทั้งหมดฉันจะสนใจมากขึ้น แต่อาจถือว่าx++เป็นจุดลำดับราวกับว่ามันเป็นฟังก์ชั่นการโทรinc_and_return_old(&x)จะทำเคล็ดลับ
ugoren

-1

ในบางกรณีรหัสประเภทนี้ถูกกำหนดในมาตรฐาน C ++ 11 ใหม่


5
สนใจที่จะทำอย่างละเอียด?
ugoren

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