web-dev-qa-db-fra.com

Les tampons de garantie standard C ne sont-ils pas touchés au-delà de leur terminateur nul?

Dans les divers cas où un tampon est fourni aux nombreuses fonctions de chaîne de la bibliothèque standard, est-il garanti que le tampon ne sera pas modifié au-delà du terminateur null? Par exemple:

char buffer[17] = "abcdefghijklmnop";
sscanf("123", "%16s", buffer);

Est-ce que buffer doit maintenant être égal à "123\0efghijklmnop"?

Un autre exemple:

char buffer[10];
fgets(buffer, 10, fp);

Si la ligne de lecture ne contient que 3 caractères, peut-on être certain que le 6ème caractère est le même qu'avant l'appel de fgets?

43
Segmented

Chaque octet individuel dans la mémoire tampon est un objet. Sauf si une partie de la description de fonction de sscanf ou fgets mentionne la modification de ces octets, ou même implique que leurs valeurs peuvent changer p. Ex. en énonçant que leurs valeurs deviennent indéterminées, la règle générale s’applique: (souligné par moi)

6.2.4 Durée de stockage des objets

2 [...] Un objet existe, a une adresse constante, et conserve sa dernière valeur stockée pendant toute sa durée de vie . [...]

C'est ce même principe qui garantit que 

#include <stdio.h>
int a = 1;
int main() {
  printf ("%d\n", a);
  printf ("%d\n", a);
}

tente d'imprimer 1 deux fois. Même si a est global, printf peut accéder aux variables globales, et la description de printf ne mentionne pas not modifying a.

