web-dev-qa-db-fra.com

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

J'ai:

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

Je veux convertir le tableau d'octets en une chaîne telle que je puisse imprimer la chaîne en utilisant printf:

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

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

"00:01:0A:0B"

Toute aide serait grandement appréciée.

71
Steve Walsh
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, vous pouvez le faire de plusieurs façons ... Je garderais probablement un pointeur sur la fin de la chaîne et utiliser sprintf. vous devriez également garder trace de la taille du tableau pour vous assurer qu'il ne soit pas plus grand que 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");
74
Mark Synowiec

Pour compléter, vous pouvez également le faire facilement sans appeler une fonction de bibliothèque lourde (pas de snprintf, pas de strcat, pas même de mémoire). Cela peut être utile, par exemple, si vous programmez un microcontrôleur ou un noyau de système d’exploitation où la libc n’est pas disponible. 

Rien de vraiment génial, vous pouvez trouver un code similaire si vous le cherchez sur Google. Vraiment ce n'est pas beaucoup plus compliqué que d'appeler snprintf et 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. Cela évite simplement la variable d'index intermédiaire i et la duplication du dernier code de casse (mais le caractère final 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);
}

Vous trouverez ci-dessous une autre version pour répondre à un commentaire disant que j’ai utilisé une "astuce" pour connaître la taille du tampon d’entrée. En réalité, il ne s’agit pas d’une astuce, mais d’une connaissance nécessaire (vous devez connaître la taille des données que vous convertissez). J'ai clarifié cela en extrayant le code de conversion dans une fonction distincte. J'ai également ajouté un code de vérification des limites pour le tampon cible, ce 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);
}
25
kriss

Voici une méthode 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
}
15
Yannuth

Des réponses similaires existent déjà ci-dessus, j'ai ajouté celle-ci pour expliquer le fonctionnement exact de la ligne de code suivante:

ptr += sprintf (ptr, "%02X", buf[i])

C'est assez délicat et pas facile à comprendre, je mets l'explication dans les commentaires ci-dessous:

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

/* Allocate twice the number of the bytes in the buf array because each byte would be 
 * converted to two hex characters, also add an extra space for the terminating null byte
 * [size] is the size of the buf array */
char output[(size * 2) + 1];

/* pointer to the first item (0 index) of the output array */
char *ptr = &output[0];

int i;

for (i = 0; i < size; i++)
{
    /* sprintf converts each byte to 2 chars hex string and a null byte, for example
     * 10 => "0A\0".
     *
     * These three chars would be added to the output array starting from
     * the ptr location, for example if ptr is pointing at 0 index then the hex chars
     * "0A\0" would be written as output[0] = '0', output[1] = 'A' and output[2] = '\0'.
     *
     * sprintf returns the number of chars written execluding the null byte, in our case
     * this would be 2. Then we move the ptr location two steps ahead so that the next
     * hex char would be written just after this one and overriding this one's null byte.
     *
     * We don't need to add a terminating null byte because it's already added from 
     * the last hex string. */  
    ptr += sprintf (ptr, "%02X", buf[i]);
}

printf ("%s\n", output);
7
razzak

Je voulais juste ajouter ce qui suit, même s’il s’agit d’un sujet légèrement hors sujet (non standard), mais je me retrouve souvent à le chercher et à tomber sur cette question parmi les premiers résultats de recherche. La fonction d'impression du noyau Linux, printk, possède également des spécificateurs de format pour la sortie du contenu de tableau/mémoire "directement" via 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 le (s)printf standard, espace utilisateur.

5
sdaau

Cette fonction convient lorsque l’utilisateur/appelant souhaite que la chaîne hexagonale soit placée dans un tableau/tampon de caractères. Avec une chaîne hexadécimale dans un tampon de caractères, l’utilisateur/appelant peut utiliser sa propre macro/fonction pour l’afficher ou le connecter à n’importe quel emplacement souhaité (par exemple, dans un fichier). Cette fonction permet également à l'appelant de contrôler le nombre d'octets (hexadécimaux) à insérer dans chaque ligne.

/**
 * @fn 
 * get_hex
 *
 * @brief 
 * Converts a char into bunary string 
 *
 * @param[in]   
 *     buf Value to be converted to hex string
 * @param[in]   
 *     buf_len Length of the buffer
 * @param[in]   
 *     hex_ Pointer to space to put Hex string into
 * @param[in]   
 *     hex_len Length of the hex string space
 * @param[in]   
 *     num_col Number of columns in display hex string
 * @param[out]   
 *     hex_ Contains the hex string
 * @return  void
 */
