web-dev-qa-db-fra.com

Conversion d'une chaîne hexadécimale en un tableau d'octets

Quel est le meilleur moyen de convertir une chaîne hexagonale de longueur variable, par ex. "01A1" à un tableau d'octets contenant ces données.

c'est-à-dire convertir ceci:

std::string = "01A1";

dans cette

char* hexArray;
int hexLength;

ou ca

std::vector<char> hexArray;

de sorte que lorsque j'écris ceci dans un fichier et que hexdump -C je reçois les données binaires contenant 01A1.

30
oracal

Cela devrait fonctionner:

int char2int(char input)
{
  if(input >= '0' && input <= '9')
    return input - '0';
  if(input >= 'A' && input <= 'F')
    return input - 'A' + 10;
  if(input >= 'a' && input <= 'f')
    return input - 'a' + 10;
  throw std::invalid_argument("Invalid input string");
}

// This function assumes src to be a zero terminated sanitized string with
// an even number of [0-9a-f] characters, and target to be sufficiently large
void hex2bin(const char* src, char* target)
{
  while(*src && src[1])
  {
    *(target++) = char2int(*src)*16 + char2int(src[1]);
    src += 2;
  }
}

En fonction de votre plate-forme spécifique, il existe probablement aussi une implémentation standard.

26
Niels Keurentjes

Cette implémentation utilise la fonction strtol intégrée pour gérer la conversion réelle de texte en octets, mais fonctionne pour toute chaîne hexagonale de longueur égale.

std::vector<char> HexToBytes(const std::string& hex) {
  std::vector<char> bytes;

  for (unsigned int i = 0; i < hex.length(); i += 2) {
    std::string byteString = hex.substr(i, 2);
    char byte = (char) strtol(byteString.c_str(), NULL, 16);
    bytes.Push_back(byte);
  }

  return bytes;
}
19
Chris Vasselli

Donc, pour le plaisir, je me demandais si je pouvais faire ce genre de conversion au moment de la compilation. Il ne comporte pas beaucoup de vérification des erreurs et a été effectué dans VS2015, qui ne prend pas encore en charge les fonctions C++ 14 constexpr (et donc comment HexCharToInt est recherché). Il faut un tableau c-string, convertit des paires de caractères en un seul octet et développe ces octets en une liste d'initialisation uniforme utilisée pour initialiser le type T fourni en tant que paramètre de modèle. T pourrait être remplacé par quelque chose comme std :: array pour renvoyer automatiquement un tableau.

#include <cstdint>
#include <initializer_list>
#include <stdexcept>
#include <utility>

/* Quick and dirty conversion from a single character to its hex equivelent */
constexpr std::uint8_t HexCharToInt(char Input)
{
    return
    ((Input >= 'a') && (Input <= 'f'))
    ? (Input - 87)
    : ((Input >= 'A') && (Input <= 'F'))
    ? (Input - 55)
    : ((Input >= '0') && (Input <= '9'))
    ? (Input - 48)
    : throw std::exception{};
}

/* Position the characters into the appropriate nibble */
constexpr std::uint8_t HexChar(char High, char Low)
{
    return (HexCharToInt(High) << 4) | (HexCharToInt(Low));
}

/* Adapter that performs sets of 2 characters into a single byte and combine the results into a uniform initialization list used to initialize T */
template <typename T, std::size_t Length, std::size_t ... Index>
constexpr T HexString(const char (&Input)[Length], const std::index_sequence<Index...>&)
{
    return T{HexChar(Input[(Index * 2)], Input[((Index * 2) + 1)])...};
}

/* Entry function */
template <typename T, std::size_t Length>
constexpr T HexString(const char (&Input)[Length])
{
    return HexString<T>(Input, std::make_index_sequence<(Length / 2)>{});
}

constexpr auto Y = KS::Utility::HexString<std::array<std::uint8_t, 3>>("ABCDEF");
7
Rob Yull

Si vous voulez utiliser OpenSSL pour le faire, voici une astuce intéressante:

BIGNUM *input = BN_new();
int input_length = BN_hex2bn(&input, argv[2]);
input_length = (input_length + 1) / 2; // BN_hex2bn() returns number of hex digits
unsigned char *input_buffer = (unsigned char*)malloc(input_length);
retval = BN_bn2bin(input, input_buffer);

Veillez simplement à éliminer tout «0x» qui précède la chaîne.

4
samoz

Vous avez dit "longueur variable". Vous voulez dire à quel point variable?

Pour les chaînes hexagonales qui entrent dans un long non signé, j'ai toujours aimé la fonction C strtoul. Pour le convertir, passez 16 en tant que valeur de base.

Le code pourrait ressembler à:

