เหตุผลที่อยู่เบื้องหลังฟังก์ชันไลบรารี C ไม่เคยตั้งค่า errno เป็นศูนย์


9

เอกสารมาตรฐาน C ที่ไม่มีฟังก์ชันไลบรารีมาตรฐาน C ตั้งerrnoเป็นศูนย์ ทำไมถึงเป็นอย่างนี้

ฉันเข้าใจว่ามันมีประโยชน์สำหรับการเรียกฟังก์ชั่นหลาย ๆ อย่างและตรวจสอบเฉพาะerrnoหลังจากที่ผ่านมา - ตัวอย่างเช่น:

errno = 0;
double x = strtod(str1, NULL);
long y = strtol(str2, NULL);
if (errno)
    // either "strtod" or "strtol" failed
else
    // both succeeded

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

ผมพยายามมองหาเอกสารต่างๆ C เหตุผล <errno.h>แต่ส่วนมากของพวกเขาไม่ได้มีรายละเอียดมากสำหรับ


1
ฉันเดาว่า "ไม่ต้องจ่ายเงินสำหรับสิ่งที่คุณไม่ต้องการ" หากคุณสนใจerrnoคุณสามารถตั้งค่าให้เป็นศูนย์ด้วยตัวเอง
Kerrek SB

2
สิ่งอื่นที่ต้องระวังคือฟังก์ชั่นสามารถตั้งค่าerrnoที่ไม่เป็นศูนย์แม้ว่ามันจะประสบความสำเร็จ (มันอาจจะเรียกฟังก์ชั่นอื่น ๆ ที่ล้มเหลว แต่นั่นก็ไม่ได้เป็นความผิดพลาดในฟังก์ชั่นด้านนอก)
Keith Thompson

คำตอบ:


10

ห้องสมุด C ไม่ได้ตั้งค่าerrnoเป็น 0 เพื่อเหตุผลทางประวัติศาสตร์1 POSIX ไม่อ้างว่าห้องสมุดจะไม่เปลี่ยนค่าในกรณีที่ประสบความสำเร็จอีกต่อไปและหน้าลินุกซ์ใหม่สำหรับการerrno.hสะท้อนสิ่งนี้:

<errno.h>ไฟล์ส่วนหัวกำหนดตัวแปรจำนวนเต็มerrnoซึ่งถูกกำหนดโดยสายระบบและบางฟังก์ชั่นห้องสมุดในกรณีที่มีข้อผิดพลาดในการบ่งบอกถึงสิ่งที่ผิดไป ค่าของมันมีความสำคัญเฉพาะเมื่อค่าส่งคืนของการโทรระบุข้อผิดพลาด (เช่น-1จากการเรียกของระบบส่วนใหญ่-1หรือNULLจากฟังก์ชั่นห้องสมุดส่วนใหญ่); ฟังก์ชั่นที่ประสบความสำเร็จจะerrnoได้รับอนุญาตให้มีการเปลี่ยนแปลง

ANSI C เหตุผลerrnoระบุว่าคณะกรรมการรู้สึกว่ามันเป็นจริงมากขึ้นเพื่อนำมาใช้และมาตรฐานการปฏิบัติที่มีอยู่ของการใช้

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

มีวิธีการตรวจสอบข้อผิดพลาดนอกเหนือจากการตรวจสอบเกือบทุกครั้งหากerrnoตั้งค่าไว้ การตรวจสอบว่าerrnoชุดที่ตั้งไว้ไม่น่าเชื่อถือเสมอไปหรือไม่เนื่องจากการโทรบางครั้งจำเป็นต้องเรียก API แยกต่างหากเพื่อรับเหตุผลข้อผิดพลาด ยกตัวอย่างเช่นferror()จะใช้ในการตรวจสอบข้อผิดพลาดถ้าคุณได้รับผลระยะสั้น ๆ จากหรือfread()fwrite()

ที่น่าสนใจพอตัวอย่างของคุณของการใช้strtod()เป็นหนึ่งในกรณีที่การตั้งค่าerrnoให้เป็น 0 ก่อนที่สายจะต้องให้ถูกต้องตรวจสอบได้หากมีข้อผิดพลาดเกิดขึ้น ทั้งหมดstrto*()สตริงฟังก์ชั่นมีความต้องการจำนวนนี้เพราะค่าตอบแทนที่ถูกต้องจะถูกส่งกลับแม้ในใบหน้าของข้อผิดพลาด

