Question

Remarque: j'ai complètement retravaillé la question pour mieux refléter ce pour quoi je fixe la prime. Veuillez excuser les incohérences avec les réponses déjà données que cela aurait pu créer. Je ne voulais pas créer une nouvelle question, car les réponses précédentes pourraient être utiles.

Je travaille sur l'implémentation d'une bibliothèque standard C et je suis confus au sujet d'un coin spécifique de la norme.

La norme définit les formats numériques acceptés par la famille de fonctions scanf (% d,% i,% u,% o,% x) en termes de définitions pour strtol , strtoul et strtod .

La norme indique également que fscanf () ne restituera qu'un maximum de caractères dans le flux d'entrée et que, par conséquent, certaines séquences acceptées par strtol , strtoul et strtod sont inacceptables pour fscanf (ISO / CEI 9899: 1999, note de bas de page 251).

J'ai essayé de trouver des valeurs présentant de telles différences. Il se trouve que le préfixe hexadécimal "0x", suivi d'un caractère qui n'est pas un chiffre hexadécimal, est un cas dans lequel les deux familles de fonctions diffèrent.

Assez drôle, il est devenu évident qu’il n’ya pas deux bibliothèques C disponibles qui ne semblent s’accorder sur le résultat. (Voir programme de test et exemple de sortie à la fin de cette question.)

Ce que j'aimerais entendre, c'est ce qui serait considéré comme un comportement conforme à la norme lors de l'analyse de "0xz"? . En citant idéalement les parties pertinentes de la norme pour faire valoir votre point de vue.

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

int main()
{
    int i, count, rc;
    unsigned u;
    char * endptr = NULL;
    char culprit[] = "0xz";

    /* File I/O to assert fscanf == sscanf */
    FILE * fh = fopen( "testfile", "w+" );
    fprintf( fh, "%s", culprit );
    rewind( fh );

    /* fscanf base 16 */
    u = -1; count = -1;
    rc = fscanf( fh, "%x%n", &u, &count );
    printf( "fscanf:  Returned %d, result %2d, consumed %d\n", rc, u, count );
    rewind( fh );

    /* strtoul base 16 */
    u = strtoul( culprit, &endptr, 16 );
    printf( "strtoul:             result %2d, consumed %d\n", u, endptr - culprit );

    puts( "" );

    /* fscanf base 0 */
    i = -1; count = -1;
    rc = fscanf( fh, "%i%n", &i, &count );
    printf( "fscanf:  Returned %d, result %2d, consumed %d\n", rc, i, count );
    rewind( fh );

    /* strtol base 0 */
    i = strtol( culprit, &endptr, 0 );
    printf( "strtoul:             result %2d, consumed %d\n", i, endptr - culprit );

    fclose( fh );
    return 0;
}

/* newlib 1.14

fscanf:  Returned 1, result  0, consumed 1
strtoul:             result  0, consumed 0

fscanf:  Returned 1, result  0, consumed 1
strtoul:             result  0, consumed 0
*/

/* glibc-2.8

fscanf:  Returned 1, result  0, consumed 2
strtoul:             result  0, consumed 1

fscanf:  Returned 1, result  0, consumed 2
strtoul:             result  0, consumed 1
*/

/* Microsoft MSVC

fscanf:  Returned 0, result -1, consumed -1
strtoul:             result  0, consumed 0

fscanf:  Returned 0, result  0, consumed -1
strtoul:             result  0, consumed 0
*/

/* IBM AIX

fscanf:  Returned 0, result -1, consumed -1
strtoul:             result  0, consumed 1

fscanf:  Returned 0, result  0, consumed -1
strtoul:             result  0, consumed 1
*/
Était-ce utile?

La solution

Communication avec Fred J. Tydeman, vice-président de PL22.11 (ANSI "C"), sur comp.std.c, a permis de mieux comprendre:

fscanf

  

Un élément d’entrée est défini comme étant le   plus longue séquence de caractères d'entrée   [...] qui est, ou est un préfixe, un   séquence d'entrée correspondante. (7.19.6.2 P9)

Cela fait " 0x " la plus longue séquence qui est un préfixe d'une séquence d'entrée correspondante. (Même avec la conversion % i , car l'hex "0x" est une séquence plus longue que la décimale "0".)

  

Le premier caractère, le cas échéant, après le   l'élément d'entrée reste non lu. (7.19.6.2 P9)

Ceci permet à fscanf de lire le "z", et de le replacer comme non concordant (en respectant la limite de repoussement à un caractère de la note de bas de page 251)).

  

