C
La lettera "x" è stata persa in un file. È stato scritto un programma per trovarlo:
#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");
}
}
È stato compilato e funzionante e finalmente ha gridato:
Hurray, letter lost in the file is finally found!
Per molti anni le lettere sono state salvate in questo modo fino a quando il nuovo ragazzo è arrivato e ha ottimizzato il codice. Conosceva i tipi di dati e sapeva che è meglio usare valori non firmati che firmati per valori non negativi poiché ha un intervallo più ampio e offre una certa protezione contro gli overflow. Quindi ha cambiato int in unsigned int . Conosceva anche ascii abbastanza bene da sapere che hanno sempre un valore non negativo. Quindi ha anche cambiato carattere in carattere non firmato . Compilò il codice e tornò a casa orgoglioso del buon lavoro che aveva fatto. Il programma sembrava così:
#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");
}
}
È tornato in rovina il giorno successivo. Mancava la lettera "a" e anche se doveva essere nel "file_sviluppo" contenente "abc", il programma lo cercava per sempre stampando solo:
Searching file for missing letter a...
Hanno licenziato il ragazzo e sono tornati alla versione precedente ricordando che non si dovrebbero mai ottimizzare i tipi di dati nel codice di lavoro.
Ma qual è la lezione che avrebbero dovuto imparare qui?
Prima di tutto, se dai un'occhiata alla tabella ASCII noterai che non esiste EOF. Questo perché EOF non è un carattere ma un valore speciale restituito da fgetc (), che può restituire il carattere esteso a int o -1 che indica la fine del file.
Finché stiamo usando il carattere con segno tutto funziona bene - il carattere uguale a 50 viene esteso da fgetc () in int uguale a 50. Quindi lo trasformiamo nuovamente in char e ne abbiamo ancora 50. Lo stesso accade per -1 o qualsiasi altro output proveniente da fgetc ().
Ma guarda cosa succede quando usiamo caratteri non firmati. Iniziamo con un carattere in fgetc () estenderlo a int e quindi vogliamo avere un carattere senza segno. L'unico problema è che non possiamo conservare -1 nel carattere senza segno. Il programma lo sta memorizzando come 255 che non è più uguale a EOF.
Avvertimento
Se dai un'occhiata alla sezione 3.1.2.5 Tipi in copia della documentazione ANSI C scoprirai che se il carattere è firmato o meno dipende esclusivamente dall'implementazione. Quindi probabilmente il ragazzo non dovrebbe essere licenziato poiché ha trovato un bug molto complicato in agguato nel codice. Potrebbe venire fuori quando si cambia il compilatore o si passa a un'architettura diversa. Mi chiedo chi verrebbe licenziato se il bug emergesse in tal caso;)
PS. Il programma è stato costruito attorno al bug menzionato in PC Assembly Language da Paul A. Carter