web-dev-qa-db-ja.com

16進文字列をバイト配列に変換する

可変長の16進文字列を変換する最良の方法は何ですか? "01A1"をそのデータを含むバイト配列に。

つまり、これを変換します:

std::string = "01A1";

これに

char* hexArray;
int hexLength;

またはこれ

std::vector<char> hexArray;

これをファイルに書き込み、hexdump -Cすると、01A1を含むバイナリデータが取得されます。

32
oracal

これは動作するはずです:

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;
  }
}

特定のプラットフォームにもよりますが、おそらく標準的な実装もあります。

29

この実装は、組み込みのstrtol関数を使用して、テキストからバイトへの実際の変換を処理しますが、任意の長さの16進文字列に対して機能します。

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;
}
22
Chris Vasselli

ですから、楽しみのために、コンパイル時にこの種の変換ができるかどうか興味がありました。多くのエラーチェックがなく、VS2015で行われましたが、C++ 14 constexpr関数はまだサポートされていません(したがって、HexCharToIntはどのように見えますか)。 c-string配列を受け取り、文字のペアを1バイトに変換し、それらのバイトを、テンプレートパラメーターとして提供されたT型を初期化するために使用される統一初期化リストに展開します。 Tはstd :: arrayのようなものに置き換えられ、自動的に配列を返します。

#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");
8
Rob Yull

OpenSSLを使用してそれを行うには、気の利いたトリックがあります。

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);

文字列の先頭の「0x」は必ず削除してください。

4
samoz

あなたは「可変長」と言いました。可変性とはどういう意味ですか?

符号なしlongに適合する16進文字列の場合、私は常にC関数strtoulが好きでした。 16進数に変換するには、基数値として16を渡します。

コードは次のようになります。

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

出力は

123
123
123

'1' == 0x31など.

2
metamystical

これはstringstreamを使用して行うことができ、値を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();
  }
2
TheoretiCAL

sscanfのような標準関数を使用して、文字列を符号なし整数に読み取ります。そうすれば、メモリに必要なバイトが既にあります。ビッグエンディアンのマシンを使用している場合、ゼロ以外の最初のバイトから整数のメモリを書き出す(memcpy)ことができます。ただし、一般にこれを安全に想定することはできないため、ビットマスキングとシフトを使用してバイトを取り出すことができます。

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

あなたの目標が速度である場合、エンコーダーとデコーダーのAVX2 SIMD実装があります: https://github.com/zbjornson/fast-hex 。これらのベンチマークは、最速のスカラー実装よりも約12倍高速です。

1
ZachB

C++ 11バリアント(gcc 4.7-リトルエンディアン形式):

    #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;
    }

Crypto ++バリアント(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());
}

最初のバリアントは2番目のバリアントよりも約2倍高速であると同時に、奇数個と偶数個のニブルで機能することに注意してください( "a56ac"の結果は{0x0a、0x56、0xac}です)。 Crypto ++は、奇数個のニブルがある場合(「a56ac」の結果が{0xa5、0x6a}である場合)最後のものを破棄し、無効な16進文字を静かにスキップします(「a5sac」の結果は{0xa5、0xac}です)。

1
Stamen Rakov

「0x01」、「0xA1」の配列のようにデータを作成できる場合は、配列を反復処理し、sscanfを使用して値の配列を作成できます

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

私はこの質問を見つけましたが、受け入れられた答えは私にタスクを解決するC++の方法のようには見えませんでした(これは悪い答えや何かを意味するものではなく、これを追加する背後にある動機を説明するだけです)。 この素敵な答え を思い出し、似たようなものを実装することにしました。ここに私が終わったものの完全なコードがあります(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;
}

./hex2bytes 61a062a063 | hexdump -Cの出力:

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

./hex2bytes 6a062a063 | hexdump -Cの(奇数文字に注意してください):

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

ここの他の回答のいくつかと非常に似ており、これは私が行ったものです:

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

theoretiCALのコードを変更しました

uint8_t buf[32] = {};
std::string hex = "0123";
while (hex.length() % 2)
    hex = "0" + hex;
std::stringstream stream;
stream << std::hex << hex;

for (size_t i= 0; i <sizeof(buf); i++)
    stream >> buf[i];
0
GeorgeMakarov

入力: "303132"、出力: "012"。入力文字列の長さは奇数でも偶数でもかまいません。

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

16進数から文字への変換の難しさは、16進数がペアで機能することです。f.ex:3132またはA0FF。したがって、偶数の16進数が想定されます。ただし、次のような奇数の桁を持つことは完全に有効である可能性があります:332およびAFF、これは0332および0AFFとして理解される必要があります。

Niels Keurentjes hex2bin()関数の改善を提案します。最初に、有効な16進数の数をカウントします。カウントする必要があるので、バッファーサイズも制御しましょう。

_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"); 
_

ところで、isxdigit()を使用するには、_#include <cctype>_が必要です。
桁数がわかれば、最初の桁が上位桁(ペアのみ)かどうか(最初の桁はペアではない)を判別できます。

_bool ishi = !(countdgts%2);         
_

次に、bin shift <<とbin orを使用して各ペアを結合し、各反復で「high」インジケーターを切り替えて、桁ごとにループできます。

_    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