web-dev-qa-db-fra.com

Déterminer la taille du tampon sprintf - quelle est la norme?

Lors de la conversion d'un int comme ceci:

char a[256];
sprintf(a, "%d", 132);

quelle est la meilleure façon de déterminer la taille a devrait être? Je suppose que le réglage manuel est correct (comme je l'ai vu utilisé partout), mais quelle taille devrait-il être? Quelle est la plus grande valeur int possible sur un système 32 bits, et existe-t-il un moyen délicat de le déterminer à la volée?

42
Dominic Bou-Samra

Le nombre maximal possible de bits dans un int est CHAR_BIT * sizeof(int), et un chiffre décimal vaut "au moins 3 bits", donc une limite supérieure lâche sur l'espace requis pour un int arbitraire est (CHAR_BIT * sizeof(int) / 3) + 3. Ce +3 est un pour le fait que nous avons arrondi vers le bas lors de la division, un pour le signe, un pour le terminateur nul.

Si par "sur un système 32 bits" vous voulez dire que vous savez que int est 32 bits, alors vous avez besoin de 12 octets. 10 pour les chiffres, un pour le signe, un pour le terminateur nul.

Dans votre cas spécifique, où l'int à convertir est 132, vous avez besoin de 4 octets. Badum, tish.

Lorsque des tampons de taille fixe peuvent être utilisés avec une limite raisonnable, ils constituent l'option la plus simple. Je ne soumets pas si humblement que la limite ci-dessus est raisonnable (13 octets au lieu de 12 pour 32 bits int et 23 octets au lieu de 21 pour 64 bits int). Mais pour les cas difficiles, en C99, vous pouvez simplement appeler snprintf pour obtenir la taille, puis malloc autant. C'est exagéré pour un cas aussi simple que celui-ci.

13
Steve Jessop

Certains soutiennent ici que cette approche est exagérée, et pour convertir des ints en chaînes, je serais plus enclin à être d'accord. Mais quand une limite raisonnable pour la taille des chaînes ne peut être trouvée, j'ai vu cette approche utilisée et je l'ai utilisée moi-même.

int size = snprintf(NULL, 0, "%d", 132);
char * a = malloc(size + 1);
sprintf(a, "%d", 132);

Je décompose ce qui se passe ici.

  1. Sur la première ligne, nous voulons déterminer le nombre de caractères dont nous avons besoin. Les 2 premiers arguments de snprintf lui indiquent que je veux écrire 0 caractères du résultat dans NULL. Lorsque nous faisons cela, snprintf n'écrira aucun caractère nulle part, il renverra simplement le nombre de caractères qui auraient été écrits. Voilà ce que nous voulions.
  2. Sur la deuxième ligne, nous allouons dynamiquement de la mémoire à un pointeur char. Assurez-vous et ajoutez 1 à la taille requise (pour le caractère de fin \0 De fin).
  3. Maintenant qu'il y a suffisamment de mémoire allouée au pointeur char, nous pouvons utiliser en toute sécurité sprintf pour écrire l'entier dans le pointeur char.

Bien sûr, vous pouvez le rendre plus concis si vous le souhaitez.

char * a = malloc(snprintf(NULL, 0, "%d", 132) + 1);
sprintf(a, "%d", 132);

Sauf s'il s'agit d'un programme "rapide et sale", vous devez toujours vous assurer de libérer la mémoire que vous avez appelée avec malloc. C'est là que l'approche dynamique se complique avec C. Cependant, à mon humble avis, si vous ne voulez pas allouer d'énormes pointeurs char alors que la plupart du temps vous n'en utiliserez qu'une très petite partie, alors Je ne pense pas que ce soit une mauvaise approche.

77
Daniel Standage

Il est possible de faire fonctionner la solution de Daniel Standage pour n'importe quel nombre d'arguments en utilisant vsnprintf qui est en C++ 11/C99.

int bufferSize(const char* format, ...) {
    va_list args;
    va_start(args, format);
    int result = vsnprintf(NULL, 0, format, args);
    va_end(args);
    return result + 1; // safe byte for \0
}

Comme spécifié dans norme c99 , section 7.19.6.12:

La fonction vsnprintf renvoie le nombre de caractères qui auraient été écrits si n avait été suffisamment grand, sans compter le caractère nul final, ou une valeur négative si un
Une erreur de codage s'est produite.

14
Regis Portalez

Je vois que cette conversation a quelques années, mais je l'ai trouvée en essayant de trouver une réponse pour MS VC++ où snprintf ne peut pas être utilisé pour trouver la taille. Je posterai la réponse que j'ai finalement trouvée au cas où cela aiderait quelqu'un d'autre:

VC++ a la fonction _scprintf spécifiquement pour trouver le nombre de caractères nécessaires.

8
HopeItHelps

Si vous imprimez un entier simple et rien d'autre, il existe un moyen beaucoup plus simple de déterminer la taille du tampon de sortie. Au moins plus simple en termes de calcul, le code est un peu obtus:

char *text;
text = malloc(val ? (int)log10((double)abs(val)) + (val < 0) + 2 : 2);

log10 (valeur) renvoie le nombre de chiffres (moins un) requis pour stocker une valeur positive non nulle dans la base 10. Il va un peu hors de la Rails pour les nombres inférieurs à un, nous spécifions donc abs () et code une logique spéciale pour zéro (l'opérateur ternaire, testez? truecase: falsecase). Ajoutez-en un pour l'espace pour stocker le signe d'un nombre négatif (val <0), un pour compenser la différence par rapport à log10, et un autre pour le terminateur nul (pour un grand total de 2), et vous venez de calculer la quantité exacte d'espace de stockage requis pour un nombre donné, sans appeler snprintf () ou équivalents deux fois pour obtenir De plus, il utilise généralement moins de mémoire que INT_MAX n'en aura besoin.

Si vous avez besoin d'imprimer un grand nombre de nombres très rapidement, prenez la peine d'allouer le tampon INT_MAX, puis d'imprimer à plusieurs reprises à la place. Moins de battements de mémoire, c'est mieux.

Notez également que vous n'avez peut-être pas réellement besoin du (double) par opposition à un (float). Je n'ai pas pris la peine de vérifier. Le lancer dans les deux sens comme cela peut également être un problème. YMMV sur tout ça. Fonctionne très bien pour moi, cependant.

2
Pegasus Epsilon

C'est bien que vous vous inquiétiez de la taille du tampon. Pour appliquer cette pensée dans le code, j'utiliserais snprintf

snprintf( a, 256, "%d", 132 );

ou

snprintf( a, sizeof( a ), "%d", 132 );  // when a is array, not pointer
1
Arun

Tout d'abord, sprintf est le diable. Si quoi que ce soit, utilisez snprintf, sinon vous risquez de jeter la mémoire et de planter votre application.

Quant à la taille du tampon, c'est comme tous les autres tampons - aussi petit que possible, aussi grand que nécessaire. Dans votre cas, vous avez un entier signé, alors prenez la plus grande taille possible et n'hésitez pas à ajouter un peu de rembourrage de sécurité. Il n'y a pas de "taille standard".

Cela ne dépend pas non plus du système sur lequel vous exécutez. Si vous définissez le tampon sur la pile (comme dans votre exemple), cela dépend de la taille de la pile. Si vous avez créé le fil vous-même, vous avez déterminé la taille de la pile vous-même, de sorte que vous connaissez les limites. Si vous prévoyez une récursivité ou une trace de pile profonde, vous devez également faire très attention.

0
EboMike