ปรับแต่ง“ ในขณะที่ (1);” ใน C ++ 0x


153

อัปเดตดูด้านล่าง!

ฉันเคยได้ยินและอ่านว่า C ++ 0x อนุญาตให้คอมไพเลอร์พิมพ์ "Hello" สำหรับตัวอย่างต่อไปนี้

#include <iostream>

int main() {
  while(1) 
    ;
  std::cout << "Hello" << std::endl;
}

เห็นได้ชัดว่ามีบางอย่างเกี่ยวข้องกับเธรดและความสามารถในการปรับให้เหมาะสม ฉันคิดว่ามันน่าแปลกใจที่หลาย ๆ คนคิดว่า

บางคนมีคำอธิบายที่ดีว่าเหตุใดจึงจำเป็นต้องอนุญาต สำหรับการอ้างอิง C ++ 0x ฉบับร่างล่าสุดระบุไว้ที่6.5/5

ลูปที่นอกเหนือจากคำสั่ง for-init ในกรณีของคำสั่ง for

  • ไม่เรียกใช้ฟังก์ชันห้องสมุด I / O และ
  • ไม่สามารถเข้าถึงหรือแก้ไขวัตถุที่ระเหยได้และ
  • ไม่มีการดำเนินการซิงโครไนซ์ (1.10) หรือการดำเนินการปรมาณู (ข้อ 29)

อาจถูกสันนิษฐานโดยการนำไปปฏิบัติเพื่อยุติ [หมายเหตุ: สิ่งนี้มีจุดประสงค์เพื่อให้การแปลงคอมไพเลอร์เช่นการลบลูปที่ว่างเปล่าแม้ว่าจะไม่สามารถพิสูจน์ได้ว่าการเลิกจ้าง - บันทึกท้าย]

แก้ไข:

บทความที่ลึกซึ้งนี้กล่าวถึงข้อความมาตรฐาน

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

ถูกต้องและคอมไพเลอร์ได้รับอนุญาตให้พิมพ์ "Bye" สำหรับโปรแกรมข้างต้นหรือไม่


มีกระทู้ที่ลึกซึ้งยิ่งขึ้นที่นี่ซึ่งเป็นเรื่องเกี่ยวกับการเปลี่ยนแปลงคล้ายกับ C เริ่มต้นโดย Guy ทำบทความเชื่อมโยงข้างต้น ในบรรดาข้อเท็จจริงที่มีประโยชน์อื่น ๆ พวกเขานำเสนอโซลูชันที่ดูเหมือนว่าจะนำไปใช้กับ C ++ 0x ( อัปเดต : สิ่งนี้จะไม่ทำงานกับ n3225 - ดูด้านล่าง!)

endless:
  goto endless;

คอมไพเลอร์ไม่ได้รับอนุญาตให้ปรับประสิทธิภาพให้เหมาะสมดูเหมือนเนื่องจากไม่ใช่การวนซ้ำ แต่เป็นการกระโดด ชายอีกคนสรุปการเปลี่ยนแปลงที่เสนอใน C ++ 0x และ C201X

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


อัปเดตเมื่อวันที่ 3.1.2011 กับ n3225: คณะกรรมการย้ายข้อความไปที่ 1.10 / 24 และพูด

การนำไปใช้อาจสันนิษฐานว่าเธรดใด ๆ จะทำอย่างใดอย่างหนึ่งต่อไปนี้:

  • ยกเลิก
  • โทรไปยังฟังก์ชั่นไลบรารี I / O
  • เข้าถึงหรือแก้ไขวัตถุที่ระเหยได้หรือ
  • ดำเนินการประสานหรือการดำเนินการปรมาณู

gotoเคล็ดลับจะไม่ทำงานอีกต่อไป!


