UB ส่วนใหญ่ที่เรามักจะกังวลเช่น NULL-deref หรือหารด้วยศูนย์คือRuntime UB การคอมไพล์ฟังก์ชันที่จะทำให้รันไทม์ UB หากดำเนินการต้องไม่ทำให้คอมไพลเลอร์หยุดทำงาน เว้นแต่จะพิสูจน์ได้ว่าฟังก์ชัน (และเส้นทางนั้นผ่านฟังก์ชัน) จะถูกเรียกใช้งานโดยโปรแกรมอย่างแน่นอน
(ความคิดที่ 2: บางทีฉันอาจไม่ได้พิจารณา template / constexpr ที่จำเป็นต้องมีการประเมินผลในเวลารวบรวมอาจเป็นไปได้ว่า UB ในระหว่างนั้นได้รับอนุญาตให้สร้างความแปลกประหลาดโดยพลการระหว่างการแปลแม้ว่าจะไม่เคยเรียกฟังก์ชันผลลัพธ์ก็ตาม)
ลักษณะการทำงานระหว่างการแปลข้อความอ้างอิง ISO C ++ ในคำตอบของ @ StoryTellerนั้นคล้ายกับภาษาที่ใช้ในมาตรฐาน ISO C C ไม่รวมเทมเพลตหรือการประเมินที่constexpr
จำเป็นในเวลาคอมไพล์
แต่ความจริงที่น่าสนใจ : ISO C กล่าวไว้ในหมายเหตุว่าหากการแปลถูกยกเลิกจะต้องมีข้อความวินิจฉัย หรือ "การทำงานระหว่างการแปล ... ในลักษณะเอกสาร" ฉันไม่คิดว่าจะอ่านว่า "เพิกเฉยต่อสถานการณ์โดยสิ้นเชิง" รวมถึงการหยุดแปลด้วย
คำตอบเก่าเขียนก่อนที่ฉันจะเรียนรู้เกี่ยวกับ UB เวลาแปล เป็นเรื่องจริงสำหรับ runtime-UB และอาจยังมีประโยชน์
ไม่มีสิ่งที่เรียกว่า UB ที่เกิดขึ้นในเวลาคอมไพล์ คอมไพเลอร์สามารถมองเห็นได้ตามเส้นทางการดำเนินการที่แน่นอน แต่ในแง่ C ++ จะไม่เกิดขึ้นจนกว่าการดำเนินการจะถึงเส้นทางการดำเนินการผ่านฟังก์ชัน
ข้อบกพร่องในโปรแกรมที่ทำให้ไม่สามารถคอมไพล์ได้ไม่ใช่ UB นั่นคือข้อผิดพลาดทางไวยากรณ์ โปรแกรมดังกล่าว "มีรูปแบบไม่ถูกต้อง" ในคำศัพท์ภาษา C ++ (ถ้าฉันมีมาตรฐานที่ถูกต้อง) โปรแกรมสามารถสร้างได้ดี แต่มี UB ความแตกต่างระหว่างพฤติกรรมที่ไม่ได้กำหนดและรูปแบบที่ไม่ดีไม่จำเป็นต้องมีข้อความวินิจฉัย
เว้นแต่ฉันจะเข้าใจผิดบางอย่าง ISO C ++ ต้องการให้โปรแกรมนี้รวบรวมและดำเนินการอย่างถูกต้องเนื่องจากการดำเนินการไม่ถึงการหารด้วยศูนย์ (ในทางปฏิบัติ ( Godbolt ) คอมไพเลอร์ที่ดีเพียงแค่สร้างไฟล์ปฏิบัติการที่ใช้งานได้ gcc / clang เตือนx / 0
แต่ไม่ใช่เรื่องนี้แม้ว่าจะปรับให้เหมาะสม แต่อย่างไรก็ตามเรากำลังพยายามบอกว่าISO C ++ ที่ต่ำทำให้คุณภาพของการนำไปใช้งานเป็นอย่างไรดังนั้นการตรวจสอบ gcc / clang แทบจะไม่เป็นการทดสอบที่มีประโยชน์นอกเหนือจากการยืนยันว่าฉันเขียนโปรแกรมอย่างถูกต้อง)
int cause_UB() {
int x=0;
return 1 / x;
}
int main(){
if (0)
cause_UB();
}
กรณีการใช้งานสำหรับสิ่งนี้อาจเกี่ยวข้องกับตัวประมวลผลล่วงหน้า C หรือconstexpr
ตัวแปรและการแตกแขนงบนตัวแปรเหล่านั้นซึ่งนำไปสู่ความไร้สาระในบางเส้นทางที่ไม่มีทางเข้าถึงสำหรับตัวเลือกค่าคงเหล่านั้น
เส้นทางของการดำเนินการที่ UB สาเหตุรวบรวมเวลาที่มองเห็นอาจจะคิดที่จะไม่เคยใช้เช่นคอมไพเลอร์สำหรับ x86 สามารถปล่อยud2
(สาเหตุการเรียนการสอนยกเว้นผิดกฎหมาย) cause_UB()
เป็นคำนิยามสำหรับ หรือภายในฟังก์ชันหากด้านใดด้านหนึ่งของif()
การนำไปสู่UB ที่สามารถพิสูจน์ได้สาขานั้นสามารถลบออก
แต่คอมไพเลอร์ยังคงมีการรวบรวมทุกอย่างอื่นในการมีสติและถูกต้องวิธี เส้นทางทั้งหมดที่ไม่พบ (หรือไม่สามารถพิสูจน์ได้ว่าพบ) UB ยังต้องถูกคอมไพล์เป็น asm ที่ดำเนินการราวกับว่าเครื่องนามธรรม C ++ กำลังทำงานอยู่
คุณสามารถโต้แย้งว่า UB ที่มองเห็นได้ในเวลาคอมไพล์โดยไม่มีเงื่อนไขmain
เป็นข้อยกเว้นสำหรับกฎนี้ หรือคอมไพล์ตามเวลาที่พิสูจน์ได้ว่าการดำเนินการที่เริ่มต้นmain
ในความเป็นจริงถึง UB ที่รับประกัน
ฉันยังคงเถียงว่าพฤติกรรมของคอมไพเลอร์ตามกฎหมายรวมถึงการผลิตระเบิดมือที่ระเบิดหากมีการทำงาน คำจำกัดความของคำจำกัดความmain
นั้นประกอบด้วยคำสั่งเดียวที่ผิดกฎหมาย ฉันขอยืนยันว่าถ้าคุณไม่เคยรันโปรแกรมแสดงว่ายังไม่มี UB เลย ตัวคอมไพเลอร์เองไม่ได้รับอนุญาตให้ระเบิด IMO
ฟังก์ชั่นที่มี UB ที่เป็นไปได้หรือพิสูจน์ได้ภายในสาขา
UB ตามเส้นทางการดำเนินการใด ๆ ที่กำหนดจะย้อนเวลากลับไปเพื่อ "ปนเปื้อน" โค้ดก่อนหน้าทั้งหมด แต่ในทางปฏิบัติคอมไพเลอร์สามารถใช้ประโยชน์จากกฎนั้นได้ก็ต่อเมื่อพวกเขาสามารถพิสูจน์ได้ว่าเส้นทางของการดำเนินการนั้นนำไปสู่ UB ที่มองเห็นได้ในเวลาคอมไพล์ เช่น
int minefield(int x) {
if (x == 3) {
*(char*)nullptr = x/0;
}
return x * 5;
}
คอมไพเลอร์ต้องสร้าง asm ที่ใช้งานได้กับสิ่งx
อื่น ๆทั้งหมดที่ไม่ใช่ 3 จนถึงจุดที่x * 5
ทำให้เกิดการเซ็น - โอเวอร์โฟลว์ UB ที่ INT_MIN และ INT_MAX หากไม่เคยเรียกใช้ฟังก์ชันนี้x==3
โปรแกรมจะไม่มี UB และต้องทำงานตามที่เขียนไว้
เราอาจเขียนด้วยif(x == 3) __builtin_unreachable();
GNU C เพื่อบอกคอมไพเลอร์ว่าx
ไม่ใช่ 3 อย่างแน่นอน
ในทางปฏิบัติมีโค้ด "ที่วางทุ่นระเบิด" อยู่ทั่วทุกที่ในโปรแกรมปกติ เช่นการหารด้วยจำนวนเต็มสัญญากับคอมไพเลอร์ว่ามันไม่ใช่ศูนย์ ตัวชี้ใด ๆ deref สัญญากับคอมไพเลอร์ว่าไม่ใช่โมฆะ