Преобразование шестнадцатеричной строки в массив байтов
Каков наилучший способ преобразования шестнадцатеричной строки переменной длины, например. "01A1"
в массив байтов, содержащий эти данные.
i.e преобразуя это:
std::string = "01A1";
в этот
char* hexArray;
int hexLength;
или
std::vector<char> hexArray;
так что, когда я пишу это в файл и hexdump -C
, я получаю двоичные данные, содержащие 01A1
.
Ответы
Ответ 1
Это должно работать:
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;
}
}
В зависимости от вашей конкретной платформы, возможно, есть и стандартная реализация.
Ответ 2
Эта реализация использует встроенную функцию strtol
для обработки фактического преобразования из текста в байты, но будет работать для любой четной шестнадцатеричной строки.
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;
}
Ответ 3
Так что для удовольствия мне было любопытно, могу ли я сделать такое преобразование во время компиляции. Он не имеет много ошибок и был выполнен в VS2015, который еще не поддерживает функции С++ 14 constexpr (таким образом, как выглядит HexCharToInt). Он принимает массив c-строк, преобразует пары символов в один байт и расширяет эти байты в единый список инициализации, используемый для инициализации типа 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");
Ответ 4
Если вы хотите использовать 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" в строку.
Ответ 5
Вы сказали "переменная длина". Как переменная вы имеете в виду?
Для шестнадцатеричных строк, которые вписываются в unsigned long, мне всегда нравилась функция C strtoul
. Чтобы преобразовать шестнадцатеричный проход 16 в качестве значения радиуса.
Код может выглядеть так:
#include <cstdlib>
std::string str = "01a1";
unsigned long val = strtoul(str.c_str(), 0, 16);
Ответ 6
Я бы использовал стандартную функцию типа 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;
}
}
Ответ 7
#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 и т.д.
Ответ 8
Вариант С++ 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());
}
Обратите внимание, что первый вариант примерно в два раза быстрее второго и в то же время работает с нечетным и четным количеством полубайтов (результатом "a56ac" является {0x0a, 0x56, 0xac}). Crypto ++ отбрасывает последнее, если существует нечетное число ниббелей (результат "a56ac" равен {0xa5, 0x6a}) и тихо пропускает недействительные шестнадцатеричные символы (результат "a5sac" равен {0xa5, 0xac}).
Ответ 9
Если ваша цель - скорость, у меня есть реализация AVX2 SIMD кодера и декодера здесь: https://github.com/zbjornson/fast-hex. Эти эталоны ~ 12 раз быстрее, чем самые быстрые скалярные реализации.
Ответ 10
Это можно сделать с помощью 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();
}
Ответ 11
Если вы можете сделать ваши данные похожими на это, например, массив "0x01", "0xA1",
Затем вы можете перебирать массив и использовать sscanf для создания массива значений
unsigned int result;
sscanf(data, "%x", &result);
Ответ 12
Я нашел этот вопрос, но принятый ответ не был похож на С++-способ решения задачи для меня (это не означает, что это плохой ответ или что-то еще, просто объясняя мотивацию добавления этого). Я вспомнил этот хороший ответ и решил реализовать что-то подобное. Вот полный код того, с чем я закончил (он также работает для 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
Ответ 13
Трудность преобразования hex в char заключается в том, что шестнадцатеричные цифры работают попарно, f.ex: 3132 или A0FF. Поэтому допускается четное число шестнадцатеричных цифр. Однако вполне возможно иметь нечетное число цифр, например: 332 и AFF, которые следует понимать как 0332 и 0AFF.
Я предлагаю усовершенствование функции Heels Kelentjes hex2bin().
Сначала мы подсчитываем количество действительных шестнадцатеричных цифр. Как мы должны считать, пусть также контролирует размер буфера:
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 < < и bin или, и
переключение индикатора "высокий" на каждой итерации:
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)
}
Ответ 14
В: "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;
}
Ответ 15
#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;
}
Ответ 16
Очень похоже на некоторые другие ответы здесь, это то, что я пошел с:
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;
}
Ответ 17
я изменил код
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];