С
Буква «х» була загублена у файлі. Для його пошуку була написана програма:
#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");
}
}
Він був складений і пробіг, і він нарешті кричить:
Hurray, letter lost in the file is finally found!
Протягом багатьох років листи рятувались таким чином, поки не прийшов новий хлопець і не оптимізував код. Він був знайомий з типами даних і знав, що для негативних значень краще використовувати непідписані, ніж підписані, оскільки він має ширший діапазон і забезпечує певний захист від переливів. Тож він змінив int на непідписаний int . Він також досить добре знав ascii, щоб знати, що вони завжди мають негативне значення. Тому він також змінив char на непідписаний char . Він склав код і пішов додому пишаючись хорошою справою, яку зробив. Програма виглядала так:
#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");
}
}
На наступний день він повернувся до хаосу. Лист "a" відсутній, і хоча він мав бути у "desert_file", що містить "abc", програма шукала його назавжди, роздруковуючи лише:
Searching file for missing letter a...
Вони звільнили хлопця і повернулися до попередньої версії, пам’ятаючи, що ніколи не слід оптимізувати типи даних у робочому коді.
Але який урок вони мали б тут засвоїти?
Перш за все, якщо ви подивитеся на таблицю ascii, ви помітите, що немає EOF. Це тому, що EOF - це не символ, а особливе значення, повернене з fgetc (), яке може повернути символ, розширений до int, або -1, що позначає кінець файлу.
Поки ми використовуємо підписаний char, все працює добре - char рівний 50 розширюється fgetc () на int, рівний 50. Потім ми перетворюємо його назад у char і все ще має 50. Те саме відбувається для -1 або будь-якого іншого виводу, що надходить від fgetc ().
Але подивіться, що станеться, коли ми використовуємо неподписані знаки. Ми починаємо з char у fgetc (), розширюємо його до int, а потім хочемо мати неподписаний char. Єдина проблема полягає в тому, що ми не можемо зберегти -1 у неподписаних знаках. Програма зберігає його як 255, що вже не дорівнює EOF.
Caveat
Якщо ви подивитесь на розділ 3.1.2.5 Типи в копії документації ANSI C, ви дізнаєтесь, підписаний чи ні знак char залежить тільки від реалізації. Тож хлопця, мабуть, не слід звільняти, оскільки він знайшов дуже хитру помилку, що ховається в коді. Це може вийти при зміні компілятора або переході до іншої архітектури. Цікаво, кого б звільнили, якби помилка вийшла в такому випадку;)
PS. Програма була побудована навколо помилки, згаданої Пол А. Картером у мові складання ПК