120 votes

Comment convertir un tableau d'octets en une chaîne hexadécimale en C ?

Je l'ai fait :

uint8 buf[] = {0, 1, 10, 11};

Je veux convertir le tableau d'octets en une chaîne de caractères de manière à pouvoir imprimer la chaîne à l'aide de printf :

printf("%s\n", str);

et get (les deux points ne sont pas nécessaires) :

"00:01:0A:0B"

Toute aide serait grandement appréciée.

0 votes

buf[i] doivent être lancés vers unsigned char ou il débordera si buf[i] > 127 c'est-à-dire : buf_ptr += sprintf(buf_ptr, "%02X", (unsigned char)buf[i]);

121voto

Mark Synowiec Points 3069
printf("%02X:%02X:%02X:%02X", buf[0], buf[1], buf[2], buf[3]);

Pour une manière plus générique :

int i;
for (i = 0; i < x; i++)
{
    if (i > 0) printf(":");
    printf("%02X", buf[i]);
}
printf("\n");

Pour concaténer une chaîne de caractères, il y a plusieurs façons de procéder. Je garderais probablement un pointeur sur la fin de la chaîne et j'utiliserais sprintf. Vous devriez également garder une trace de la taille du tableau pour vous assurer qu'il ne dépasse pas l'espace alloué :

int i;
char* buf2 = stringbuf;
char* endofbuf = stringbuf + sizeof(stringbuf);
for (i = 0; i < x; i++)
{
    /* i use 5 here since we are going to add at most 
       3 chars, need a space for the end '\n' and need
       a null terminator */
    if (buf2 + 5 < endofbuf)
    {
        if (i > 0)
        {
            buf2 += sprintf(buf2, ":");
        }
        buf2 += sprintf(buf2, "%02X", buf[i]);
    }
}
buf2 += sprintf(buf2, "\n");

1 votes

Merci Mark - mon problème est un peu plus compliqué. J'ai en fait un tampon d'une longueur de X octets. J'espérais trouver un moyen générique de faire cela pour X octets et d'obtenir une chaîne de caractères comme résultat.

0 votes

Juste mis à jour pour ajouter du code pour gérer un nombre donné d'octets... en supposant que x est la longueur.

0 votes

Merci encore Mark, mais la chose que je trouvais la plus délicate pour ce problème était de savoir comment imprimer ceci dans une chaîne.

46voto

kriss Points 10450

Pour être complet, vous pouvez aussi facilement le faire sans appeler aucune fonction de bibliothèque lourde (pas de snprintf, pas de strcat, pas même de memcpy). Cela peut être utile, par exemple si vous programmez un microcontrôleur ou un noyau d'OS où la libc n'est pas disponible.

Rien de vraiment fantaisiste, vous pouvez trouver un code similaire si vous le recherchez sur Google. En réalité, ce n'est pas beaucoup plus compliqué que d'appeler snprintf et c'est beaucoup plus rapide.

#include <stdio.h>

int main(){
    unsigned char buf[] = {0, 1, 10, 11};
    /* target buffer should be large enough */
    char str[12];

    unsigned char * pin = buf;
    const char * hex = "0123456789ABCDEF";
    char * pout = str;
    int i = 0;
    for(; i < sizeof(buf)-1; ++i){
        *pout++ = hex[(*pin>>4)&0xF];
        *pout++ = hex[(*pin++)&0xF];
        *pout++ = ':';
    }
    *pout++ = hex[(*pin>>4)&0xF];
    *pout++ = hex[(*pin)&0xF];
    *pout = 0;

    printf("%s\n", str);
}

Voici une autre version légèrement plus courte. Elle évite simplement la variable d'index intermédiaire i et la duplication du code de la dernière casse (mais le caractère de fin est écrit deux fois).

#include <stdio.h>
int main(){
    unsigned char buf[] = {0, 1, 10, 11};
    /* target buffer should be large enough */
    char str[12];

    unsigned char * pin = buf;
    const char * hex = "0123456789ABCDEF";
    char * pout = str;
    for(; pin < buf+sizeof(buf); pout+=3, pin++){
        pout[0] = hex[(*pin>>4) & 0xF];
        pout[1] = hex[ *pin     & 0xF];
        pout[2] = ':';
    }
    pout[-1] = 0;

    printf("%s\n", str);
}

Voici une autre version pour répondre à un commentaire disant que j'ai utilisé un "truc" pour connaître la taille du tampon d'entrée. En fait, ce n'est pas une astuce mais une connaissance nécessaire de l'entrée (vous devez connaître la taille des données que vous convertissez). J'ai rendu cela plus clair en extrayant le code de conversion dans une fonction séparée. J'ai également ajouté un code de vérification des limites pour le tampon cible, qui n'est pas vraiment nécessaire si nous savons ce que nous faisons.

#include <stdio.h>

void tohex(unsigned char * in, size_t insz, char * out, size_t outsz)
{
    unsigned char * pin = in;
    const char * hex = "0123456789ABCDEF";
    char * pout = out;
    for(; pin < in+insz; pout +=3, pin++){
        pout[0] = hex[(*pin>>4) & 0xF];
        pout[1] = hex[ *pin     & 0xF];
        pout[2] = ':';
        if (pout + 3 - out > outsz){
            /* Better to truncate output string than overflow buffer */
            /* it would be still better to either return a status */
            /* or ensure the target buffer is large enough and it never happen */
            break;
        }
    }
    pout[-1] = 0;
}

