Lý do đằng sau các chức năng thư viện C không bao giờ đặt errno thành 0


9

Tiêu chuẩn C bắt buộc rằng không có chức năng thư viện chuẩn C nào được đặt errnothành không. Tại sao chính xác là đây?

Tôi có thể hiểu nó hữu ích khi gọi một số chức năng và chỉ kiểm tra errnosau chức năng cuối cùng - ví dụ:

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

Tuy nhiên, điều này không được coi là "thực hành xấu"? Vì bạn đang chỉ kiểm tra errnovào cuối, bạn chỉ biết rằng một trong những chức năng đã thất bại, nhưng không chức năng thất bại. Chỉ đơn giản là biết rằng một cái gì đó thất bại đủ tốt cho hầu hết các chương trình thực tế?

Tôi đã cố gắng tìm kiếm các tài liệu Cơ sở C khác nhau, nhưng nhiều tài liệu không có nhiều chi tiết <errno.h>.


1
Tôi đoán đó là "không trả tiền cho những gì bạn không muốn". Nếu bạn quan tâm errno, bạn luôn có thể tự đặt nó về 0.
Kerrek SB

2
Một điều khác cần lưu ý là một hàm có thể được đặt thành errnogiá trị khác không ngay cả khi nó thành công. (Nó có thể gọi một số chức năng khác không thành công, nhưng điều đó không tạo thành một sự thất bại trong chức năng bên ngoài.)
Keith Thompson

Câu trả lời:


10

Thư viện C không được đặt errnothành 0 vì lý do lịch sử 1 . POSIX không còn tuyên bố các thư viện của mình sẽ không thay đổi giá trị trong trường hợp thành công và trang người dùng Linux mớierrno.h phản ánh điều này:

Tệp <errno.h>tiêu đề xác định biến số nguyên errno, được đặt bởi các lệnh gọi hệ thống và một số hàm thư viện trong trường hợp có lỗi để chỉ ra lỗi sai. Giá trị của nó chỉ có ý nghĩa khi giá trị trả về của cuộc gọi chỉ ra lỗi (nghĩa là -1từ hầu hết các cuộc gọi hệ thống; -1hoặc NULLtừ hầu hết các chức năng của thư viện); một chức năng thành công được phép thay đổi errno.

sở lý luận ANSI C tuyên bố rằng ủy ban cảm thấy thực tế hơn khi áp dụng và tiêu chuẩn hóa việc sử dụng hiện có errno.

Các máy móc báo cáo lỗi tập trung vào cài đặt errnothường được coi là có khả năng chịu đựng tốt nhất. Nó đòi hỏi một `` khớp nối bệnh lý '' giữa các chức năng của thư viện và sử dụng một ô nhớ có thể ghi tĩnh, gây cản trở việc xây dựng các thư viện có thể chia sẻ. Tuy nhiên, Ủy ban ưu tiên tiêu chuẩn hóa máy móc hiện có, tuy nhiên thiếu này hơn là phát minh ra thứ gì đó tham vọng hơn.

Hầu như luôn luôn có một cách để kiểm tra lỗi ngoài việc kiểm tra nếu errnođã được đặt. Kiểm tra nếu errnođã đặt không phải lúc nào cũng đáng tin cậy, vì một số cuộc gọi yêu cầu gọi API riêng để nhận lý do lỗi. Chẳng hạn, ferror()được sử dụng để kiểm tra lỗi nếu bạn nhận được kết quả ngắn từ fread()hoặc fwrite().

Thật thú vị, ví dụ về việc sử dụng của bạn strtod()là một trong những trường hợp cài đặt errnothành 0 trước khi cuộc gọi được yêu cầu để phát hiện chính xác nếu xảy ra lỗi. Tất cả các hàm strto*()chuỗi đến số đều có yêu cầu này, bởi vì giá trị trả về hợp lệ được trả về ngay cả khi gặp lỗi.

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? */
    }
}

Đoạn mã trên dựa trên hành vi strtod()như được ghi lại trên Linux . Tiêu chuẩn C chỉ quy định rằng dòng chảy bên dưới không thể trả về giá trị lớn hơn giá trị dương nhỏ nhất doublevà việc có errnođược đặt thành hay không ERANGEđược thực hiện được xác định 2 .

