นี่คือข้อผิดพลาดของเสียงดังกราว
... เมื่ออินไลน์ฟังก์ชันที่มีการวนซ้ำไม่สิ้นสุด พฤติกรรมจะแตกต่างกันเมื่อwhile(1);
ปรากฏในหลักโดยตรงซึ่งมีกลิ่นมากสำหรับฉัน
ดูคำตอบของสรุปและลิงก์ของ @ Arnavion ส่วนที่เหลือของคำตอบนี้ถูกเขียนขึ้นก่อนที่ฉันจะยืนยันว่ามันเป็นข้อผิดพลาด
ในการตอบคำถามชื่อ: ฉันจะทำให้วนที่ไม่มีที่สิ้นสุดที่จะไม่ได้รับการเพิ่มประสิทธิภาพได้อย่างไร ? -
สร้างdie()
แมโครไม่ใช่ฟังก์ชันเพื่อแก้ไขข้อบกพร่องนี้ใน Clang 3.9 และใหม่กว่า (รุ่นก่อนหน้านี้อย่างใดอย่างหนึ่งดังกราวช่วยให้วงหรือส่งเสียงcall
ไปยังรุ่นที่ไม่ใช่แบบอินไลน์ของฟังก์ชั่นที่มีห่วงอนันต์.) ที่ปรากฏจะปลอดภัยแม้ว่าprint;while(1);print;
inlines ฟังก์ชั่นเข้ามาของผู้โทร ( Godbolt ) -std=gnu11
vs. -std=gnu99
ไม่เปลี่ยนแปลงอะไรเลย
หากคุณสนใจ GNU C เพียงอย่างเดียวP__J ____asm__("");
ในวงก็สามารถใช้ได้เช่นกันและไม่ควรทำให้โค้ดที่ล้อมรอบเหมาะสมสำหรับคอมไพเลอร์ใด ๆ ที่เข้าใจ คำสั่ง asm GNU C Basic นั้นมีความหมายโดยนัยvolatile
ดังนั้นสิ่งนี้จึงนับเป็นผลข้างเคียงที่มองเห็นได้ซึ่งต้อง "เรียกใช้" หลาย ๆ ครั้งตามที่มันเป็นในเครื่องนามธรรม C (และใช่เสียงดังกร่างใช้ภาษา GNU ของ C ตามที่จัดทำโดยคู่มือ GCC)
บางคนแย้งว่ามันอาจถูกกฎหมายเพื่อเพิ่มประสิทธิภาพการวนซ้ำที่ไม่มีที่สิ้นสุด ผมไม่เห็นด้วย1แต่แม้ว่าเรายอมรับว่ามันไม่สามารถนอกจากนี้ยังจะมีกฎหมายสำหรับเสียงดังกราวที่จะสรุปงบหลังจากที่วงจะไม่สามารถเข้าถึงและให้การดำเนินการในฤดูใบไม้ร่วงปิดท้ายของฟังก์ชั่นในการทำงานต่อไปหรือลงในถังขยะ ที่ถอดรหัสเป็นคำแนะนำแบบสุ่ม
(นั่นจะเป็นไปตามมาตรฐานสำหรับ Clang ++ (แต่ยังไม่มีประโยชน์มาก); ลูปที่ไม่มีที่สิ้นสุดโดยไม่มีผลข้างเคียงใด ๆ คือ UB ใน C ++ แต่ไม่ใช่ C
คือขณะที่ (1) พฤติกรรมที่ไม่ได้กำหนดใน C? UB ทำให้คอมไพเลอร์ปล่อย สำหรับโค้ดบนเส้นทางของการดำเนินการที่จะพบ UB แน่นอนasm
คำสั่งในลูปจะหลีกเลี่ยง UB นี้สำหรับ C ++ แต่ในทางปฏิบัติ Clang การคอมไพล์เป็น C ++ จะไม่ลบลูปที่ไม่มีที่สิ้นสุดของนิพจน์ยกเว้นเมื่ออินไลน์เหมือนกับ รวบรวมเป็น C. )
การแทรกแบบอินไลน์ด้วยตนเองwhile(1);
เปลี่ยนวิธีการรวบรวมเสียงดังกังวาน: วงวนไม่สิ้นสุดแสดงเป็น asm นี่คือสิ่งที่เราคาดหวังจาก POV นักกฎหมาย
#include <stdio.h>
int main() {
printf("begin\n");
while(1);
//infloop_nonconst(1);
//infloop();
printf("unreachable\n");
}
บนตัวรวบรวมคอมไพเลอร์ Godbolt Clang 9.0 -O3 คอมไพล์เป็น C ( -xc
) สำหรับ x86-64:
main: # @main
push rax # re-align the stack by 16
mov edi, offset .Lstr # non-PIE executable can use 32-bit absolute addresses
call puts
.LBB3_1: # =>This Inner Loop Header: Depth=1
jmp .LBB3_1 # infinite loop
.section .rodata
...
.Lstr:
.asciz "begin"
คอมไพเลอร์เดียวกันที่มีตัวเลือกเดียวกันจะคอมไพล์ a main
ที่เรียกinfloop() { while(1); }
ไปยังอันดับแรกputs
แต่จากนั้นก็หยุดการเปล่งคำสั่งmain
หลังจากนั้น ดังนั้นอย่างที่ฉันได้กล่าวไว้การประมวลผลจะตกจากจุดสิ้นสุดของฟังก์ชั่นไปยังฟังก์ชันใด ๆ ก็ตามที่อยู่ถัดไป (แต่ด้วยสแต็กที่ไม่ถูกต้องสำหรับรายการฟังก์ชันดังนั้นมันจึงไม่ใช่ tailcall ที่ใช้ได้)
ตัวเลือกที่ถูกต้องจะเป็น
- ปล่อย
label: jmp label
ลูปไม่สิ้นสุด
- หรือ (ถ้าเรายอมรับว่าสามารถลบลูปไม่สิ้นสุด) ปล่อยสายอื่นเพื่อพิมพ์สตริงที่ 2 และ
return 0
จากmain
นั้น
การหยุดทำงานหรือดำเนินการต่อโดยไม่พิมพ์ "ไม่สามารถเข้าถึง" นั้นไม่ชัดเจนสำหรับการใช้งาน C11 เว้นแต่ว่ามี UB ที่ฉันไม่ได้สังเกตเห็น
เชิงอรรถ 1:
สำหรับบันทึกฉันเห็นด้วยกับคำตอบของ @ Lundin ซึ่งอ้างอิงมาตรฐานสำหรับหลักฐานว่า C11 ไม่อนุญาตให้มีการยุติการวนซ้ำแบบไม่ จำกัด แม้ในขณะที่ว่างเปล่า (ไม่มี I / O, ระเหย, การซิงโครไนซ์หรืออื่น ๆ ผลข้างเคียงที่มองเห็นได้)
นี่เป็นชุดของเงื่อนไขที่จะอนุญาตให้รวบรวมลูปเป็นลูป asm ที่ว่างเปล่าสำหรับ CPU ปกติ (แม้ว่าเนื้อความจะไม่ว่างเปล่าในแหล่งที่มาการมอบหมายให้ตัวแปรไม่สามารถมองเห็นได้ในเธรดหรือตัวจัดการสัญญาณอื่นที่ไม่มี data-race UB ในขณะที่ลูปทำงานอยู่ดังนั้นการดำเนินการตามมาตรฐานจึงสามารถลบลูปเนื้อความดังกล่าวได้ถ้าต้องการ เป็นแล้วจากนั้นคำถามจะบอกว่าสามารถลบลูปเองได้หรือไม่ ISO C11 บอกว่าไม่ชัดเจน)
เนื่องจาก C11 แยกออกมาเป็นกรณีที่การใช้งานไม่สามารถถือว่าการวนซ้ำสิ้นสุดลง (และไม่ใช่ UB) ดูเหมือนว่าพวกเขาตั้งใจที่จะนำเสนอการวนรอบในเวลาทำงาน การนำไปใช้งานที่กำหนดเป้าหมาย CPUs ด้วยโมเดลการดำเนินการที่ไม่สามารถทำงานได้อย่างไม่มีขีด จำกัด ในเวลาที่ จำกัด นั้นไม่มีเหตุผลที่จะลบลูปอนันต์คงที่ว่างเปล่า หรือโดยทั่วไปแล้วถ้อยคำที่แน่นอนนั้นเกี่ยวกับว่าพวกเขาสามารถ "สันนิษฐานว่าจะยุติ" หรือไม่ หากลูปไม่สามารถยุติได้นั่นหมายความว่าโค้ดในภายหลังนั้นไม่สามารถเข้าถึงได้ไม่ว่าคุณจะโต้แย้งอะไรเกี่ยวกับคณิตศาสตร์และอินฟินิตี้และต้องใช้เวลานานเท่าใดในการทำงานจำนวนมหาศาลบนเครื่องจักรสมมุติ
นอกจากนั้นเสียงดังกังวานไม่เพียง แต่เป็น DeathStation 9000 ที่เป็นไปตามมาตรฐาน ISO C เท่านั้น แต่ตั้งใจให้เป็นประโยชน์สำหรับการเขียนโปรแกรมระบบระดับต่ำในโลกแห่งความเป็นจริงรวมถึงเมล็ดและสิ่งฝังตัว ดังนั้นไม่ว่าหรือไม่คุณยอมรับข้อโต้แย้งเกี่ยวกับ C11 ที่ช่วยให้การกำจัดของwhile(1);
มันไม่ได้ทำให้รู้สึกว่าเสียงดังกราวอยากจะทำจริงว่า ถ้าคุณเขียนwhile(1);
นั่นอาจไม่ใช่อุบัติเหตุ การกำจัดลูปที่สิ้นสุดโดยไม่ตั้งใจโดยอุบัติเหตุ (ด้วยนิพจน์ตัวแปรควบคุมรันไทม์) อาจเป็นประโยชน์และมันก็สมเหตุสมผลสำหรับคอมไพเลอร์ในการทำเช่นนั้น
เป็นเรื่องยากที่คุณจะต้องหมุนจนกว่าจะมีการขัดจังหวะครั้งต่อไป แต่ถ้าคุณเขียนลงใน C นั่นคือสิ่งที่คุณคาดหวัง (และจะเกิดอะไรขึ้นใน GCC และ Clang ยกเว้น Clang เมื่อวงวนไม่สิ้นสุดอยู่ในฟังก์ชัน wrapper)
ตัวอย่างเช่นในเคอร์เนลระบบปฏิบัติการดั้งเดิมเมื่อตัวกำหนดตารางเวลาไม่มีภารกิจในการรันมันอาจรันภารกิจว่าง while(1);
การดำเนินแรกของการที่อาจจะมี
หรือสำหรับฮาร์ดแวร์ที่ไม่มีคุณสมบัติการประหยัดพลังงานซึ่งอาจเป็นการใช้งานเพียงอย่างเดียว (จนถึงต้นยุค 2000 นั่นคือฉันคิดว่าไม่หายากใน x86 แม้ว่าhlt
คำสั่งจะมีอยู่จริง IDK ถ้ามันช่วยประหยัดพลังงานได้อย่างมีความหมายจนกระทั่ง CPU เริ่มมีสถานะไม่ใช้พลังงานต่ำ)