Thursday, February 27, 2020

fgets

J'ai apparemment laissé dans un commentaire de revue "fgets doesn't guarantee you have a terminating \0 on oversized lines". Il faut bien reconnaître que c'est le genre de farce auxquelles il faut s'attendre de la part de la bibliothèque C standard. Mais là, à y regarder une 2eme fois, j'avais tout faux.

fgets(ptr, size, stream)  reads in at most one less than size characters from stream and stores them into the buffer pointed to by ptr.  Reading stops after an EOF or a newline.  If a newline is read, it is stored into the buffer. A terminating null byte ('\0') is stored after the last character in the buffer.
C'est presque le mieux qu'on puisse espérer, non ? on lui passe un buffer alloué avec data[N] comme (data, N) et il se garde un dernier p'tit caractère pour la route le \0 de fin-de-chaîne. Que demander de mieux ? Non, en vérité, c'est de strncpy qu'il faut se méfier.

Mais de ma phase de déni/doute, je me suis quand-même refait un p'tit programme pour tirer ça au clair. Bin rien à redire. On a bien un \0 de terminaison à chaque coup (le programme ne lit que 16 caractères à la fois). Soit juste après le retour à la ligne (\n), soit en dernière position.

Seul cas où il peut être manquant: si il n'y avait rien à lire du tout. (auquel cas fgets nous renvoie un pointeur nul plutôt qu'un pointeur vers notre buffer, ce bon vieux data). Même une fin de flux sans fin de ligne ne le met pas en défaut.


Brave petit.

Toujours à vous demander ce que c'est que cette histoire de \0 ? vous pensiez qu'un ordinateur auquel on répond "Piet Burner" quand il vous demande "Entrez votre nom >" savait où se trouvait le début et la fin du nom en mémoire ? Bin pas franchement. Dans la majorité des cas, il a besoin d'un caractère spécial, en fin de chaîne, pour savoir qu'il a tout lu / copié / analysé / imprimé ... C'est le caractère de valeur 0 (pas le caractère ASCII en forme de 0 que vous rajoutez à la fin de votre fiche de paie) qui s'y colle. Enfin. Presque toujours. Il existe un obscur système d'exploitation dans lequel ce rôle était dédié au caractère '$'. Si, si...

Faites en sorte que le \0 soit manquant (en mangeant trop de pommes, en écrabouillant la tête de Yoshi ou en tapant le Konami Code pendant le chargement du jeu), et le CPU continuera "sagement" à manipuler "des choses" comme si c'était votre nom ... avec potentiellement la possibilité d'aller écraser l'emplacement de votre personnage sur la map, des bouts d'inventaires au le nom du prochain pokemon que vous allez rencontrer :P

Oh, et tant qu'on y est, j'utilise aussi régulièrement snprintf, bien que codant en C++. Elle a le bon goût de ne pas écrire plus de size caractères, terminateur \0 compris et renvoie la longueur de chaîne qui aurait été écrite si on avait eu la place. Longueur qui s'entend au sens strlen (sans compter le terminateur) et donc si on me répond que 16 quand j'ai passé un tableau de 16 bytes pour stocker le résultat, c'est déjà un sacrifice de données (bien vu, jigé ;)

1 comment:

PypeBros said...

c'est de strncpy qu'il faut se méfier
...

Bin oui, parce qu'apparemment, strncpy n'a jamais été conçu comme un 'strcpy avec une destination limitée', mais bien comme une 'copie d'une chaine de taille fixe N'