errno = 0;
char *endptr;
double x = strtod(str1, &endptr);
if (endptr == str1) {
    /*...parse error */
} else if (errno == ERANGE) {
    if (x == 0) {
        /*...underflow */
    } else if (x == HUGE_VAL) {
        /*...positive overflow */
    } else if (x == -HUGE_VAL) {
        /*...negative overflow */
    } else {
        /*...unknown range error? */
    }
}

รหัสดังกล่าวจะขึ้นอยู่กับลักษณะการทำงานของstrtod()การบันทึกไว้บน Linux มาตรฐาน C เท่านั้นกำหนดว่า underflow ไม่สามารถกลับมากขึ้นคุ้มค่ากว่าในเชิงบวกที่เล็กที่สุดdoubleและไม่ว่าจะได้หรือไม่errnoถูกตั้งค่าให้ERANGEมีการดำเนินงานที่กำหนดไว้2

มีจริงกว้างขวางCERT Advisory เขียนขึ้นที่แนะนำเสมอการตั้งค่าerrnoให้เป็น 0 ก่อนที่จะโทรห้องสมุดและการตรวจสอบความคุ้มค่าหลังจากที่โทรแสดงให้เห็นความล้มเหลวที่เกิดขึ้น เพราะนี่คือบางสายห้องสมุดจะตั้งerrnoแม้ว่าการเรียกตัวเองประสบความสำเร็จ3

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


1. ก่อนหน้านี้ฉันอ้างว่าเพื่อหลีกเลี่ยงการปิดบังข้อผิดพลาดจากการโทรก่อนหน้านี้ ฉันไม่พบหลักฐานใด ๆ ที่สนับสนุนข้อเรียกร้องนี้ ฉันยังมีprintf()ตัวอย่างปลอม
2. ขอบคุณ @chux ที่ชี้เรื่องนี้ออกมา การอ้างอิงคือ C.11 §7.22.1.3¶10
3. ชี้โดย @KeithThompson ในความคิดเห็น


ปัญหาเล็กน้อย: ดูเหมือนว่าอันเดอร์โฟลว์อาจส่งผลให้เกิดผลลัพธ์ที่ไม่ใช่ 0: "ฟังก์ชันส่งคืนค่าที่ขนาดไม่ใหญ่กว่าจำนวนบวกปกติที่น้อยที่สุดในประเภทส่งคืน" C11 7.22.1.3.10
chux - Reinstate Monica

@ chux: ขอบคุณ ฉันจะแก้ไข ตั้งแต่การตั้งค่าerrnoที่จะERANGEมีการดำเนินการตามที่กำหนดไว้ในกรณีของอันเดอร์โฟล์มีจริงไม่มีทางแบบพกพาในการตรวจสอบ underflow รหัสของฉันติดตามสิ่งที่ฉันพบในหน้า man ของ Linux บนระบบของฉัน
jxh

ความคิดเห็นของคุณเกี่ยวกับstrto*ฟังก์ชั่นนั้นเป็นเหตุผลที่ฉันถามว่าตัวอย่างของฉันจะถูกพิจารณาว่าเป็นแนวปฏิบัติที่ไม่ดีหรือไม่ แต่เป็นวิธีเดียวที่errnoการตั้งค่าเป็นศูนย์จะมีประโยชน์หรือไม่

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

1

คุณสามารถตรวจสอบข้อผิดพลาดสำหรับการเรียกใช้ฟังก์ชั่นทั้งสองถ้าคุณสนใจจริงๆ

errno = 0;
double x = strtod(str1, NULL);
if (errno)
    // strtod"  failed
else
    // "strtod" succeeded

long y = strtol(str2, NULL);
if (errno)
    // "strtol" failed
else
    // "strtol" succeeded

เนื่องจากเราไม่เคยรู้ว่าฟังก์ชั่นมัคเรียกใช้ในกระบวนการอย่างไร lib สามารถตั้งค่า errnos สำหรับการเรียกใช้ฟังก์ชันแต่ละครั้งได้อย่างไร


1

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

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