4
while(1) { MyMysteriousFunction(); }ต้องสามารถคอมไพล์ได้อย่างอิสระโดยไม่ทราบคำจำกัดความของฟังก์ชั่นลึกลับใช่มั้ย ดังนั้นเราจะทราบได้อย่างไรว่ามันทำการโทรไปยังฟังก์ชั่นไลบรารี I / O ใด ๆ กล่าวอีกนัยหนึ่ง: แน่นอนว่ากระสุนแรกอาจจะเป็นวลีทำให้ไม่สามารถเรียกใช้ฟังก์ชันได้
Daniel Earwicker

19
@Daniel: หากมีการเข้าถึงคำจำกัดความของฟังก์ชั่นมันสามารถพิสูจน์ได้หลายอย่าง มีสิ่งต่าง ๆ เช่นการเพิ่มประสิทธิภาพระหว่างกระบวนการ
Potatoswatter

3
ตอนนี้ใน C ++ 03 คอมไพเลอร์ได้รับอนุญาตให้เปลี่ยนint x = 1; for(int i = 0; i < 10; ++i) do_something(&i); x++;เป็นfor(int i = 0; i < 10; ++i) do_something(&i); int x = 2;หรือไม่? หรืออาจเป็นวิธีอื่นโดยมีxการเตรียมใช้งาน2ก่อนลูป มันสามารถบอกได้ว่าdo_somethingไม่สนใจคุณค่าของxมันดังนั้นจึงเป็นการเพิ่มประสิทธิภาพที่ปลอดภัยอย่างสมบูรณ์แบบหาก do_somethingไม่ทำให้ค่าของiการเปลี่ยนแปลงนั้นจบลงด้วยการวนซ้ำไม่สิ้นสุด
Dennis Zickefoose

4
ดังนั้นหมายความว่าที่main() { start_daemon_thread(); while(1) { sleep(1000); } }เพิ่งออกทันทีแทนที่จะใช้ภูตของฉันในด้ายพื้นหลัง?
Gabe

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

คำตอบ:


33

บางคนมีคำอธิบายที่ดีว่าเหตุใดจึงจำเป็นต้องอนุญาต

ใช่ Hans Boehm ให้เหตุผลในเรื่องนี้ในN1528: ทำไมพฤติกรรมที่ไม่ได้กำหนดสำหรับลูปไม่สิ้นสุด? แม้ว่านี่จะเป็นเอกสาร WG14 เหตุผลก็มีผลกับ C ++ เช่นกันและเอกสารนั้นอ้างถึงทั้ง WG14 และ WG21:

เนื่องจาก N1509 ชี้ให้เห็นอย่างถูกต้องร่างปัจจุบันเป็นหลักให้พฤติกรรมที่ไม่ได้กำหนดให้ลูปไม่มีที่สิ้นสุดใน 6.8.5p6 ปัญหาสำคัญของการทำเช่นนี้คือมันอนุญาตให้โค้ดเคลื่อนที่ข้ามลูปที่อาจไม่สิ้นสุด ตัวอย่างเช่นสมมติว่าเรามีลูปต่อไปนี้โดยที่ count และ count2 เป็นตัวแปรโกลบอล (หรือมีที่อยู่ของพวกเขา) และ p เป็นตัวแปรโลคอลซึ่งไม่ได้ใช้ที่อยู่:

for (p = q; p != 0; p = p -> next) {
    ++count;
}
for (p = q; p != 0; p = p -> next) {
    ++count2;
}

ลูปทั้งสองนี้จะถูกรวมเข้าด้วยกันและแทนที่ด้วยลูปต่อไปนี้หรือไม่?

for (p = q; p != 0; p = p -> next) {
        ++count;
        ++count2;
}

หากไม่มีการแจกจ่ายพิเศษใน 6.8.5p6 สำหรับลูปแบบไม่มีที่สิ้นสุดนี่จะไม่ได้รับอนุญาต: หากลูปแรกไม่ยุติเพราะคิวชี้ไปที่รายการแบบวงกลมต้นฉบับจะไม่เขียนลงใน count2 ดังนั้นจึงสามารถเรียกใช้พร้อมกับเธรดอื่นที่เข้าถึงหรืออัพเดตจำนวน 2 นี่ไม่ปลอดภัยอีกต่อไปกับเวอร์ชันที่แปลงแล้วซึ่งจะนับการเข้าถึง 2 แม้จะมีลูปไม่สิ้นสุด ดังนั้นการเปลี่ยนแปลงที่อาจแนะนำการแข่งขันข้อมูล

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

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

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