Si l'élément en entrée ne correspond pas   séquence, l'exécution du   directive échoue: cette condition est un   échec correspondant. (7.19.6.2 P10)

Cela fait " 0x " échec de la correspondance, c'est-à-dire que fscanf ne doit attribuer aucune valeur, renvoyer zéro (si le % x ou le % i était le premier spécificateur de conv.), et laissez " z " en tant que premier caractère non lu du flux d'entrée.

strtol

La définition de strtol (et strtoul ) diffère en un point crucial:

  

La séquence de sujet est définie comme étant la   la plus longue sous-séquence initiale du   chaîne d'entrée, en commençant par la première   caractère non-blanc, qui est de   la forme attendue . (7.20.1.4 P4, c'est moi qui souligne)

Ce qui signifie que strtol doit rechercher la séquence la plus longue valide , dans ce cas le "0". Il doit pointer endptr sur "x" et renvoyer zéro comme résultat.

Autres conseils

Je ne crois pas que l'analyse soit autorisée à produire des résultats différents. La référence Plaugher indique simplement que l'implémentation strtol () pourrait être une version différente, plus efficace, car elle dispose d'un accès complet à la chaîne entière.

Selon la spécification C99, la famille de fonctions scanf () analyse les entiers de la même manière que la famille de fonctions strto * () . Par exemple, pour le spécificateur de conversion x , ceci lit:

  

Correspond à un éventuellement signé   nombre entier hexadécimal, dont le format est   le même que prévu pour le sujet   séquence de la fonction strtoul avec   la valeur 16 pour l'argument base .

Donc si sscanf () et strtoul () donnent des résultats différents, l'implémentation de libc n'est pas conforme.

Quels sont les résultats attendus de votre exemple le code devrait être un peu flou, cependant:

strtoul () accepte un préfixe facultatif 0x ou 0X si base est 16 , et la spécification lit

  

La séquence de sujet est définie comme étant la   la plus longue sous-séquence initiale du   chaîne d'entrée, en commençant par la première   caractère non-blanc, c'est-à-dire   la forme attendue.

Pour la chaîne "0xz" , la sous-séquence initiale la plus longue de la forme attendue est "0" ; la valeur doit donc être 0 et l'argument endptr doivent être définis sur x .

mingw-gcc 4.4.0 n'est pas d'accord et ne parvient pas à analyser la chaîne avec strtoul () et sscanf () . Le raisonnement pourrait être que la sous-séquence initiale la plus longue de la forme attendue est "0x" - ce qui n'est pas un littéral entier valide. Aucun analyse n'est donc effectuée.

Je pense que cette interprétation de la norme est fausse: une sous-séquence de la forme attendue devrait toujours donner une valeur entière valide (si elle est hors limites, les valeurs MIN / MAX sont renvoyés et errno est défini sur ERANGE ).

cygwin-gcc 3.4.4 (qui utilise newlib autant que je sache) n'analysera pas non plus le littéral si strtoul () est utilisé, mais analyse la chaîne en fonction de mon interprétation de la norme avec sscanf () .

Attention, mon interprétation de la norme est sujette à votre problème initial, c’est-à-dire que la norme ne garantit que de pouvoir ungetc () une fois. Pour décider si le 0x fait partie du littéral, vous devez lire deux caractères à l'avance: le x et le caractère suivant. Si ce n'est pas un caractère hexagonal, ils doivent être repoussés. S'il y a plus de jetons à analyser, vous pouvez les tamponner et contourner ce problème, mais s'il s'agit du dernier jeton, vous devez ungetc () les deux caractères.

Je ne suis pas vraiment sûr de ce que fscanf () devrait faire si ungetc () échouait. Peut-être juste définir l'indicateur d'erreur du flux?

Pour résumer ce qui devrait se produire selon la norme lors de l'analyse des nombres:

  • si fscanf () réussit, le résultat doit être identique à celui obtenu via strto * ()
  • contrairement à strto * () , fscanf () échoue si

      

    la plus longue séquence de caractères d'entrée [...] qui est ou est le préfixe d'une séquence d'entrée correspondante

    selon la définition de fscanf () n'est pas

      

    la sous-séquence initiale la plus longue [...] de la forme attendue

    selon la définition de strto * ()

C’est un peu moche, mais une conséquence nécessaire de l’exigence selon laquelle fscanf () doit être gourmand, mais ne peut pas repousser plus d’un caractère.