Trên thực tế, có một bài viết tư vấn chứng nhận mở rộng khuyến nghị luôn luôn đặt errnothành 0 trước khi gọi thư viện và kiểm tra giá trị của nó sau khi cuộc gọi cho biết đã xảy ra lỗi . Điều này là do một số cuộc gọi thư viện sẽ được đặt errnongay cả khi cuộc gọi đó đã thành công 3 .

Giá trị errnolà 0 khi khởi động chương trình, nhưng nó không bao giờ được đặt thành 0 bởi bất kỳ chức năng thư viện nào. Giá trị của errnocó thể được đặt thành khác không bằng lệnh gọi hàm thư viện cho dù có lỗi hay không, miễn là việc sử dụng errnokhông được ghi lại trong phần mô tả chức năng trong Tiêu chuẩn C. Nó có ý nghĩa đối với một chương trình kiểm tra nội dung errnochỉ sau khi một lỗi được báo cáo. Chính xác hơn, errnochỉ có ý nghĩa sau khi hàm thư viện đặt errnolỗi đã trả về mã lỗi.


1. Trước đây tôi đã tuyên bố rằng đó là để tránh che giấu lỗi từ một cuộc gọi trước đó. Tôi không thể tìm thấy bất kỳ bằng chứng nào để hỗ trợ cho tuyên bố này. Tôi cũng đã có một printf()ví dụ không có thật .
2. Cảm ơn @chux đã chỉ ra điều này. Tham chiếu là C.11 §7.22.1.3 10.
3. Được chỉ ra bởi @KeithThndry trong một bình luận.


Vấn đề nhỏ: xuất hiện dưới dòng có thể dẫn đến kết quả khác 0: "các hàm trả về giá trị có cường độ không lớn hơn số dương bình thường nhỏ nhất trong loại trả về" C11 7.22.1.3.10.
chux - Phục hồi lại

@chux: Cảm ơn. Tôi sẽ chỉnh sửa. Kể từ khi thiết lập errnođể ERANGEđược thực hiện theo quy định tại trường hợp của Van cân bằng, có thực sự là không có cách nào để phát hiện di underflow. Mã của tôi tuân theo những gì tôi tìm thấy trong trang người dùng Linux trên hệ thống của tôi.
jxh

Nhận xét của bạn về các strto*chức năng chính xác là lý do tại sao tôi hỏi liệu ví dụ của tôi có bị coi là thực hành xấu hay không, nhưng đó là cách duy nhất errnokhông được đặt thành 0 sẽ hữu ích hoặc thậm chí áp dụng.

@DrewMcGowen: Tôi đoán rằng điểm duy nhất bạn có thể thực hiện là errnocó thể được đặt ngay cả trong trường hợp thành công, vì vậy chỉ cần sử dụng thực tế là nó đã được đặt không thực sự là một chỉ báo đủ tốt để xảy ra lỗi. Bạn phải tự kiểm tra kết quả của từng cuộc gọi để biết có lỗi xảy ra hay không.
jxh

1

Bạn có thể kiểm tra lỗi cho cả hai chức năng gọi, nếu bạn thực sự quan tâm.

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

Vì chúng ta không bao giờ biết làm thế nào hàm mach được gọi trong một tiến trình, làm thế nào lib có thể đặt errnos cho mỗi lệnh gọi, phải không?


1

Tự ý thay đổi lỗi sai tương tự như 'bắt và nuốt' một ngoại lệ. Trước khi có những trường hợp ngoại lệ sẽ lan truyền qua các lớp khác nhau của chương trình và cuối cùng đạt đến điểm mà người gọi sẽ bắt và trả lời ngoại lệ theo một cách nào đó hoặc đơn giản vượt qua giới hạn, đã có lỗi. Không thay đổi errno, trừ khi bạn bằng cách nào đó xử lý, ghi đè, coi là không liên quan, lỗi, là điều cần thiết cho mô hình lan truyền xử lý lỗi thế hệ đầu tiên này.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.