for (i = 1; i != 15; i += 2)

หรือ

for (i = 1; i <= 10; i += j)

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

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

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


1
มีการเพิ่มประสิทธิภาพที่ปลอดภัยและมีประโยชน์ที่อำนวยความสะดวกโดยภาษาปัจจุบันที่จะไม่อำนวยความสะดวกเช่นกันโดยการพูดว่า "ถ้าการเลิกจ้างของวงขึ้นอยู่กับสถานะของวัตถุใด ๆ เวลาที่จำเป็นในการดำเนินการห่วงไม่ได้พิจารณา ผลข้างเคียงที่สังเกตได้ถึงแม้เวลาดังกล่าวจะไม่มีที่สิ้นสุด " ได้รับdo { x = slowFunctionWithNoSideEffects(x);} while(x != 23);รหัส Hoisting หลังจากวงซึ่งจะไม่ขึ้นอยู่กับxจะดูเหมือนปลอดภัยและเหมาะสม แต่ช่วยให้คอมไพเลอร์ที่จะคิดx==23ในรหัสดังกล่าวดูเหมือนว่าอันตรายมากกว่าประโยชน์
supercat

47

สำหรับฉันเหตุผลที่เกี่ยวข้องคือ:

สิ่งนี้มีจุดประสงค์เพื่อให้การแปลงคอมไพเลอร์เช่นการลบลูปที่ว่างเปล่าแม้ว่าจะไม่สามารถพิสูจน์ได้ว่าการเลิกจ้าง

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

แก้ไข: ตัวอย่างใบ้:

while (complicated_condition()) {
    x = complicated_but_externally_invisible_operation(x);
}
complex_io_operation();
cout << "Results:" << endl;
cout << x << endl;

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

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

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

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

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


"การพิสูจน์ 1) ค่อนข้างง่าย" - อันที่จริงแล้วมันไม่ได้ปฏิบัติตามทันทีจาก 3 เงื่อนไขเพื่อให้คอมไพเลอร์ได้รับอนุญาตให้ทำการยกเลิกลูปภายใต้ข้อที่โยฮันเนสถาม ฉันคิดว่าพวกมันมีจำนวน "วงวนไม่มีผลที่สังเกตได้ยกเว้นอาจหมุนตลอดไป" และประโยคทำให้มั่นใจได้ว่า "การหมุนตลอดไป" ไม่ได้รับประกันพฤติกรรมสำหรับวงวนดังกล่าว
Steve Jessop

@ Steve: มันง่ายถ้าลูปไม่ยุติ complex_io_operationแต่ถ้าห่วงไม่ยุติแล้วมันอาจมีพฤติกรรมขี้ปะติ๋วซึ่งมีผลต่อการประมวลผลของ
ฟิลิปพอตเตอร์

โอ๊ะโอใช่ฉันพลาดไปแล้วว่ามันอาจแก้ไขไอเดีย / นามแฝงที่ไม่ลบเลือน / สิ่งที่ใช้ใน IO op ดังนั้นคุณพูดถูก: แม้ว่ามันจะไม่จำเป็นต้องทำตาม แต่ก็มีหลายกรณีที่คอมไพเลอร์สามารถและพิสูจน์ได้ว่าไม่มีการดัดแปลงใด ๆ เกิดขึ้น
Steve Jessop

