C
Chữ "x" đã bị mất trong một tập tin. Một chương trình đã được viết để tìm thấy nó:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
FILE* fp = fopen("desert_file", "r");
char letter;
char missing_letter = argv[1][0];
int found = 0;
printf("Searching file for missing letter %c...\n", missing_letter);
while( (letter = fgetc(fp)) != EOF ) {
if (letter == missing_letter) found = 1;
}
printf("Whole file searched.\n");
fclose(fp);
if (found) {
printf("Hurray, letter lost in the file is finally found!\n");
} else {
printf("Haven't found missing letter...\n");
}
}
Nó được biên dịch và chạy và cuối cùng nó hét lên:
Hurray, letter lost in the file is finally found!
Trong nhiều năm, các bức thư đã được giải cứu theo cách này cho đến khi anh chàng mới đến và tối ưu hóa mã. Anh ta đã quen thuộc với các kiểu dữ liệu và biết rằng tốt hơn là sử dụng không dấu hơn là ký cho các giá trị không âm vì nó có phạm vi rộng hơn và bảo vệ chống tràn. Vì vậy, anh thay đổi int thành int unsign . Anh ta cũng biết rõ về ascii để biết rằng chúng luôn có giá trị không âm. Vì vậy, ông cũng thay đổi char thành char không dấu . Anh ta biên soạn mã và về nhà tự hào về công việc tốt mà anh ta đã làm. Chương trình trông như thế này:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
FILE* fp = fopen("desert_file", "r");
unsigned char letter;
unsigned char missing_letter = argv[1][0];
unsigned int found = 0;
printf("Searching file for missing letter %c...\n", missing_letter);
while( (letter = fgetc(fp)) != EOF ) {
if (letter == missing_letter) found = 1;
}
printf("Whole file searched.\n");
fclose(fp);
if (found) {
printf("Hurray, letter lost in the file is finally found!\n");
} else {
printf("Haven't found missing letter...\n");
}
}
Anh ta trở lại một sự tàn phá vào ngày hôm sau. Chữ "a" bị thiếu và mặc dù nó được cho là nằm trong "Sah_file" có chứa "abc", chương trình đang tìm kiếm nó mãi mãi chỉ in ra:
Searching file for missing letter a...
Họ đã sa thải anh chàng và quay trở lại phiên bản trước đó nhớ rằng người ta không bao giờ nên tối ưu hóa các kiểu dữ liệu trong mã làm việc.
Nhưng bài học mà họ nên học ở đây là gì?
Trước hết, nếu bạn xem bảng ascii bạn sẽ nhận thấy rằng không có EOF. Đó là bởi vì EOF không phải là một ký tự mà là một giá trị đặc biệt được trả về từ fgetc (), có thể trả về ký tự được mở rộng thành int hoặc -1 biểu thị phần cuối của tệp.
Miễn là chúng ta đang sử dụng char đã ký, mọi thứ đều hoạt động tốt - char bằng 50 được mở rộng bởi fgetc () thành int bằng 50. Sau đó, chúng tôi chuyển đổi nó trở lại thành char và vẫn có 50. Điều tương tự xảy ra với -1 hoặc bất kỳ đầu ra nào khác đến từ fgetc ().
Nhưng hãy nhìn những gì xảy ra khi chúng ta sử dụng char không dấu. Chúng tôi bắt đầu với một char trong fgetc () mở rộng nó thành int và sau đó muốn có một char không dấu. Vấn đề duy nhất là chúng ta không thể bảo toàn -1 trong char không dấu. Chương trình đang lưu trữ nó là 255 mà không còn bằng EOF.
Hãy cẩn thận
Nếu bạn xem phần 3.1.2.5 Các loại trong bản sao tài liệu ANSI C, bạn sẽ thấy rằng char có được ký hay không chỉ phụ thuộc vào việc thực hiện. Vì vậy, anh chàng có lẽ không nên bị sa thải khi anh ta tìm thấy một lỗi rất khó ẩn giấu trong mã. Nó có thể xuất hiện khi thay đổi trình biên dịch hoặc chuyển sang kiến trúc khác. Tôi tự hỏi ai sẽ bị sa thải nếu lỗi xuất hiện trong trường hợp như vậy;)
Tái bút Chương trình được xây dựng xung quanh lỗi được đề cập trong Ngôn ngữ hội PC của Paul A. Carter