static inline void
get_hex(char *buf, int buf_len, char* hex_, int hex_len, int num_col)
{
    int i;
#define ONE_BYTE_HEX_STRING_SIZE   3
  unsigned int byte_no = 0;

  if (buf_len <= 0) {
      if (hex_len > 0) {
        hex_[0] = '\0';
      }
      return;
  }

  if(hex_len < ONE_BYTE_HEX_STRING_SIZE + 1)
  {
      return;
  }

  do {
         for (i = 0; ((i < num_col) && (buf_len > 0) && (hex_len > 0)); ++i )
         {
            snprintf(hex_, hex_len, "%02X ", buf[byte_no++] & 0xff);
            hex_ += ONE_BYTE_HEX_STRING_SIZE;
            hex_len -=ONE_BYTE_HEX_STRING_SIZE;
            buf_len--;
         }
         if (buf_len > 1)
         {
             snprintf(hex_, hex_len, "\n");
             hex_ += 1;
         }
  } while ((buf_len) > 0 && (hex_len > 0));

}

Exemple: Code

#define DATA_HEX_STR_LEN 5000
    char      data_hex_str[DATA_HEX_STR_LEN];

    get_hex(pkt, pkt_len, data_hex_str, DATA_HEX_STR_LEN, 16);
    //      ^^^^^^^^^^^^                                  ^^
    //      Input byte array                              Number of (hex) byte
    //      to be converted to hex string                 columns in hex string

    printf("pkt:\n%s",data_hex_str) 

SORTIE

pkt:
BB 31 32 00 00 00 00 00 FF FF FF FF FF FF DE E5 
A8 E2 8E C1 08 06 00 01 08 00 06 04 00 01 DE E5 
A8 E2 8E C1 67 1E 5A 02 00 00 00 00 00 00 67 1E 
5A 01 
1
Sandesh Kumar Sodhi

C'est une façon d'effectuer la conversion:

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

#define l_Word 15
#define u_Word 240

char *hex_str[]={"0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"};

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


     char *str = malloc(50);
     char *tmp;
     char *tmp2;

     int i=0;


     while( i < (argc-1)) {
          tmp = hex_str[*(argv[i]) & l_Word];
          tmp2 = hex_str[*(argv[i]) & u_Word];

          if(i == 0) { memcpy(str,tmp2,1); strcat(str,tmp);}
          else { strcat(str,tmp2); strcat(str,tmp);}
          i++;
    }

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

}
1
rajaganesh

Solution

La fonction btox convertit des données arbitraires *bb en une chaîne non terminée *xp de n chiffres hexadécimaux:

void btox(char *xp, const char *bb, int n) 
{
    const char xx[]= "0123456789ABCDEF";
    while (--n >= 0) xp[n] = xx[(bb[n>>1] >> ((1 - (n&1)) << 2)) & 0xF];
}

Exemple

#include <stdio.h>

typedef unsigned char uint8;

void main(void) 
{
    uint8 buf[] = {0, 1, 10, 11};
    int n = sizeof buf << 1;
    char hexstr[n + 1];

    btox(hexstr, buf, n);
    hexstr[n] = 0; /* Terminate! */
    printf("%s\n", hexstr);
}

Résultat: 00010A0B.

Live: Tio.run .

0
7vujy0f0hy

La solution de ZincX adaptée pour inclure les délimiteurs du côlon:

char buf[] = {0,1,10,11};
int i, size = sizeof(buf) / sizeof(char);
char *buf_str = (char*) malloc(3 * size), *buf_ptr = buf_str;
if (buf_str) {
  for (i = 0; i < size; i++)
    buf_ptr += sprintf(buf_ptr, i < size - 1 ? "%02X:" : "%02X\0", buf[i]);
  printf("%s\n", buf_str);
  free(buf_str);
}
0
Gnubie

J'ajouterai la version C++ ici à toute personne intéressée.

#include <iostream>
#include <iomanip>
inline void print_bytes(char const * buffer, std::size_t count, std::size_t bytes_per_line, std::ostream & out) {
    std::ios::fmtflags flags(out.flags()); // Save flags before manipulation.
    out << std::hex << std::setfill('0');
    out.setf(std::ios::uppercase);
    for (std::size_t i = 0; i != count; ++i) {
        auto current_byte_number = static_cast<unsigned int>(static_cast<unsigned char>(buffer[i]));
        out << std::setw(2) << current_byte_number;
        bool is_end_of_line = (bytes_per_line != 0) && ((i + 1 == count) || ((i + 1) % bytes_per_line == 0));
        out << (is_end_of_line ? '\n' : ' ');
    }
    out.flush();
    out.flags(flags); // Restore original flags.
}

Il imprimera l'hexdump de buffer de longueur count sur std::ostreamout (vous pouvez lui attribuer la valeur par défaut std::cout). Chaque ligne contiendra bytes_per_line octets, chaque octet étant représenté par une lettre hexadécimale majuscule à deux chiffres. Il y aura un espace entre les octets. Et à la fin de la ligne ou à la fin de la mémoire tampon, une nouvelle ligne sera imprimée. Si bytes_per_line est défini sur 0, la nouvelle ligne ne sera pas imprimée. Essayez par vous-même.

