C
A letra "x" foi perdida em um arquivo. Um programa foi escrito para encontrá-lo:
#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");
}
}
Foi compilado e rodado e finalmente gritou:
Hurray, letter lost in the file is finally found!
Por muitos anos, as cartas foram resgatadas dessa maneira até que o novo funcionário veio e otimizou o código. Ele conhecia os tipos de dados e sabia que é melhor usar valores não assinados do que assinados para valores não negativos, pois ele possui um intervalo mais amplo e oferece alguma proteção contra estouros. Então ele mudou int para unsigned int . Ele também conhecia ascii o suficiente para saber que eles sempre têm valor não negativo. Então ele também mudou char para char não assinado . Ele compilou o código e voltou para casa orgulhoso do bom trabalho que fez. O programa ficou assim:
#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");
}
}
Ele voltou a uma confusão no dia seguinte. A letra "a" estava faltando e, embora devesse estar no "desert_file" que contém "abc", o programa estava procurando por ele para sempre imprimir apenas:
Searching file for missing letter a...
Eles demitiram o cara e voltaram para a versão anterior, lembrando que nunca se deve otimizar tipos de dados no código de trabalho.
Mas qual é a lição que eles deveriam ter aprendido aqui?
Primeiro de tudo, se você der uma olhada na tabela ascii, notará que não há EOF. Isso ocorre porque o EOF não é um caractere, mas um valor especial retornado de fgetc (), que pode retornar o caractere estendido para int ou -1, indicando o final do arquivo.
Enquanto estivermos usando char assinado, tudo funcionará bem - char igual a 50 é estendido por fgetc () para int igual a 50 também. Depois, transformamos novamente em char e ainda temos 50. O mesmo acontece para -1 ou qualquer outra saída proveniente de fgetc ().
Mas veja o que acontece quando usamos caracteres não assinados. Começamos com um caractere em fgetc () estendendo-o para int e, em seguida, queremos ter um caracter não assinado. O único problema é que não podemos preservar -1 em caracteres não assinados. O programa está armazenando-o como 255, que não é mais igual ao EOF.
Embargo
Se você der uma olhada na seção 3.1.2.5 Tipos de cópia da documentação ANSI C, descobrirá que se char é assinado ou não, depende apenas da implementação. Portanto, o cara provavelmente não deve ser demitido, pois encontrou um bug muito complicado à espreita no código. Pode surgir ao alterar o compilador ou mudar para uma arquitetura diferente. Gostaria de saber quem seria demitido se o bug fosse lançado nesse caso;)
PS. O programa foi criado com base no bug mencionado na linguagem Assembly de PC por Paul A. Carter