ฉันพบคำถามหลายครั้งและต้องการช่วยตอบคำถามที่ครอบคลุมมากขึ้น ฉันคิดว่าวิธีที่ดีที่สุดในการคิดเกี่ยวกับสิ่งนี้คือวิธีคืนข้อผิดพลาดไปยังผู้โทรและอะไรคุณส่งคืน
อย่างไร
มี 3 วิธีในการส่งคืนข้อมูลจากฟังก์ชัน:
- ส่งคืนค่า
- ออกอาร์กิวเมนต์
- Out of Band, ที่มีไม่ใช่ goto (setjmp / longjmp), ไฟล์หรือตัวแปรที่กำหนดขอบเขตแบบโกลบอล, ระบบไฟล์ ฯลฯ
ส่งคืนค่า
คุณสามารถส่งคืนค่าได้เพียงวัตถุเดียวอย่างไรก็ตามมันอาจจะซับซ้อนโดยพลการ นี่คือตัวอย่างของฟังก์ชันการส่งคืนข้อผิดพลาด:
enum error hold_my_beer();
ข้อดีอย่างหนึ่งของค่าตอบแทนคืออนุญาตให้มีการผูกมัดการเรียกสำหรับการจัดการข้อผิดพลาดที่รบกวนน้อย:
!hold_my_beer() &&
!hold_my_cigarette() &&
!hold_my_pants() ||
abort();
สิ่งนี้ไม่เพียงเกี่ยวกับความสามารถในการอ่านเท่านั้น แต่ยังช่วยให้สามารถประมวลผลพอยน์เตอร์ของฟังก์ชันดังกล่าวได้อย่างสม่ำเสมอ
ออกอาร์กิวเมนต์
คุณสามารถส่งคืนมากกว่าผ่านวัตถุมากกว่าหนึ่งรายการผ่านการขัดแย้ง แต่แนวปฏิบัติที่ดีที่สุดแนะนำให้รักษาจำนวนอาร์กิวเมนต์ให้ต่ำ (พูดว่า <= 4):
void look_ma(enum error *e, char *what_broke);
enum error e;
look_ma(e);
if(e == FURNITURE) {
reorder(what_broke);
} else if(e == SELF) {
tell_doctor(what_broke);
}
ออกจากวงดนตรี
ด้วย setjmp () คุณกำหนดสถานที่และวิธีที่คุณต้องการจัดการกับค่า int และคุณโอนการควบคุมไปยังตำแหน่งนั้นผ่านทาง longjmp () ดูการใช้งานจริงของ setjmp และ longjmp ใน CC
อะไร
- ตัวบ่งชี้
- รหัส
- วัตถุ
- โทรกลับ
ตัวบ่งชี้
ตัวบ่งชี้ข้อผิดพลาดเพียงบอกคุณว่ามีปัญหา แต่ไม่มีอะไรเกี่ยวกับลักษณะของปัญหาที่กล่าวถึง:
struct foo *f = foo_init();
if(!f) {
/// handle the absence of foo
}
นี่เป็นวิธีที่มีประสิทธิภาพน้อยที่สุดสำหรับฟังก์ชั่นในการสื่อสารสถานะข้อผิดพลาด แต่สมบูรณ์แบบหากผู้โทรไม่สามารถตอบสนองต่อข้อผิดพลาดในลักษณะที่จบการศึกษาได้
รหัส
รหัสข้อผิดพลาดบอกผู้โทรเกี่ยวกับลักษณะของปัญหาและอาจอนุญาตการตอบสนองที่เหมาะสม (จากด้านบน) มันสามารถเป็นค่าตอบแทนหรือตัวอย่างเช่น look_ma () ด้านบนอาร์กิวเมนต์ข้อผิดพลาด
วัตถุ
ด้วยวัตถุข้อผิดพลาดผู้โทรสามารถทราบเกี่ยวกับปัญหาที่ซับซ้อนโดยพลการ ตัวอย่างเช่นรหัสข้อผิดพลาดและข้อความที่มนุษย์สามารถอ่านได้ นอกจากนี้ยังสามารถแจ้งให้ผู้โทรทราบว่ามีหลายสิ่งที่ผิดพลาดหรือเกิดข้อผิดพลาดต่อรายการเมื่อประมวลผลการรวบรวม:
struct collection friends;
enum error *e = malloc(c.size * sizeof(enum error));
...
ask_for_favor(friends, reason);
for(int i = 0; i < c.size; i++) {
if(reason[i] == NOT_FOUND) find(friends[i]);
}
แทนที่จะจัดสรรอาเรย์ข้อผิดพลาดล่วงหน้าคุณยังสามารถจัดสรรได้อีกครั้งแบบไดนามิกตามที่ต้องการแน่นอน
โทรกลับ
การโทรกลับเป็นวิธีที่มีประสิทธิภาพที่สุดในการจัดการข้อผิดพลาดเนื่องจากคุณสามารถบอกฟังก์ชั่นว่าพฤติกรรมที่คุณต้องการเห็นเกิดขึ้นเมื่อมีสิ่งผิดปกติ อาร์กิวเมนต์โทรกลับสามารถเพิ่มเข้าไปในแต่ละฟังก์ชั่นหรือถ้าคุณต้องการปรับแต่งตามความต้องการของอินสแตนซ์ของโครงสร้างเช่นนี้:
struct foo {
...
void (error_handler)(char *);
};
void default_error_handler(char *message) {
assert(f);
printf("%s", message);
}
void foo_set_error_handler(struct foo *f, void (*eh)(char *)) {
assert(f);
f->error_handler = eh;
}
struct foo *foo_init() {
struct foo *f = malloc(sizeof(struct foo));
foo_set_error_handler(f, default_error_handler);
return f;
}
struct foo *f = foo_init();
foo_something();
ประโยชน์ที่น่าสนใจอย่างหนึ่งของการโทรกลับคือสามารถเรียกใช้ได้หลายครั้งหรือไม่มีเลยในกรณีที่ไม่มีข้อผิดพลาดซึ่งไม่มีค่าใช้จ่ายบนเส้นทางแห่งความสุข
อย่างไรก็ตามมีการผกผันของการควบคุม รหัสการโทรไม่ทราบว่ามีการเรียกกลับหรือไม่ ด้วยเหตุนี้จึงอาจเหมาะสมที่จะใช้ตัวบ่งชี้