0
WiSaGaN

Si vous souhaitez stocker les valeurs hexadécimales dans une chaîne char *, vous pouvez utiliser snprintf. Vous devez allouer de l'espace pour tous les caractères imprimés, y compris les zéros et les deux points.

Développer la réponse de Mark:

char str_buf* = malloc(3*X + 1);   // X is the number of bytes to be converted

int i;
for (i = 0; i < x; i++)
{
    if (i > 0) snprintf(str_buf, 1, ":");
    snprintf(str_buf, 2, "%02X", num_buf[i]);  // need 2 characters for a single hex value
}
snprintf(str_buf, 2, "\n\0"); // dont forget the NULL byte

Donc maintenant str_buf contiendra la chaîne hexadécimale.

0
Mr. Shickadance

Il n'y a pas de primitive pour cela en C. Je serais probablement en malloc (ou peut-être allouer) un tampon suffisamment long et une boucle sur l'entrée. Je l'ai également constaté avec une bibliothèque de chaînes dynamiques avec une sémantique (mais pas de syntaxe!) Semblable à la variable ostringstream de C++, qui est une solution plausiblement plus générique, mais qui ne vaut peut-être pas la complexité supplémentaire pour un seul cas.

0
crazyscot

Pour une utilisation simple, j'ai créé une fonction qui code la chaîne d'entrée (données binaires):

/* Encodes string to hexadecimal string reprsentation
    Allocates a new memory for supplied lpszOut that needs to be deleted after use
    Fills the supplied lpszOut with hexadecimal representation of the input
    */
void StringToHex(unsigned char *szInput, size_t size_szInput, char **lpszOut)
{
    unsigned char *pin = szInput;
    const char *hex = "0123456789ABCDEF";
    size_t outSize = size_szInput * 2 + 2;
    *lpszOut = new char[outSize];
    char *pout = *lpszOut;
    for (; pin < szInput + size_szInput; pout += 2, pin++)
    {
        pout[0] = hex[(*pin >> 4) & 0xF];
        pout[1] = hex[*pin & 0xF];
    }
    pout[0] = 0;
}

Usage:

unsigned char input[] = "This is a very long string that I want to encode";
char *szHexEncoded = NULL;
StringToHex(input, strlen((const char *)input), &szHexEncoded);

printf(szHexEncoded);

// The allocated memory needs to be deleted after usage
delete[] szHexEncoded;
0
mbuster

Version légèrement modifiée de Yannith . C'est juste que j'aime l'avoir comme valeur de retour

typedef struct {
   size_t len;
   uint8_t *bytes;
} vdata;

char* vdata_get_hex(const vdata data)
{
   char hex_str[]= "0123456789abcdef";

   char* out;
   out = (char *)malloc(data.len * 2 + 1);
   (out)[data.len * 2] = 0;
   
   if (!data.len) return NULL;
   
   for (size_t i = 0; i < data.len; i++) {
      (out)[i * 2 + 0] = hex_str[(data.bytes[i] >> 4) & 0x0F];
      (out)[i * 2 + 1] = hex_str[(data.bytes[i]     ) & 0x0F];
   }
   return out;
}

0
Mikhail Baynov

Basé sur la réponse de Yannuth mais simplifiée.

Ici, la longueur de dest[] est impliquée comme étant deux fois de len et son attribution est gérée par l'appelant.

void create_hex_string_implied(const unsigned char *src, size_t len, unsigned char *dest)
{
    static const unsigned char table[] = "0123456789abcdef";

    for (; len > 0; --len)
    {
        unsigned char c = *src++;
        *dest++ = table[c >> 4];
        *dest++ = table[c & 0x0f];
    }
}
0
konsolebox

Je sais que cette question a déjà une réponse, mais je pense que ma solution pourrait aider quelqu'un.

Donc, dans mon cas, j'avais un tableau d'octets représentant la clé et j'avais besoin de convertir ce tableau d'octets en tableau de caractères de valeurs hexadécimales afin de l'imprimer sur une ligne. J'ai extrait mon code pour une fonction comme celle-ci:

char const * keyToStr(uint8_t const *key)
{
    uint8_t offset = 0;
    static char keyStr[2 * KEY_SIZE + 1];

    for (size_t i = 0; i < KEY_SIZE; i++)
    {
        offset += sprintf(keyStr + offset, "%02X", key[i]);
    }
    sprintf(keyStr + offset, "%c", '\0');

    return keyStr;
}

Maintenant, je peux utiliser ma fonction comme ceci:

Serial.print("Public key: ");
Serial.println(keyToStr(m_publicKey));

L'objet Serial fait partie de la bibliothèque Arduino et m_publicKey est membre de ma classe avec la déclaration suivante uint8_t m_publicKey[32].

0
NutCracker