2013-06-23 2 views
25

Каков наилучший способ преобразования переменной длины в шестнадцатеричную строку, например. "01A1" в массив байтов, содержащий эти данные.Преобразование шестнадцатеричной строки в байт-массив

т.е. превращаю-:

std::string = "01A1"; 

в этот

char* hexArray; 
int hexLength; 

или это

std::vector<char> hexArray; 

так, что, когда я пишу это в файл и hexdump -C это я получаю двоичные данные содержащий 01A1.

+0

http://stackoverflow.com/questions/347949/convert-stdstring -to-const-char-or-char –

+13

@alexvii Это не ответ на этот вопрос. – dhavenith

+2

Вы можете установить std :: streams в шестнадцатеричный режим для чтения и записи чисел в шестнадцатеричном формате –

ответ

23

Это должно работать:

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

В зависимости от конкретной платформы есть, вероятно, также стандартная реализация, хотя.

+0

Хотя это работает (не могу попробовать его atm), есть ли более стандартный способ? – oracal

+0

Я не уверен, исходная строка имеет те же элементы, почему нам нужно скрывать до ascii, чтобы получить числовой эквивалент? – fayyazkl

+0

@fayyazkl Я не понимаю, что вы имеете в виду? –

1

Я бы использовал стандартную функцию, например 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; 
    } 
} 
0

Если вы можете сделать ваши данные, чтобы выглядеть следующим образом, например, массив «0х01», «0xA1» Затем вы можете перебирать свой массив и использовать sscanf, чтобы создать массив значений

unsigned int result; 
sscanf(data, "%x", &result);   
+2

Является ли это «подсказкой» или ответом? И что вы подразумеваете под «попробуйте это»? Это будет работать? И отличается ли он от существующих ответов? Как? – jogojapan

+0

@jogojapan Я счастлив написать весь код, действительно ли он вам нужен? Вы видите разницу в базовом подходе? –

+2

Моя проблема в том, что я не понимаю, что вы пытаетесь нам рассказать. Есть подсказка, есть строка (за ней следует другая версия этой строки с префиксом '0x'), а затем очень короткая инструкция о некоторой итерации. Смысл всего этого, особенно. в контексте существующих ответов, не ясен для меня. Это будет иметь влияние на upvotes/downvotes, которые вы получите для этого. – jogojapan

0

Я нашел этот вопрос, но принятый ответ не был похож на C++-способ решения задачи для меня (это не значит, что это плохой ответ или что-то еще, просто объясняя мотивацию добавления этого). Я вспомнил this nice answer и решил реализовать нечто подобное. Вот полный код того, что я закончил с (он также работает на 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 = "abcdef"; 

     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

очень красивый char2int()! Но я боюсь, что результат не оправдает ожиданий, когда с нечетным количеством шестнадцатеричных цифр. Например, попробуйте с 6a062a063. Я бы понял, 6 a0 62 a0 63, но ваш код делает 6a 06 2a 06 3 из него. – Christophe

+1

Вы правы насчет нечетного числа шестнадцатеричных цифр, @ Кристофе. Спасибо! Я обновил код, чтобы обработать такой случай (кстати, это не так для принятого ответа, еще лучше обрабатывать такие строки). – xaizek

+0

Следует отметить, что я принял принятый ответ как наиболее полное полное решение вопроса OP. Никаких вопросов о исключительных случаях не было задано, поэтому я предположил (как и многие функции stdc) предварительно обработанный вход. –

0

Сложность преобразования шестнадцатеричных символов заключается в том, что шестнадцатеричные цифры работают попарно, 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);   

Тогда мы можем цикл цифра за цифрой, комбинируя каждую пару с помощью бен сдвиг < < и мусорное ведро или, и переключая «высокий» индикатор на каждой итерации:

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) 
} 
3

Вы сказали, что «переменная длина «. Насколько переменна вы имеете в виду?

Для шестнадцатеричных строк, которые вписываются в unsigned long, мне всегда нравилась функция C strtoul. Чтобы преобразовать шестнадцатеричный проход 16 в качестве значения радиуса.

код может выглядеть следующим образом:

#include <cstdlib> 
std::string str = "01a1"; 
unsigned long val = strtoul(str.c_str(), 0, 16); 
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 'в строку.

17

Эта реализация использует встроенную функцию 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; 
} 
4

Так что для удовольствия мне было любопытно, могу ли я сделать такое преобразование во время компиляции. Он не имеет много ошибок и был выполнен в VS2015, который еще не поддерживает функции C++ 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"); 
+1

. Я поддержал вас, потому что вам было весело. –

+0

Фантастический! Мне нужен способ инициализации массива из строкового литерала, и это почти то, что мне нужно. –

1

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("ABCDEFabcdef")) 
     { 
      // 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}).

1
#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 и т.д.

0

В: "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

Если ваша цель скорость, у меня есть реализация AVX2 SIMD кодера и декодера здесь: https://github.com/zbjornson/fast-hex.Эти эталоны ~ 12x быстрее, чем самые быстрые скалярные реализации.

0

Это может быть сделано с 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(); 
    }