อย่างไรก็ตามมันหมายความว่าลูปที่ไม่มีที่สิ้นสุดที่ใช้ในตัวอย่างการเรียนรู้จะได้รับผลและจะเพิ่ม gotchas ในรหัสเริ่มต้นฉันไม่สามารถพูดได้ว่านี่เป็นสิ่งที่ดีอย่างสิ้นเชิง " เพียงคอมไพล์ด้วยการปรับให้เหมาะสมและมันก็ยังคงใช้ได้
KitsuneYMG

1
@supercat: สิ่งที่คุณอธิบายคือสิ่งที่จะเกิดขึ้นในทางปฏิบัติ แต่ไม่ใช่สิ่งที่ร่างมาตรฐานต้องการ เราไม่สามารถสรุปได้ว่าคอมไพเลอร์ไม่รู้ว่าการวนซ้ำจะยุติลงหรือไม่ ถ้าคอมไพเลอร์ไม่ทราบว่าวงจะไม่ยุติก็สามารถทำสิ่งที่มันชอบ DS9K จะสร้างปีศาจจมูกใด ๆห่วงอนันต์ไม่มี I / O ฯลฯ (ดังนั้นแก้ DS9K ที่ลังเลปัญหา.)
ฟิลิปพอตเตอร์

15

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

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

ถ้าลูปอนันต์มี UB มันก็หมายความว่าโปรแกรมที่ไม่ใช่การยกเลิกจะไม่ถือว่ามีความหมาย: ตามไปที่ C ++ 0x พวกเขามีไม่มีความหมาย

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


7
“ ลูปไม่มีที่สิ้นสุดว่างเปล่าเป็นพฤติกรรมที่ไม่ได้กำหนด” อลันทัวริงจะขอแตกต่างกัน แต่เมื่อเขาหมุนตัวไปในหลุมศพของเขา
Donal Fellows

11
@ Donal: ฉันไม่เคยพูดอะไรเกี่ยวกับความหมายของมันในเครื่องทัวริง เรากำลังคุยกันเรื่องความหมายของวง จำกัด ที่ไม่มี effcts ด้านใน C ++ และเมื่อฉันอ่านมัน C ++ 0x เลือกที่จะพูดว่าลูปดังกล่าวไม่ได้ถูกกำหนด
jalf

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

1
นี่หมายความว่า C ++ 0x ไม่เหมาะกับอุปกรณ์ฝังตัวหรือไม่ while(1){...}อุปกรณ์เกือบทั้งหมดที่ฝังตัวจะไม่ยุติและทำงานของพวกเขาภายในไขมันใหญ่ พวกเขายังใช้เป็นประจำwhile(1);เพื่อเรียกใช้การรีเซ็ต watchdog ช่วย
vsz

1
@vsz: แบบฟอร์มแรกใช้ได้ ลูปไม่มีที่สิ้นสุดถูกนิยามไว้อย่างดีตราบใดที่พวกมันมีพฤติกรรมที่สังเกตได้ รูปแบบที่สองนั้นซับซ้อนกว่า แต่ฉันคิดได้สองวิธีง่ายๆ: (1) คอมไพเลอร์ที่กำหนดเป้าหมายอุปกรณ์แบบฝังตัวสามารถเลือกที่จะกำหนดพฤติกรรมที่เข้มงวดขึ้นในกรณีนั้นหรือ (2) คุณสร้างเนื้อหาที่เรียกฟังก์ชั่นห้องสมุดจำลอง . ตราบใดที่คอมไพเลอร์ไม่ทราบว่าฟังก์ชั่นนั้นทำอะไรก็ต้องคิดว่ามันอาจมีผลข้างเคียงบางอย่างดังนั้นจึงไม่สามารถยุ่งกับลูปได้
jalf

8

ผมคิดว่านี่คือตามสายของประเภทนี้ของคำถามที่อ้างอิงอีกด้าย การเพิ่มประสิทธิภาพสามารถลบลูปที่ว่างเปล่าได้เป็นครั้งคราว