#include <cstdlib>
std::string str = "01a1";
unsigned long val = strtoul(str.c_str(), 0, 16);
3
Zan Lynx

J'utiliserais une fonction standard telle que sscanf pour lire la chaîne dans un entier non signé, et vous disposeriez déjà des octets dont vous avez besoin en mémoire. Si vous étiez sur une machine big endian, vous pourriez simplement écrire (memcpy) la mémoire de l'entier du premier octet différent de zéro. Cependant, vous ne pouvez pas supposer cela en général en toute sécurité. Vous pouvez donc utiliser certains masques et décalages de bits pour extraire les octets.

const char* src = "01A1";
char hexArray[256] = {0};
int hexLength = 0;

// read in the string
unsigned int hex = 0;
sscanf(src, "%x", &hex);

// write it out
for (unsigned int mask = 0xff000000, bitPos=24; mask; mask>>=8, bitPos-=8) {
    unsigned int currByte = hex & mask;
    if (currByte || hexLength) {
        hexArray[hexLength++] = currByte>>bitPos;
    }
}
2
TooTone
#include <iostream>
#include <sstream>
#include <vector>

int main() {
    std::string s("313233");
    char delim = ',';
    int len = s.size();
    for(int i = 2; i < len; i += 3, ++len) s.insert(i, 1, delim);
    std::istringstream is(s);
    std::ostringstream os;
    is >> std::hex;
    int n;
    while (is >> n) {
        char c = (char)n;
        os << std::string(&c, 1);
        if(is.peek() == delim) is.ignore();
    }

    // std::string form
    std::string byte_string = os.str();
    std::cout << byte_string << std::endl;
    printf("%s\n", byte_string.c_str());

    // std::vector form
    std::vector<char> byte_vector(byte_string.begin(), byte_string.end());
    byte_vector.Push_back('\0'); // needed for a c-string
    printf("%s\n", byte_vector.data());
}

La sortie est

123
123
123

'1' == 0x31, etc.

1
metamystical

Si votre objectif est la vitesse, je dispose d’une implémentation AVX2 SIMD d’un codeur et d’un décodeur ici: https://github.com/zbjornson/fast-hex . Ces benchmarks sont environ 12 fois plus rapides que les implémentations scalaires les plus rapides.

1
ZachB

Cela peut être fait avec un stringstream, il vous suffit de stocker la valeur dans un type numérique intermédiaire tel que int

  std::string test = "01A1"; // assuming this is an even length string
  char bytes[test.length()/2];
  stringstream converter;
  for(int i = 0; i < test.length(); i+=2)
  {
      converter << std::hex << test.substr(i,2);
      int byte;
      converter >> byte;
      bytes[i/2] = byte & 0xFF;
      converter.str(std::string());
      converter.clear();
  }
1
TheoretiCAL

Variante C++ 11 (avec gcc 4.7 - format little endian):

    #include <string>
    #include <vector>

    std::vector<uint8_t> decodeHex(const std::string & source)
    {
        if ( std::string::npos != source.find_first_not_of("0123456789ABCDEFabcdef") )
        {
            // you can throw exception here
            return {};
        }

        union
        {
            uint64_t binary;
            char byte[8];
        } value{};

        auto size = source.size(), offset = (size % 16);
        std::vector<uint8_t> binary{};
        binary.reserve((size + 1) / 2);

        if ( offset )
        {
            value.binary = std::stoull(source.substr(0, offset), nullptr, 16);

            for ( auto index = (offset + 1) / 2; index--; )
            {
                binary.emplace_back(value.byte[index]);
            }
        }

        for ( ; offset < size; offset += 16 )
        {
            value.binary = std::stoull(source.substr(offset, 16), nullptr, 16);
            for ( auto index = 8; index--; )
            {
                binary.emplace_back(value.byte[index]);
            }
        }

        return binary;
    }

Variante Crypto ++ (avec gcc 4.7):

#include <string>
#include <vector>

#include <crypto++/filters.h>
#include <crypto++/hex.h>

std::vector<unsigned char> decodeHex(const std::string & source)
{
    std::string hexCode;
    CryptoPP::StringSource(
              source, true,
              new CryptoPP::HexDecoder(new CryptoPP::StringSink(hexCode)));

    return std::vector<unsigned char>(hexCode.begin(), hexCode.end());
}

Notez que la première variante est environ deux fois plus rapide que la seconde et fonctionne en même temps avec un nombre pair et impair (le résultat de "a56ac" est {0x0a, 0x56, 0xac}). Crypto ++ rejette le dernier s'il y a un nombre impair de nibbels (le résultat de "a56ac" est {0xa5, 0x6a}) et ignore en silence les caractères hexadécimaux invalides (le résultat de "a5sac" est {0xa5, 0xac}).