Ni la description de fgets ni celle de sscanf ne font mention de la modification des tampons au-delà des octets censés être écrits (sauf en cas d'erreur de lecture). Ces octets ne sont donc pas modifiés.

23
user743382

La norme C99 draft standard ne stipule pas explicitement ce qui devrait se passer dans ces cas, mais en prenant en compte de nombreuses variantes, vous pouvez montrer que cela doit fonctionner d’une certaine manière pour qu’il réponde aux spécifications dans tous les cas.

La norme dit:

% s - Correspond à une séquence de caractères n'appartenant pas à un espace blanc.252)

Si aucun modificateur de longueur n'est présent, l'argument correspondant doit être un pointeur Vers l'élément initial d'un tableau de caractères suffisamment grand pour accepter la séquence Et un caractère nul final, qui sera ajouté automatiquement. .

Voici deux exemples montrant que cela doit fonctionner comme vous le souhaitez pour respecter la norme.

Exemple A:

char buffer[4] = "abcd";
char buffer2[10];  // Note the this could be placed at what would be buffer+4
sscanf("123 4", "%s %s", buffer, buffer2);
// Result is buffer =  "123\0"
//           buffer2 = "4\0"

Exemple B:

char buffer[17] = "abcdefghijklmnop";
char* buffer2 = &buffer[4];
sscanf("123 4", "%s %s", buffer, buffer2);
// Result is buffer = "123\04\0"

Notez que l’interface de sscanf ne fournit pas assez d’informations pour vraiment savoir qu’elles étaient différentes. Donc, si l'exemple B doit fonctionner correctement, il ne doit pas perturber les octets après le caractère nul dans l'exemple A. Cela est dû au fait qu'il doit fonctionner dans les deux cas conformément à ce bit de spéc.

Donc implicitement cela doit fonctionner comme vous l'avez indiqué en raison de la spécification.

Des arguments similaires peuvent être placés pour d'autres fonctions, mais je pense que vous pouvez voir l'idée de cet exemple.

REMARQUE: Si vous définissez des limites de taille dans le format, telles que "% 16s", pourrait modifier le comportement. Selon la spécification, il serait fonctionnellement acceptable que sscanf mette à zéro un tampon à ses limites avant d'écrire les données dans le tampon. En pratique, la plupart des implémentations optent pour la performance, ce qui signifie qu'elles laissent le reste.

Lorsque le but de la spécification est de faire ce type de remise à zéro, cela est généralement spécifié explicitement. strncpy est un exemple. Si la longueur de la chaîne est inférieure à la longueur maximale de la mémoire tampon spécifiée, le reste de l'espace est rempli de caractères nuls. Le fait que cette même fonction "chaîne" puisse également renvoyer une chaîne non terminée en fait l'une des fonctions les plus courantes permettant aux utilisateurs de lancer leur propre version.

En ce qui concerne les objets, une situation similaire pourrait survenir. Le seul inconvénient, c'est que la spécification indique explicitement que si rien n'est lu, le tampon reste intact. Une implémentation fonctionnelle acceptable pourrait éviter cela en vérifiant s'il y avait au moins un octet à lire avant de mettre à zéro le tampon.

31
caveman

La norme est quelque peu ambiguë à ce sujet, mais je pense qu’une lecture raisonnable de celle-ci est que la réponse est: oui, il n’est pas permis d’écrire plus d’octets dans le tampon que de lire + null. Par ailleurs, une lecture/interprétation plus stricte du texte pourrait permettre de conclure que la réponse est non, il n'y a aucune garantie. Voici ce que un brouillon publiquement disponible dit à propos de fgets.

char *fgets(char * restrict s, int n, FILE * restrict stream);

La fonction fgets lit au plus un nombre de caractères inférieur au nombre de caractères spécifié par n dans le flux pointé par stream dans le tableau pointé par s. Aucun caractère supplémentaire n'est lu après un caractère de nouvelle ligne (qui est conservé) ou après la fin du fichier. Un caractère nul est écrit immédiatement après le dernier caractère lu dans le tableau.

La fonction fgets renvoie s en cas de succès. Si la fin du fichier est rencontrée et qu'aucun caractère n'a été lu dans le tableau, le contenu du tableau reste inchangé et un pointeur null est renvoyé. Si une erreur de lecture se produit pendant l'opération, le contenu du tableau est indéterminé et un pointeur null est renvoyé.

Il y a une garantie à propos de combien il est supposé read de l'entrée, c'est-à-dire arrêter la lecture à la nouvelle ligne ou EOF et ne pas lire plus que n-1 octets. Bien que rien ne soit dit explicitement à propos de combien il est permis de write dans le tampon, il est de notoriété publique que le paramètre fgets 's n est utilisé pour empêcher les débordements de tampon. Il est un peu étrange que la norme utilise le terme ambigu read , ce qui n’implique pas nécessairement que gets ne puisse pas écrire dans le tampon plus de n octets, si vous voulez cocher la case la terminologie qu'il utilise. Notez cependant que la même terminologie "en lecture" est utilisée pour les deux problèmes: la limite n- et la limite EOF/newline. Donc, si vous interprétez la "lecture" liée à n comme une limite d'écriture dans le tampon, vous pouvez/devez interpréter l'autre lecture de la même manière, c'est-à-dire qu'elle n'écrit pas plus que ce qu'elle lit lorsque chaîne est plus courte que le tampon. 

D'autre part, si vous faites la distinction entre les utilisations du verbe-phrase "read into" (= "écrire") et juste "read", vous ne pouvez pas lire le texte du comité de la même manière. Vous êtes assuré qu'il ne "lira pas" (= "écrire dans") le tableau de plus d'octets n, mais si la chaîne d'entrée est terminée plus tôt par newline ou EOF, vous ne pouvez garantir que reste (de l'entrée) ne sera pas "lu", mais si cela implique implicitement ne sera pas "lu dans" (= "écrit dans"), le tampon n'est pas clair avec cette lecture plus stricte. La question cruciale est que le mot clé est "into", ce qui est élidé. Le problème est donc de savoir si la complétion donnée par moi entre parenthèses dans la citation modifiée suivante correspond à l'interprétation voulue:

Aucun caractère supplémentaire n'est lu [dans le tableau] après un caractère de nouvelle ligne (qui est conservé) ou après la fin du fichier.

Franchement, un seul postcondition indiqué sous forme de formule (et serait assez court dans ce cas) aurait été beaucoup plus utile que le verbiage que j'ai cité ...

Je ne me dérange pas d'essayer d'analyser leurs écrits sur la famille *scanf, car je soupçonne que ce sera encore plus compliqué étant donné toutes les autres choses qui se passent dans ces fonctions; Leur écriture pour fscanf fait environ cinq pages ... Mais je suppose qu’une logique similaire s’applique.

8
Fizz

est-il garanti que le tampon ne sera pas modifié au-delà du terminateur nul ?

Non, il n'y a pas de garantie. 

La mémoire tampon doit-elle maintenant être égale à "123\0efghijklmnop"?

Oui. Mais c'est uniquement parce que vous avez utilisé les paramètres corrects pour vos fonctions liées aux chaînes. Si vous gâchez la longueur de la mémoire tampon, entrez les modificateurs dans sscanf et autres, alors votre programme sera compilé. Mais cela échouera probablement pendant l'exécution.

Si la ligne de lecture ne contient que 3 caractères, peut-on être certain que le 6ème caractère est le même qu'avant l'appel de fgets?

Oui. Une fois que fgets() figure, vous avez une chaîne de 3 caractères qui stocke l'entrée dans la mémoire tampon fournie, sans se soucier de la réinitialisation de l'espace fourni.

4
Igor S.K.

La mémoire tampon doit-elle maintenant être égale à "123\0efghijklmnop"?

Ici, buffer est simplement constitué de 123 chaîne garantie se terminant par NUL.

Oui, la mémoire allouée pour le tableau buffer ne sera pas dé-allouée. Cependant, vous vous assurez que/restreignez votre chaîne buffer ne peut au mieux contenir que des éléments 16 char que vous pouvez y lire à tout moment. Tout dépend maintenant si vous écrivez un seul caractère ou maximum ce que buffer peut prendre. 

Par exemple: 

char buffer[4096] = "abc";` 

fait réellement quelque chose ci-dessous,

memcpy(buffer, "abc", sizeof("abc"));
memset(&buffer[sizeof("abc")], 0, sizeof(buffer)-sizeof("abc"));

La norme insiste sur le fait que si une partie quelconque du tableau de caractères est initialisée, elle constitue tout ce dont elle est constituée à tout moment jusqu'à ce qu'elle obéisse à sa limite de mémoire.

1
Sunil Bojanapally

Il n’existe aucune garantie standard. C’est pourquoi il est recommandé d’utiliser les fonctions sscanf et fgets (en ce qui concerne la taille de la mémoire tampon) comme indiqué dans votre question (et l’utilisation de fgets est considérée préférable à gets). 

Cependant, certaines fonctions standard utilisent un terminateur nul dans leur travail, par exemple. strlen (mais je suppose que vous posez des questions sur la modification de chaîne)

MODIFIER:

Dans votre exemple

fgets(buffer, 10, fp);

les caractères intouchables après le 10ème sont garantis (le contenu et la longueur de buffer ne seront pas considérés par fgets)

EDIT2:

De plus, lorsque vous utilisez fgets, gardez à l’esprit que '\n' sera stocké dans les tampons. par exemple.

 "123\n\0fghijklmnop"

au lieu de prévu 

 "123\0efghijklmnop"
0
VolAnd

Dépend de la fonction utilisée (et dans une moindre mesure de sa mise en œuvre). sscanf commencera à écrire dès qu'il rencontrera son premier caractère non blanc, et continuera à écrire jusqu'à son premier caractère blanc, où il ajoutera un 0 de finition et lui reviendra. Mais une fonction comme strncpy (célèbre) met le reste de la mémoire tampon à zéro.

Cependant, rien dans la norme C n’indique comment ces fonctions se comportent.

0
Steve D