Certains implémenteurs de bibliothèques ont opté pour un comportement différent. A mon avis

  • laisser strto * () ne pas rendre les résultats cohérents est stupide ( bad mingw )
  • repoussant plusieurs caractères pour que fscanf () accepte toutes les valeurs acceptées par strto * () , mais est justifié ( hourra pour newlib s'ils n'ont pas botter strto * () : ()
  • ne pas repousser les caractères qui ne correspondent pas, mais seulement analyser ceux de "forme attendue" semble douteux alors que les caractères disparaissent dans les airs ( bad glibc )

Je ne suis pas sûr de comprendre la question, mais pour une chose, scanf () est supposé gérer EOF. scanf () et strtol () sont différents types de bêtes. Peut-être devriez-vous comparer strtol () et sscanf () à la place?

Je ne suis pas sûr que la mise en œuvre de scanf () puisse être liée à ungetc (). scanf () peut utiliser tous les octets du tampon de flux. ungetc () pousse simplement un octet à la fin du tampon et le décalage est également modifié.

scanf("%d", &x);
ungetc('9', stdin);
scanf("%d", &y);
printf("%d, %d\n", x, y);

Si l'entrée est "100", la sortie est "100, 9". Je ne vois pas comment scanf () et ungetc () pourraient interférer. Désolé si j'ai ajouté un commentaire naïf.

Pour la saisie dans les fonctions scanf () ainsi que pour les fonctions strtol () , en Sec. 7.20.1.4 P7 indique: Si la séquence de sujet est vide ou ne présente pas la forme attendue, aucune conversion n'est effectuée. la valeur de nptr est stockée dans l'objet pointé par endptr, à condition que endptr ne soit pas un pointeur nul . Vous devez également tenir compte des règles d'analyse des jetons définis dans les règles de Sec. 6.4.4 Constantes , règle indiquée en Sec. 7.20.1.4 P5 .

Le reste du comportement, tel que la valeur errno , doit être spécifique à la mise en oeuvre. Par exemple, sur ma machine FreeBSD, les valeurs EINVAL et ERANGE sont utilisées. Sous Linux, il en va de même, où les référents standard se réfèrent uniquement à la ERANGE . .

Réponse obsolète après la réécriture de la question. Quelques liens intéressants dans les commentaires cependant.

  

En cas de doute, écrivez un test. - proverbe

Après avoir testé toutes les combinaisons de spécificateurs de conversion et de variations d'entrée auxquelles je pouvais penser, je peux dire qu'il est correct que les deux familles de fonctions ne donnent pas des résultats identiques . (Au moins dans la glibc, qui est ce que j'ai disponible pour les tests.)

La différence apparaît lorsque trois circonstances se rencontrent:

  1. Vous utilisez "% i" ou "% x" (en autorisant la saisie hexadécimale).
  2. L'entrée contient le préfixe hexadécimal "0x" (facultatif).
  3. Il n'y a pas de chiffre hexadécimal valide après le préfixe hexadécimal.

Exemple de code:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    char * string = "0xz";
    unsigned u;
    int count;
    char c;
    char * endptr;

    sscanf( string, "%x%n%c", &i, &count, &c );
    printf( "Value: %d - Consumed: %d - Next char: %c - (sscanf())\n", u, count, c );
    i = strtoul( string, &endptr, 16 );
    printf( "Value: %d - Consumed: %td - Next char: %c - (strtoul())\n", u, ( endptr - string ), *endptr );
    return 0;
}

Sortie:

Value: 0 - Consumed: 1 - Next char: x - (sscanf())
Value: 0 - Consumed: 0 - Next char: 0 - (strtoul())

Cela me trouble. Évidemment, sscanf () ne sera pas sauvegardé au 'x' , sinon il ne serait pas en mesure d'analyser aucun " Hexadécimaux préfixés par 0x "". Donc, il a lu le 'z' et l'a trouvé non correspondant. Mais il décide d’utiliser uniquement le <0> en tant que valeur. Cela signifierait repousser le 'z' et le 'x' . (Oui, je sais que sscanf () , que j'ai utilisé ici pour faciliter les tests, ne fonctionne pas sur un flux, mais je suppose fortement qu'ils ont créé tous les ... scanf () les fonctions se comportent de manière identique par souci de cohérence.)

Donc ... un caractère ungetc () n'est pas vraiment la raison, ici ...?: - /

Oui, les résultats diffèrent . Je ne peux toujours pas l'expliquer correctement, cependant ...: - (

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top