1
Stamen Rakov

Si vous pouvez faire en sorte que vos données ressemblent à ceci: par exemple, un tableau de "0x01", "0xA1" Ensuite, vous pouvez répéter votre tableau et utiliser sscanf pour créer le tableau de valeurs

unsigned int result;
sscanf(data, "%x", &result);         
0
Anand Rathi

J'ai trouvé cette question, mais la réponse acceptée ne m'a pas semblé être un moyen de résoudre le problème en C++ (cela ne veut pas dire que ce soit une mauvaise réponse ou quoi que ce soit, il suffit d'expliquer la motivation derrière l'ajout de celle-ci). Je me suis rappelé cette belle réponse et a décidé de mettre en œuvre quelque chose de similaire. Voici le code complet de ce avec quoi j'ai fini (ça marche aussi pour std::wstring):

#include <cctype>
#include <cstdlib>

#include <algorithm>
#include <iostream>
#include <iterator>
#include <ostream>
#include <stdexcept>
#include <string>
#include <vector>

template <typename OutputIt>
class hex_ostream_iterator :
    public std::iterator<std::output_iterator_tag, void, void, void, void>
{
    OutputIt out;
    int digitCount;
    int number;

public:
    hex_ostream_iterator(OutputIt out) : out(out), digitCount(0), number(0)
    {
    }

    hex_ostream_iterator<OutputIt> &
    operator=(char c)
    {
        number = (number << 4) | char2int(c);
        digitCount++;

        if (digitCount == 2) {
            digitCount = 0;
            *out++ = number;
            number = 0;
        }
        return *this;
    }

    hex_ostream_iterator<OutputIt> &
    operator*()
    {
        return *this;
    }

    hex_ostream_iterator<OutputIt> &
    operator++()
    {
        return *this;
    }

    hex_ostream_iterator<OutputIt> &
    operator++(int)
    {
        return *this;
    }

private:
    int
    char2int(char c)
    {
        static const std::string HEX_CHARS = "0123456789abcdef";

        const char lowerC = std::tolower(c);
        const std::string::size_type pos = HEX_CHARS.find_first_of(lowerC);
        if (pos == std::string::npos) {
            throw std::runtime_error(std::string("Not a hex digit: ") + c);
        }
        return pos;
    }
};

template <typename OutputIt>
hex_ostream_iterator<OutputIt>
hex_iterator(OutputIt out)
{
    return hex_ostream_iterator<OutputIt>(out);
}

template <typename InputIt, typename OutputIt>
hex_ostream_iterator<OutputIt>
from_hex_string(InputIt first, InputIt last, OutputIt out)
{
    if (std::distance(first, last) % 2 == 1) {
        *out = '0';
        ++out;
    }
    return std::copy(first, last, out);
}

int
main(int argc, char *argv[])
{
    if (argc != 2) {
        std::cout << "Usage: " << argv[0] << " hexstring" << std::endl;
        return EXIT_FAILURE;
    }

    const std::string input = argv[1];
    std::vector<unsigned char> bytes;
    from_hex_string(input.begin(), input.end(),
                    hex_iterator(std::back_inserter(bytes)));

    typedef std::ostream_iterator<unsigned char> osit;
    std::copy(bytes.begin(), bytes.end(), osit(std::cout));

    return EXIT_SUCCESS;
}

Et la sortie de ./hex2bytes 61a062a063 | hexdump -C:

00000000  61 a0 62 a0 63                                    |a.b.c|
00000005

Et de ./hex2bytes 6a062a063 | hexdump -C (notez le nombre impair de caractères):

00000000  06 a0 62 a0 63                                    |..b.c|
00000005
0
xaizek
#include <iostream>

using byte = unsigned char;

static int charToInt(char c) {
    if (c >= '0' && c <= '9') {
        return c - '0';
    }
    if (c >= 'A' && c <= 'F') {
        return c - 'A' + 10;
    }
    if (c >= 'a' && c <= 'f') {
        return c - 'a' + 10;
    }
    return -1;
}

// Decodes specified HEX string to bytes array. Specified nBytes is length of bytes
// array. Returns -1 if fails to decode any of bytes. Returns number of bytes decoded
// on success. Maximum number of bytes decoded will be equal to nBytes. It is assumed
// that specified string is '\0' terminated.
int hexStringToBytes(const char* str, byte* bytes, int nBytes) {
    int nDecoded {0};
    for (int i {0}; str[i] != '\0' && nDecoded < nBytes; i += 2, nDecoded += 1) {
        if (str[i + 1] != '\0') {
            int m {charToInt(str[i])};
            int n {charToInt(str[i + 1])};
            if (m != -1 && n != -1) {
                bytes[nDecoded] = (m << 4) | n;
            } else {
                return -1;
            }
        } else {
            return -1;
        }
    }
    return nDecoded;
}