3
เป็นคำถามที่ดี ดูเหมือนว่าคนที่แต่งตัวประหลาดมีปัญหาตรงที่ย่อหน้านี้อนุญาตให้คอมไพเลอร์ที่จะทำให้ ในการอภิปรายที่เชื่อมโยงกันโดยหนึ่งในคำตอบนั้นมีการเขียนว่า"โชคไม่ดีที่คำว่า 'พฤติกรรมที่ไม่ได้กำหนด' ไม่ได้ใช้อย่างไรก็ตามทุกครั้งที่มาตรฐานระบุว่า 'คอมไพเลอร์อาจถือว่า P' มันบอกเป็นนัยว่าโปรแกรมที่มี คุณสมบัติที่ไม่ใช่ P มีความหมายที่ไม่ได้กำหนด " . เรื่องนี้ทำให้ฉันประหลาดใจ นี่หมายความว่าโปรแกรมตัวอย่างของฉันด้านบนมีพฤติกรรมที่ไม่ได้กำหนดและอาจแยกออกจากที่ไหนก็ได้?
Johannes Schaub - litb

@Johannes: ข้อความ "อาจจะถือว่า" ไม่เกิดขึ้นที่อื่นในร่างที่ฉันต้องส่งและ "อาจถือว่า" เกิดขึ้นเพียงสองสามครั้ง แม้ว่าฉันจะตรวจสอบเรื่องนี้ด้วยฟังก์ชั่นการค้นหาที่ไม่สามารถจับคู่ข้ามตัวแบ่งบรรทัดได้ดังนั้นฉันอาจพลาดบางอย่าง ดังนั้นฉันไม่แน่ใจว่าลักษณะทั่วไปของผู้เขียนคือการรับประกันหลักฐานแต่เป็นนักคณิตศาสตร์ที่ฉันต้องยอมรับตรรกะของการโต้แย้งว่าถ้าคอมไพเลอร์จะถือว่าสิ่งที่ false แล้วโดยทั่วไปก็อาจอนุมานอะไร ...
สตีฟเจสซอพ

... การอนุญาตให้มีการโต้แย้งในเหตุผลของผู้เรียบเรียงเกี่ยวกับโปรแกรมที่บอกเป็นนัยอย่างยิ่งที่ UB เนื่องจากโดยเฉพาะอย่างยิ่งมันอนุญาตให้คอมไพเลอร์สำหรับ X ใด ๆ เพื่ออนุมานว่าโปรแกรมนั้นเทียบเท่ากับ X แน่นอนอนุญาตให้คอมไพเลอร์เพื่ออนุมานว่า อนุญาตให้ทำเช่นนั้น ฉันเห็นด้วยกับผู้เขียนเช่นกันว่าหากตั้งใจจะให้ระบุไว้อย่างชัดเจนว่า UB และถ้ามันไม่ได้ตั้งใจข้อความที่ระบุนั้นผิดและควรแก้ไข (อาจเป็นภาษาสเปคเทียบเท่า "คอมไพเลอร์อาจแทนที่ วนรอบด้วยรหัสที่ไม่มีผลกระทบ "ฉันไม่แน่ใจ)
Steve Jessop

@SteveJessop: คุณคิดอย่างไรกับการบอกว่าการประมวลผลของรหัสใด ๆ - รวมถึงลูปที่ไม่มีที่สิ้นสุด - อาจถูกเลื่อนออกไปจนกว่าจะถึงเวลาที่บางสิ่งบางอย่างของรหัสจะส่งผลกระทบต่อพฤติกรรมของโปรแกรมที่สังเกตได้และสำหรับวัตถุประสงค์นั้น กฎเวลาที่ต้องใช้ในการเรียกใช้โค้ด - แม้ว่าจะไม่มีที่สิ้นสุด - ไม่ใช่ "ผลข้างเคียงที่สังเกตได้" หากคอมไพเลอร์สามารถแสดงให้เห็นว่าลูปไม่สามารถออกได้โดยไม่มีตัวแปรที่ถือค่าที่แน่นอนตัวแปรนั้นอาจถูกพิจารณาว่าเป็นค่านั้นแม้ว่ามันจะยังสามารถแสดงให้เห็นว่าลูปไม่สามารถออกจากมันได้
supercat