int main(){
    enum {insz = 4, outsz = 3*insz};
    unsigned char buf[] = {0, 1, 10, 11};
    char str[outsz];
    tohex(buf, insz, str, outsz);
    printf("%s\n", str);
}

1 votes

Ce n'est pas une astuce, simplement une constante. Dans le contexte de la question, il est clair que la longueur de la source que nous voulons convertir en hexadécimal est bien connue (j'aurais pu mettre un 4 codé en dur au lieu de sizeof). Dans le cas général, la fonction doit être appelée sur une entrée de longueur connue et le tampon cible doit avoir 3 fois + 1 octets disponibles. Ceci doit être assuré par l'appelant, il n'y a aucune raison pour que la fonction de conversion effectue cette tâche. L'appel à strlen() peut être un moyen de trouver la taille de la source dans certains cas, mais pas toujours. Que faire si le nombre à convertir en hexadécimal contient des zéros ?

0 votes

Inspiré par votre fonction, j'ai écrit une version qui renvoie également le nombre d'octets écrits dans le tampon de sortie, similaire à snprintf, etc. gist.github.com/cellularmitosis/0d8c0abf7f8aa6a2dff3

0 votes

Je pense que vous devriez donner automatiquement la bonne taille à votre tampon de sortie en utilisant char str[ sizeof(buf)*3 + 1 ] ;

15voto

Yannuth Points 51

Voici une méthode qui est beaucoup plus rapide :

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

unsigned char *     bin_to_strhex(const unsigned char *bin, unsigned int binsz,
                                  unsigned char **result)
{
  unsigned char     hex_str[]= "0123456789abcdef";
  unsigned int      i;

  if (!(*result = (unsigned char *)malloc(binsz * 2 + 1)))
    return (NULL);

  (*result)[binsz * 2] = 0;

  if (!binsz)
    return (NULL);

  for (i = 0; i < binsz; i++)
    {
      (*result)[i * 2 + 0] = hex_str[(bin[i] >> 4) & 0x0F];
      (*result)[i * 2 + 1] = hex_str[(bin[i]     ) & 0x0F];
    }
  return (*result);
}

int                 main()
{
  //the calling
  unsigned char     buf[] = {0,1,10,11};
  unsigned char *   result;

  printf("result : %s\n", bin_to_strhex((unsigned char *)buf, sizeof(buf), &result));
  free(result);

  return 0
}

3 votes

Ce code contient un bug qui ne se manifeste que sur des entrées étranges non imprimables (je n'ai pas eu le temps de creuser exactement ce qui se passe mathématiquement). Essayez d'encoder le binaire de l'hexadécimal ca9e3c972f1c5db40c0b4a66ab5bc1a20ca4457bdbe5e0f8925896d5ed37‌​d726 et vous obtiendrez ÌaÌe3cÌ72f1c5dÌ40c0b4a66Ìb5bÌ1Ì20cÌ4457bÌbÌ5Ì0Ì8Ì258Ì6Ì5Ìd37‌​Ì726 dehors. Pour résoudre ce problème, la mèche à l'intérieur hex_str dans la première ligne de la boucle for doit être changé en (input[i] >> 4) & 0x0F comme dans la réponse de @kriss. Ensuite, cela fonctionne bien.

0 votes

Bogue - ne vérifie pas l'échec de malloc().

0 votes

Il est toujours préférable d'utiliser des chars non signés absolument partout car personne ne veut courir le risque des chars signés (une caractéristique matérielle folle du DEC PDP11), et de cette façon vous ne courez pas le risque que les comparaisons signées tournent mal ou que les décalages droits signés corrompent les valeurs. Dans ce cas, pour être juste, le code fait défensivement un & 0x0F partout, ce qui vous protège ici.

7voto

sdaau Points 6262

Je voulais juste ajouter ce qui suit, même si c'est un peu hors sujet (pas le C standard), mais je me retrouve souvent à le chercher, et je tombe sur cette question parmi les premiers résultats de recherche. La fonction d'impression du noyau Linux, printk Le format de sortie du contenu des tableaux/mémoires est également spécifié par un spécificateur de format singulier :

https://www.kernel.org/doc/Documentation/printk-formats.txt

Raw buffer as a hex string:
    %*ph    00 01 02  ...  3f
    %*phC   00:01:02: ... :3f
    %*phD   00-01-02- ... -3f
    %*phN   000102 ... 3f

    For printing a small buffers (up to 64 bytes long) as a hex string with
    certain separator. For the larger buffers consider to use
    print_hex_dump(). 

... cependant, ces spécificateurs de format ne semblent pas exister pour la norme, l'espace utilisateur (s)printf .

0voto

crazyscot Points 6675

Il n'y a pas de primitive pour cela en C. J'aurais probablement malloqué (ou peut-être alloué) un tampon assez long et bouclé sur l'entrée. J'ai aussi vu que cela se faisait avec une bibliothèque dynamique de chaînes de caractères avec une sémantique (mais pas de syntaxe !) similaire à celle du C++. ostringstream qui est une solution plausible plus générique, mais qui ne vaut peut-être pas la complexité supplémentaire pour un seul cas.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X