int main(int argc, char* argv[]) {
    if (argc < 2) {
        return 1;
    }

    byte bytes[0x100];
    int ret {hexStringToBytes(argv[1], bytes, 0x100)};
    if (ret < 0) {
        return 1;
    }
    std::cout << "number of bytes: " << ret << "\n" << std::hex;
    for (int i {0}; i < ret; ++i) {
        if (bytes[i] < 0x10) {
            std::cout << "0";
        }
        std::cout << (bytes[i] & 0xff);
    }
    std::cout << "\n";

    return 0;
}
0
nullptr

Très semblable aux autres réponses ici, voici ce que j’ai choisi:

typedef uint8_t BYTE;

BYTE* ByteUtils::HexStringToBytes(BYTE* HexString, int ArrayLength)
{
  BYTE* returnBytes;
  returnBytes = (BYTE*) malloc(ArrayLength/2);
  int j=0;

  for(int i = 0; i < ArrayLength; i++)
  {
    if(i % 2 == 0)
    {
      int valueHigh = (int)(*(HexString+i));
      int valueLow =  (int)(*(HexString+i+1));

      valueHigh = ByteUtils::HexAsciiToDec(valueHigh);
      valueLow =  ByteUtils::HexAsciiToDec(valueLow);

      valueHigh *= 16;
      int total = valueHigh + valueLow;
      *(returnBytes+j++) = (BYTE)total;
    }
  }
  return returnBytes;
}

int ByteUtils::HexAsciiToDec(int value)
{
  if(value > 47 && value < 59)
  {
    value -= 48;
  }
  else if(value > 96 && value < 103)
  {
    value -= 97;
    value += 10;
  }
  else if(value > 64 && value < 71)
  {
    value -= 65;
    value += 10;
  }
  else
  {
    value = 0;
  }
  return value;
}
0
Willem van Ketwich

Dans: "303132", Out: "012". La chaîne d'entrée peut avoir une longueur impaire ou paire.

char char2int(char input)
{
    if (input >= '0' && input <= '9')
        return input - '0';
    if (input >= 'A' && input <= 'F')
        return input - 'A' + 10;
    if (input >= 'a' && input <= 'f')
        return input - 'a' + 10;

    throw std::runtime_error("Incorrect symbol in hex string");
};

string hex2str(string &hex)
{
    string out;
    out.resize(hex.size() / 2 + hex.size() % 2);

    string::iterator it = hex.begin();
    string::iterator out_it = out.begin();
    if (hex.size() % 2 != 0) {
        *out_it++ = char(char2int(*it++));
    }

    for (; it < hex.end() - 1; it++) {
        *out_it++ = char2int(*it++) << 4 | char2int(*it);
    };

    return out;
}
0
dontsov

La difficulté dans une conversion d'hex en caractère est que les chiffres hexadécimaux fonctionnent par paire, f.ex: 3132 ou A0FF. Donc, un nombre pair de chiffres hexadécimaux est supposé. Cependant, il pourrait être parfaitement valide d'avoir un nombre impair de chiffres, tels que: 332 et AFF, ce qui devrait être compris comme 0332 et 0AFF. 

Je propose une amélioration à la fonction de Niels Keurentjes hex2bin (). Nous comptons d’abord le nombre de chiffres hexadécimaux valides. Comme nous devons compter, contrôlons également la taille de la mémoire tampon: 

void hex2bin(const char* src, char* target, size_t size_target)
{
    int countdgts=0;    // count hex digits
    for (const char *p=src; *p && isxdigit(*p); p++) 
        countdgts++;                            
    if ((countdgts+1)/2+1>size_target)
        throw exception("Risk of buffer overflow"); 

En passant, pour utiliser isxdigit(), vous devrez #include <cctype>.
Une fois que nous savons combien de chiffres, nous pouvons déterminer si le premier est le chiffre le plus élevé (uniquement les paires) ou non (le premier chiffre n'est pas une paire).

bool ishi = !(countdgts%2);         

Ensuite, nous pouvons boucler, chiffre par chiffre, en combinant chaque paire en utilisant bin shift << et bin ou, et En basculant l’indicateur «haut» à chaque itération: 

    for (*target=0; *src; ishi = !ishi)  {    
        char tmp = char2int(*src++);    // hex digit on 4 lower bits
        if (ishi)
            *target = (tmp << 4);   // high:  shift by 4
        else *target++ |= tmp;      // low:  complete previous  
    } 
  *target=0;    // null terminated target (if desired)
}
0
Christophe