@supercat: ตามที่คุณได้กล่าวถึงข้อสรุปนั้นฉันไม่คิดว่ามันจะช่วยปรับปรุง ถ้าห่วงสรรพสิ่งที่ไม่เคยออกแล้วสำหรับวัตถุใด ๆXและบิตรูปแบบxคอมไพเลอร์สามารถแสดงให้เห็นวงที่ไม่ได้ออกโดยไม่ถือบิตรูปแบบX xมันเป็นความจริงที่ว่างเปล่า ดังนั้นXอาจจะถือว่าถือรูปแบบบิตใด ๆ และว่าไม่ดีเท่าที่ UB ในแง่ที่ว่าสำหรับการที่ไม่ถูกต้องXและxมันจะรวดเร็วสาเหตุบางอย่าง ดังนั้นฉันเชื่อว่าคุณจะต้องแม่นยำมากขึ้นในมาตรฐานของคุณ เป็นการยากที่จะพูดคุยเกี่ยวกับสิ่งที่เกิดขึ้น "ในตอนท้าย" การวนซ้ำไม่สิ้นสุดและแสดงให้เห็นว่าเทียบเท่ากับการใช้งานที่ จำกัด
Steve Jessop

8

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

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

volatile int dummy_side_effect;

while (1) {
    dummy_side_effect = 0;
}

printf("Never prints.\n");

2
@ JohannesSchaub-litb: หากลูป - ไม่มีที่สิ้นสุดหรือไม่ - ไม่อ่านหรือเขียนตัวแปรที่ผันผวนในระหว่างการดำเนินการและไม่เรียกใช้ฟังก์ชันใด ๆ ที่อาจทำเช่นนั้นคอมไพเลอร์มีอิสระที่จะเลื่อนส่วนใดส่วนหนึ่งของลูปจนกว่า ความพยายามครั้งแรกในการเข้าถึงสิ่งที่คำนวณได้ กำหนดให้unsigned int dummy; while(1){dummy++;} fprintf(stderror,"Hey\r\n"); fprintf(stderror,"Result was %u\r\n",dummy);คนแรกfprintfสามารถดำเนินการ แต่ที่สองไม่สามารถ (คอมไพเลอร์สามารถย้ายการคำนวณdummyระหว่างทั้งสองfprintfแต่ไม่ผ่านหนึ่งที่พิมพ์ค่าของมัน)
supercat

1

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

โมฆะ testfermat (int n)
{
  int a = 1, b = 1, c = 1;
  ในขณะที่ (pow (a, n) + pow (b, n)! = pow (c, n))
  {
    ถ้า (b> a) a ++; อื่นถ้า (c> b) {a = 1; ข ++}; อื่น {a = 1; ข = 1; C ++};
  }
  printf ("ผลลัพธ์คือ");
  printf ("% d /% d /% d", a, b, c);
}

เช่น

โมฆะ testfermat (int n)
{
  if (fork_is_first_thread ())
  {
    int a = 1, b = 1, c = 1;
    ในขณะที่ (pow (a, n) + pow (b, n)! = pow (c, n))
    {
      ถ้า (b> a) a ++; อื่นถ้า (c> b) {a = 1; ข ++}; อื่น {a = 1; ข = 1; C ++};
    }
    signal_other_thread_and_die ();
  }
  อื่น // กระทู้ที่สอง
  {
    printf ("ผลลัพธ์คือ");
    wait_for_other_thread ();
  }
  printf ("% d /% d /% d", a, b, c);
}

โดยทั่วไปแล้วไม่มีเหตุผลแม้ว่าฉันอาจกังวลว่า:

  int ทั้งหมด = 0;
  สำหรับ (i = 0; num_reps> i; i ++)
  {
    update_progress_bar (i);
    รวม + = do_something_slow_with_no_side_effects (i);
  }
  show_result (รวม);

