9
votes

Quelle est la bonne façon de mettre en œuvre une bonne "iTOA ()"?

Je me demandais si ma mise en œuvre d'une fonction "ITOA" est correcte. Peut-être que vous pouvez m'aider à me procurer un peu plus "correct", je suis à peu près sûr que je manque quelque chose. (Peut-être qu'il y a déjà une bibliothèque qui fait la conversion comme je le veux, mais ... Impossible de trouver)

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

char * itoa(int i) {
  char * res = malloc(8*sizeof(int));
  sprintf(res, "%d", i);
  return res;
}

int main(int argc, char *argv[]) {
 ...


3 commentaires

Fais attention. Il n'y a rien à indiquer que l'utilisateur doit supprimer le pointeur de retour après.


Connexes: Stackoverflow.com/q/4351371/103167


Vous n'avez pas mis en œuvre itoa . Vous venez de transférer son travail à la fonction de bibliothèque sprintf qui fait le travail pour vous.


12 Réponses :


3
votes

Je pense que vous allouez peut-être trop de mémoire. MALLOC (8 * TAILLEOF (INT)) Vous donnera 32 octets sur la plupart des machines, ce qui est probablement excessif pour une représentation de texte d'un INT.


9 commentaires

Tu as raison. Depuis 2 ^ 15 - 1, c'est en fait 32767, j'ai juste besoin de 6 * Taillef.


@Nicolas: Et un de plus pour le signe, si vous envisagez des nombres négatifs.


Merci ! (Ne peut pas encore +1, je ne suis pas encore inscrit) mais j'aurais!


Tailleof (int) * 3 + 2 doit toujours être suffisant (chaque octet contribue à 3 chiffres ou moins, et les 2 supplémentaires sont pour la résiliation du signe et de la null.


@R.: Pas sur les architectures où char_bit est supérieur à 9. par exemple. Certains DSP ont char_bit == 32 et Tailleof (int) == 1 .


@CAF: hein. Comment ça va pour quelque chose. Une chose est sûre, alors aide à défier mes idées préconçues. Merci!


Si vous souhaitez en tenir compte de cela, vous pouvez plutôt utiliser ((TYPE) - 1) / 3 + 2) pour les types signés ou (((((((((((((caractères) Type))) / 3 + 1) pour les types non signés.


@CAF: Nice! Merci pour l'information supplémentaire! Être un gars .NET, je suis impressionné à quel point leur est d'apprendre lorsque vous plongez sous la touche .NET / Java / Python / etc abstraction.


C'est exactement pourquoi j'ai posé cette question; Même avec une question aussi simple que c'était, je ne pouvais pas penser à tous les cas, et mon style de programmation n'est pas encore brillant.



-1
votes
main()
{
  int i=1234;
  char stmp[10];
#if _MSC_VER
  puts(_itoa(i,stmp,10));
#else
  puts((sprintf(stmp,"%d",i),stmp));
#endif
  return 0;
}

0 commentaires

0
votes

Il y a quelques suggestions que je pourrais faire. Vous pouvez utiliser un tampon statique et STRUP pour éviter de répartir à plusieurs reprises une trop de mémoire sur les appels ultérieurs. J'ajouterais également une vérification des erreurs.

char *itoa(int i)
{
  static char buffer[12];

  if (snprintf(buffer, sizeof(buffer), "%d", i) < 0)
    return NULL;

  return strdup(buffer);
}


0 commentaires

2
votes

Je ne suis pas tout à fait sûr où vous obtenez 8 * Tailleof (int) comme nombre maximal possible de caractères - CEIL (8 / (log (10) / log (2 )))) donne un multiplicateur de 3 * . De plus, sous C99 et certaines plateformes de Posix plus anciennes, vous pouvez créer une version à l'allocation avec précision avec sprintf () : xxx

hth


2 commentaires

Deux appels à Snprintf semblent une mauvaise pratique.


@EvilClown: Eh bien, lorsque la chaîne de format est juste % d Ce n'est pas vraiment nécessaire, car vous pouvez déterminer l'espace maximum requis à la compilation. Pour un Int de 32 bits, il est 12, de sorte que vous ne vous inquiétez pas trop trop sur la suralimentation. Ce modèle général d'appel snapintf deux fois est absolument des choses standard, cependant.



0
votes

Ceci devrait fonctionner:

#include <string.h>
#include <stdlib.h>
#include <math.h>

char * itoa_alloc(int x) {
   int s = x<=0 ? 1 ? 0; // either space for a - or for a 0
   size_t len = (size_t) ceil( log10( abs(x) ) );
   char * str = malloc(len+s + 1);

   sprintf(str, "%i", x);

   return str;
}


3 commentaires

On dirait que beaucoup de travail pour calculer la bonne taille lorsque le maximum est un 12 octets connu. CEIL, log10, ABS, deux variables temporaires (total de 8 octets) et une opération ternaire.


@EvilClown: Ouais, mais le processus général fonctionne pour tout entier de taille.


Échoue avec int_min .



1
votes

Vous devez utiliser une fonction dans la famille printf à cette fin. Si vous écrivez le résultat sur stdout ou un fichier, utilisez printf / fprintf . Sinon, utilisez snprintf avec un tampon assez grand pour maintenir 3 * Tailleof (type) +2 octets ou plus.


0 commentaires

7
votes

La seule erreur réelle est que vous ne vérifiez pas la valeur de retour de MALLOC code> pour null.

Le nom ITOA code> est un peu déjà pris pour une fonction C'est non standard, mais pas rare. Il n'alloue pas de mémoire, mais il écrit à un tampon fourni par l'appelant: p> xxx pré>

si vous ne voulez pas compter sur votre plate-forme, je voudrais toujours conseiller après le motif. Les fonctions de manutention à cordes qui renvoient la mémoire nouvellement attribuée en C sont généralement plus de problèmes que ce qu'ils valent à long terme, car la plupart du temps, vous finissez par faire de nouvelles manipulations, vous devez donc vous libérer de nombreux résultats intermédiaires. Par exemple, comparez: p> xxx pré>

vs. P> xxx pré>

Si vous aviez la raison d'être particulièrement préoccupée par la performance (par exemple Si vous implémentez une bibliothèque de style STDLIB, y compris itoa code>), ou si vous activez des bases que sprintf code> ne prend pas en charge, vous pourriez envisager de ne pas appeler sprintf code>. Mais si vous voulez une chaîne de base 10, votre premier instinct avait raison. Il n'y a absolument rien "incorrect" sur le spécificateur de format p>

Voici une implémentation possible de itoa code>, pour la base 10 uniquement: p>

int itobase10n(char *buf, size_t sz, int value) {
    return snprintf(buf, sz, "%d", value);
}


8 commentaires

C'est idiot, s'il ne retourne pas la mémoire allouée, alors pourquoi ne pas simplement appeler Snprintf directement. SNPRINTF (EndPTR, octets, "% d", numéro) - Cela empêcherait une déclaration de variable temporaire et ne dispose pas de ces possibilités de dépassement de tampon dans ce code d'échantillon.


@EvilClown: la raison de ne pas appeler snaprintf directement est la même raison que quiconque écrit une fonction: supporter l'opération spécifiquement de convertir un entier à une chaîne, par opposition à l'une des nombreuses choses qui SNPRINTF peut faire avec des formats de chaîne différents. Il n'y a qu'une possibilité de dépassement de tampon dans ce code sur les plates-formes où int est d'au moins 48 bits, et je pense que ce problème a été couvert bien ailleurs. N'hésitez pas à fusionner un de ceux-ci dans cette réponse :-)


Quoi qu'il en soit, Snprintf est souvent sur-évalué. Si vous êtes inquiet des débordements de tampon et que votre solution consiste à utiliser SnPrintf, alors pourquoi ne vous inquiétez pas de tronquer la chaîne de résultat et de faire la mauvaise réponse? Un programme "sécurisé" qui ne fonctionne pas réellement est bien sûr meilleur qu'un programme non sécurisé qui ne fonctionne pas, mais toujours pas génial ;-p


@Steve, c'est pourquoi Snprintf retourne un négatif lorsqu'il y a une troncature et que vous vérifiez des erreurs. Le meilleur des deux mondes!


Standard snprintf ne revient pas négatif sur la troncature - c'est un pré-C99-ism par ex. Microsoft _snprintf et très anciennes versions glibc. Il renvoie le nombre d'octets qui auraient été écrits, si la taille a été suffisamment grande. Vérification de la déclaration négative signifie que vous manquerez de la troncature sur les implémentations standard. SNPRINTF est bon pour la "mesure, allouer, écrire", mais cela n'aide pas beaucoup avec des tampons courts imo. Et ceci est même avant de vous inquiéter de savoir si la longueur que vous avez adoptée est réellement correcte. La manipulation de la chaîne sûre en C est difficile.


Je n'ai pas réalisé que ce n'était pas standard, merci pour l'information. Je préférerais toujours voir Snprintf qu'une fonction qui envoie simplement Snprintf. Je comprends "abstraction" mais je ne le ferais pas dans ce cas. Recommanderiez-vous strcpy_strings_that_start_with_a au lieu de Strcpy ?


@EvilClown: Bien sûr que non, car les deux auraient exactement les mêmes paramètres et la mise en œuvre. Voulez-vous vous recommander d'éviter les bibliothèques HTTP, en faveur de la manipulation des prises directement? Nous pouvons jouer avec des extrêmes trompeurs indéfiniment - dans ce cas, le wrapper "supprime" un paramètre afin de créer un "inverse" à la fonction standard atoi , donc il ne fait pas beaucoup Comparé à l'utilisation directe de SNPRINTF, mais cela fait plus que rien. L'existence de atoi fait itoa une abstraction particulièrement naturelle.


Je suppose, finalement, je pense que cela vaut toujours la peine d'écrire quelles que soient les fonctions correspondent aux abstractions que l'appelant veut (dans la mesure du possible et la performance permettant), même si elles se retrouvent très similaires aux fonctions existantes. Si je me suis retrouvé souvent vouloir copier des chaînes qui commencent nécessairement avec a , je suppose peut-être que j'écrirais une fonction que affirme le premier caractère puis appelle strcpy < / code>. Sur une version de sortie, cela deviendrait un wrapper sans rien.



2
votes

J'ai trouvé une ressource intéressante traitant de plusieurs problèmes différents avec la mise en œuvre de l'ITOA
Vous voudrez peut-être le chercher trop
ITOA () implémentations avec des tests de performance


2 commentaires

Char * La version de mise en œuvre de 0,4 dans ce document est la meilleure mise en œuvre des performances.


@ADAM Le lien est cassé.



11
votes
// Yet, another good itoa implementation
// returns: the length of the number string
int itoa(int value, char *sp, int radix)
{
    char tmp[16];// be careful with the length of the buffer
    char *tp = tmp;
    int i;
    unsigned v;

    int sign = (radix == 10 && value < 0);    
    if (sign)
        v = -value;
    else
        v = (unsigned)value;

    while (v || tp == tmp)
    {
        i = v % radix;
        v /= radix;
        if (i < 10)
          *tp++ = i+'0';
        else
          *tp++ = i + 'a' - 10;
    }

    int len = tp - tmp;

    if (sign) 
    {
        *sp++ = '-';
        len++;
    }

    while (tp > tmp)
        *sp++ = *--tp;

    return len;
}

// Usage Example:
char int_str[15]; // be careful with the length of the buffer
int n = 56789;
int len = itoa(n,int_str,10);

4 commentaires

Il manque '\ 0', soyez prudent lorsque vous l'utilisez


@Thomas, je viens de modifier la fonction pour renvoyer la longueur de la chaîne créée.


Votre réclamation sur v / = radix étant plus rapide que v = v / radix n'est pas vrai. Tout compilateur doit produire le même code pour les deux formulaires. Je l'ai testé dans GCC avec des optimisations désactivées.


Ajout * sp = '\ 0'; avant retour len; de sorte que sp sera une chaîne ASCII corrigée correctement terminée que vous pouvez utiliser dans des fonctions telles que strcpy .



3
votes

Un bon int code> à string em> ou itoa () code> a ces propriétés;

  • fonctionne pour tous [int_min ... int_max] code>, base [2 ... 36] code> sans débordement de tampon. LI>
  • n'assume pas int code>. li>
  • ne nécessite pas de complément de 2. li>
  • ne nécessite pas non signé code> pour avoir une plus grande plage positive que int code>. En d'autres termes, n'utilise pas non signé code>. li>
  • permet l'utilisation de '-' code> pour des nombres négatifs, même lorsque base! = 10 code>. li>. ul>

    adapter le traitement des erreurs au besoin. (besoin C99 ou plus tard): P>

    char* itostr(char *dest, size_t size, int a, int base) {
      // Max text needs occur with itostr(dest, size, INT_MIN, 2)
      char buffer[sizeof a * CHAR_BIT + 1 + 1]; 
      static const char digits[36] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    
      if (base < 2 || base > 36) {
        fprintf(stderr, "Invalid base");
        return NULL;
      }
    
      // Start filling from the end
      char* p = &buffer[sizeof buffer - 1];
      *p = '\0';
    
      // Work with negative `int`
      int an = a < 0 ? a : -a;  
    
      do {
        *(--p) = digits[-(an % base)];
        an /= base;
      } while (an);
    
      if (a < 0) {
        *(--p) = '-';
      }
    
      size_t size_used = &buffer[sizeof(buffer)] - p;
      if (size_used > size) {
        fprintf(stderr, "Scant buffer %zu > %zu", size_used , size);
        return NULL;
      }
      return memcpy(dest, p, size_used);
    }
    


0 commentaires

1
votes

sprintf est assez lent, si la performance compte, ce n'est probablement pas la meilleure solution.

Si l'argument de base est une puissance de 2 la conversion peut être effectuée avec un décalage et un masquage, et on peut éviter d'inverser la chaîne par enregistrer les chiffres des positions les plus élevées. Par exemple, quelque chose comme ceci pour la base = 16 p> xxx pré>

Const chiffre chiffre [] = {'0', '1', '2', '3', '4', '4' ',' 5 ',' 6 ',' 7 ',' 8 ',' 9 ',' 9 ',' A ',' B ',' B ',' C ',' E ',' E ',' F '}; P >

/* skip zeros in the highest positions */
int i = num_iter;
for (; i >= 0; i--)
{
    int digit = (value >> (bits_per_digit*i)) & 15;
    if ( digit > 0 )  break;
}

for (; i >= 0; i--)
{
    int digit = (value >> (bits_per_digit*i)) & 15;
    result[len++] = digits[digit];
}


0 commentaires

1
votes
  • INTEGER-TO-ASCII doit convertir les données d'un type d'entier standard dans une chaîne ASCII. LI>
  • Toutes les opérations doivent être effectuées à l'aide de l'indexation arithmétique du pointeur, pas d'une indexation de tableau. LI>
  • Le nombre que vous souhaitez convertir est transmis comme un entier 32 bits signé. li>
  • Vous devriez pouvoir prendre en charge les bases 2 à 16 en spécifiant la valeur entière de la base que vous souhaitez convertir en (base). li>
  • Copiez la chaîne de caractères convertie sur le pointeur UINT8_T * transmis sous forme de paramètre (PTR). LI>
  • Le numéro 32 bits signé aura une taille de chaîne maximale (indice: Think Base 2). Li>
  • Vous devez placer un terminateur null à la fin de la fonction C-String convertie doit renvoyer la longueur des données converties (y compris un signe négatif). li>
  • Exemple My_itoa (PTR, 1234, 10) doit renvoyer une longueur de chaîne ASCII de 5 (y compris le terminateur NULL). li>
  • Cette fonction doit gérer les données signées. LI>
  • Vous ne pouvez pas utiliser de fonctions de chaîne ou de bibliothèques. LI>

    . P>

    int32_t my_atoi(uint8_t *ptr, uint8_t digits, uint32_t base){
        int32_t sgnd=0, rslt=0;
        for(int i=0; i<digits; i++){
            if(*(ptr)=='-'){*ptr='0';sgnd=1;}
            else if(*(ptr+i)>'9'){rslt+=(*(ptr+i)-'7');}
            else{rslt+=(*(ptr+i)-'0');}
            if(!*(ptr+i+1)){break;}
            rslt*=base;
        }
        if(sgnd){rslt=-rslt;}
        return rslt;
    }
    
    • ASCII-TO-INTEGER doit convertir les données d'une chaîne représentée par ASCII en un type entier. LI>
    • Toutes les opérations doivent être effectuées à l'aide de pointeur arithmétique, pas d'indexation de tableau li>
    • La chaîne de caractères à convertir est transmise sous la forme d'un pointeur UINT8_T * (PTR). LI>
    • Le nombre de chiffres de votre jeu de caractères est transmis en tant qu'integer UINT8_T (chiffres). LI>
    • Vous devriez être capable de prendre en charge les bases 2 à 16. li>
    • L'entier converti 32 bits signé doit être retourné. LI>
    • Cette fonction doit gérer les données signées. LI>
    • Vous ne pouvez pas utiliser de fonctions de chaîne ou de bibliothèques. LI> ul>

      . p> xxx pré> ul>


2 commentaires

Assez lent je ferais cela avec une récursion


D'accord, vous pouvez trouver ici une analyse plus détaillée: strudel.org.uk/itoa