การใช้ assert () ในการปฏิบัติที่ไม่ดีของ C ++ หรือไม่?


94

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

ในทางกลับกัน C ++ กำหนดstd::logic_errorซึ่งหมายถึงการโยนในกรณีที่มีข้อผิดพลาดในตรรกะของโปรแกรม (ดังนั้นชื่อ) การโยนอินสแตนซ์อาจเป็นทางเลือกที่สมบูรณ์แบบของ C ++ assertมากกว่า

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

ฉันนึกถึงความคิดเห็นสามประการเกี่ยวกับปัญหานี้:

  • ยึดมั่นในการยืนยันของ C เนื่องจากโปรแกรมถูกยกเลิกทันทีจึงไม่สำคัญว่าการเปลี่ยนแปลงจะถูกยกเลิกอย่างถูกต้องหรือไม่ นอกจากนี้การใช้#defines ใน C ++ ก็แย่พอ ๆ กัน
  • โยนข้อยกเว้นและจับมันใน main () การอนุญาตให้รหัสข้ามตัวทำลายในสถานะใด ๆ ของโปรแกรมถือเป็นการปฏิบัติที่ไม่ดีและต้องหลีกเลี่ยงค่าใช้จ่ายทั้งหมดดังนั้นจึงมีการเรียกร้องให้ยุติ () หากมีการโยนข้อยกเว้นจะต้องถูกจับ
  • ทิ้งข้อยกเว้นและให้มันยุติโปรแกรม ข้อยกเว้นในการยุติโปรแกรมนั้นไม่เป็นไรและเนื่องจากNDEBUGสิ่งนี้จะไม่เกิดขึ้นในรุ่นสร้าง การจับเป็นสิ่งที่ไม่จำเป็นและเปิดเผยรายละเอียดการนำโค้ดภายในไปmain()ใช้

มีคำตอบที่ชัดเจนสำหรับปัญหานี้หรือไม่? อ้างอิงมืออาชีพใด ๆ ?

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


22
ไม่จริงlogic_errorเป็นข้อผิดพลาดทางตรรกะ ข้อผิดพลาดในตรรกะของโปรแกรมเรียกว่าข้อบกพร่อง คุณไม่ได้แก้บั๊กด้วยการทิ้งข้อยกเว้น
R.Martinho Fernandes

4
การยืนยันข้อยกเว้นรหัสข้อผิดพลาด แต่ละอันมีกรณีการใช้งานที่แตกต่างกันโดยสิ้นเชิงและคุณไม่ควรใช้กรณีอื่นที่จำเป็น
Kerrek SB

5
ตรวจสอบให้แน่ใจว่าคุณใช้ในstatic_assertที่ที่เหมาะสมหากคุณมีอยู่
Flexo

4
@trion ฉันไม่เห็นว่ามันช่วยได้อย่างไร คุณจะโยนstd::bug?
R.Martinho Fernandes

3
@trion: อย่าทำอย่างนั้น ข้อยกเว้นไม่ใช่สำหรับการดีบัก อาจมีคนจับข้อยกเว้น ไม่จำเป็นต้องกังวลเกี่ยวกับ UB เมื่อโทรstd::abort(); มันจะส่งสัญญาณที่ทำให้กระบวนการยุติลง
Kerrek SB

คำตอบ:


74

การยืนยันมีความเหมาะสมอย่างยิ่งในรหัส C ++ ข้อยกเว้นและกลไกการจัดการข้อผิดพลาดอื่น ๆ ไม่ได้มีไว้สำหรับสิ่งเดียวกับการยืนยัน

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

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


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

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


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

101
  • ยืนยันมีการแก้จุดบกพร่อง ผู้ใช้รหัสที่จัดส่งของคุณไม่ควรเห็น หากมีการยืนยันรหัสของคุณจะต้องได้รับการแก้ไข

    CWE-617: Reachable Assertion

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

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

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

  • จะมีการยกเว้นสำหรับกรณีพิเศษ หากพบผู้ใช้จะไม่สามารถทำตามที่ต้องการได้ แต่อาจกลับไปทำงานที่อื่นได้

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


1
มามองหาสิ่งนี้ยืนยัน; การยืนยันรูปแบบใด ๆ ที่ส่งผ่านไปยังรหัสการผลิตชี้ไปที่การออกแบบและการควบคุมคุณภาพที่ไม่ดี จุดที่เรียกการยืนยันคือจุดที่ควรจัดการเงื่อนไขข้อผิดพลาดอย่างสง่างาม (ฉันไม่เคยใช้คำยืนยัน) สำหรับข้อยกเว้นกรณีการใช้งานเดียวที่ฉันรู้คือเมื่อ ctor อาจล้มเหลวส่วนอื่น ๆ ทั้งหมดมีไว้สำหรับการจัดการข้อผิดพลาดตามปกติ
slashmais

5
@slashmais: ความเชื่อมั่นเป็นที่น่ายกย่อง แต่ถ้าคุณไม่ได้จัดส่งรหัสที่สมบูรณ์แบบและปราศจากข้อบกพร่องฉันพบว่าการยืนยัน (แม้กระทั่งสิ่งที่ทำให้ผู้ใช้ขัดข้อง) ดีกว่าพฤติกรรมที่ไม่ได้กำหนด ข้อบกพร่องเกิดขึ้นในระบบที่ซับซ้อนและด้วยการยืนยันคุณจะมีวิธีดูและวินิจฉัยได้ว่าเกิดขึ้นที่ไหน
Kerrek SB

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

14

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


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

หากการยืนยันล้มเหลวหมายความว่าตรรกะของส่วนหนึ่งของโปรแกรมเสีย การยืนยันที่ล้มเหลวไม่จำเป็นต้องหมายความว่าจะไม่มีอะไรสำเร็จ ปลั๊กอินที่เสียอาจไม่ควรยกเลิกโปรแกรมประมวลผลคำทั้งหมด
daemonspring

13

การไม่ใช้ผู้ทำลายเนื่องจาก alling abort () ไม่ใช่พฤติกรรมที่ไม่ได้กำหนด!

ถ้าเป็นเช่นนั้นก็จะเป็นพฤติกรรมที่ไม่ได้กำหนดไว้ที่จะเรียกstd::terminate()เช่นกันดังนั้นอะไรคือจุดที่จะให้มัน?

assert() มีประโยชน์เช่นเดียวกับ C ++ เช่นเดียวกับใน C การยืนยันไม่ได้มีไว้สำหรับจัดการข้อผิดพลาด แต่เป็นการยกเลิกโปรแกรมทันที


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

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

6

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

ฉันจะจัดกลุ่มเป็น 2 ประเภท:

  • บาปของนักพัฒนา (เช่นฟังก์ชันความน่าจะเป็นที่ส่งกลับค่าลบ):

ความน่าจะเป็นลอย () {กลับ -1.0; }

ยืนยัน (ความน่าจะเป็น ()> = 0.0)

  • เครื่องเสีย (เช่นเครื่องที่รันโปรแกรมของคุณผิดพลาดมาก):

int x = 1;

ยืนยัน (x> 0);

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

และหากมีข้อผิดพลาดในการพัฒนาดังกล่าวคุณไม่ควรมั่นใจเกี่ยวกับกลไกการกู้คืนหรือการจัดการข้อผิดพลาด เช่นเดียวกับข้อผิดพลาดของฮาร์ดแวร์


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