จะกลายเป็น

  int ทั้งหมด = 0;
  if (fork_is_first_thread ())
  {
    สำหรับ (i = 0; num_reps> i; i ++)
      รวม + = do_something_slow_with_no_side_effects (i);
    signal_other_thread_and_die ();
  }
  อื่น
  {
    สำหรับ (i = 0; num_reps> i; i ++)
      update_progress_bar (i);
    wait_for_other_thread ();
  }
  show_result (รวม);

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


ฉันคิดว่ากรณีแถบความคืบหน้าของคุณไม่สามารถแยกออกได้เนื่องจากการแสดงแถบความคืบหน้าเป็นการโทร I / O ของห้องสมุด การเพิ่มประสิทธิภาพไม่ควรเปลี่ยนพฤติกรรมที่มองเห็นได้ด้วยวิธีนี้
ฟิลิปพอตเตอร์

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

1
สิ่งนี้จะอธิบายแถบความคืบหน้าทั้งหมดที่ดำเนินไปอย่างรวดเร็วตั้งแต่ 0 ถึง 100 จากนั้นแขวนเป็นเวลานาน;)
paulm

0

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

ในกรณีที่แตกต่างกันอาจเกิดขึ้นได้ว่าเครื่องมือเพิ่มประสิทธิภาพของคุณจะมีระดับความซับซ้อนที่ดีกว่าสำหรับรหัสของคุณ (เช่น O (n ^ 2) และคุณจะได้ O (n) หรือ O (1) หลังจากการปรับให้เหมาะสม)

ดังนั้นหากรวมกฎดังกล่าวที่ไม่อนุญาตให้นำการวนซ้ำไม่สิ้นสุดลงในมาตรฐาน C ++ จะทำให้การเพิ่มประสิทธิภาพหลายอย่างเป็นไปไม่ได้ และคนส่วนใหญ่ไม่ต้องการสิ่งนี้ ฉันคิดว่านี่ค่อนข้างตอบคำถามของคุณ


อีกสิ่งหนึ่ง: ฉันไม่เคยเห็นตัวอย่างที่ถูกต้องที่คุณต้องการวงวนไม่สิ้นสุดซึ่งไม่ทำอะไรเลย

ตัวอย่างหนึ่งที่ฉันเคยได้ยินคือแฮ็กที่น่าเกลียดที่ควรแก้ไขอย่างอื่น: มันเกี่ยวกับระบบฝังตัวที่มีวิธีเดียวที่จะทริกเกอร์การรีเซ็ตคือการหยุดอุปกรณ์เพื่อให้สุนัขเฝ้าบ้านทำการรีสตาร์ทโดยอัตโนมัติ

หากคุณรู้ว่าตัวอย่างที่ถูกต้อง / ดีที่คุณต้องการวงวนไม่สิ้นสุดซึ่งไม่ทำอะไรเลยโปรดบอกฉัน


1
ตัวอย่างของตำแหน่งที่คุณอาจต้องการวนรอบไม่สิ้นสุด: ระบบฝังตัวที่คุณไม่ต้องการนอนด้วยเหตุผลด้านประสิทธิภาพและรหัสทั้งหมดห้อยอยู่กับอินเตอร์รัปต์หรือสองอัน?
JCx

@JCx ใน Standard C การขัดจังหวะควรตั้งค่าสถานะที่ตรวจสอบการวนรอบหลักดังนั้นการวนรอบหลักจะมีลักษณะการทำงานที่สังเกตได้ในกรณีของการตั้งค่าสถานะ การเรียกใช้โค้ดจำนวนมากในอินเตอร์รัปต์นั้นไม่ใช่แบบพกพา
MM

-1

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

พูดอีกอย่างก็คือสร้างความกลมกลืนของคุณ - รวมถึงข้อโต้แย้งที่ส่งผ่านเข้าไปในลูปผ่านตัวชี้ / การอ้างอิง


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

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