Перетворення шістнадцяткового рядка в масив байт

Який найкращий спосіб перетворити шістнадцятковий рядок із змінною довжиною, наприклад, "01A1" до масиву байтів, що містить ці дані.

тобто перетворює це:

std::string = "01A1";

в цьому

char* hexArray;
int hexLength;

або це

std::vector hexArray;

так що, коли я пишу це у файл і hexdump -C , я отримую двійкові дані, що містять 01A1 .

29
Виходячи з коментаря, зробленого вами до іншої відповіді, я думаю, вам потрібно додати до свого питання, що має відбутися, коли вхідні дані складаються з непарної кількості символів. Якщо відсутній 0 буде додано до початку рядка або кінця?
додано Автор Zan Lynx, джерело
додано Автор Alex VII, джерело
Ви можете встановити std :: stream на шестнадцятковий режим для читання і запису чисел у форматі hex
додано Автор πάντα ῥεῖ, джерело
@oracal Див. мою відповідь на підхід струнної течії
додано Автор TheoretiCAL, джерело
Я не думаю, що будь-який висновок ascii не потрібно, просто використовуйте C api для перетворення в матрицю, якщо я не отримав питання неправильно. Я вказав api в моїх ансах нижче stackoverflow.com/a/17273020/986760 .
додано Автор fayyazkl, джерело
@makulik я спробував використовувати потоки і std :: hex, але не міг нічого працювати. Не могли б ви показати мені приклад? Дякую.
додано Автор oracal, джерело
@alexvii Це не є відповіддю на це питання.
додано Автор dhavenith, джерело

16 Відповіді

Це має працювати:

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

Залежно від вашої конкретної платформи, можливо, є також стандартна реалізація.

24
додано
@fayyazkl Я не розумію, що ви маєте на увазі?
додано Автор Niels Keurentjes, джерело
@Christophe Я мав на увазі поведінку atoi при синтаксичному аналізі "до тих пір, поки він має значущий вхід", - якщо ви його подаєте, рядок 123abc повертає ціле число 123 код> ( cplusplus.com/reference/cstdlib/atoi ). Як таку, я написав цю функцію на тій же самій посаді, як дезінфікований вхід, і «невизначена або брудна» поведінка в іншому випадку. Додавання валідації вхідних даних, звичайно, тривіально, але не завжди потрібні додаткові витрати.
додано Автор Niels Keurentjes, джерело
@fayyazkl ви неправильно зрозуміли питання - це про перетворення читається людиною 4-символьного рядка "01A1" в 2 в байтах пам'яті (1 і 161). Тому, очевидно, необхідне перетворення ASCII.
додано Автор Niels Keurentjes, джерело
@Christophe тому, що while перевіряє * src && src [1] , він би розібрав AF , а потім зіткнеться з нулем src [1] і припиніть конвертацію. Це схоже на поведінку atoi у цьому відношенні - воно зупиняється на вхідних даних.
додано Автор Niels Keurentjes, джерело
@NielsKeurentjes Я не впевнений, що atoi() ігнорує останню непарну цифру ... Чи не здається вам, що це виглядає як помилка, коли AFF обробляється як AF замість 0AFF?
додано Автор Christophe, джерело
@Niels Keurentjes чудове рішення! Але що станеться, якщо є непарна кількість шістнадцяткових цифр. Наприклад AFF?
додано Автор Christophe, джерело
Добре, дякую. Так, я не думав, що був заданий ОП.
додано Автор fayyazkl, джерело
@NielsKeurentjes Що трапилося з використанням c_str() для вищезазначеного? Чому ми повинні вручну перетворити ascii 'A' на hex A і покласти в цільовий символ *. Те, що ви зробили, є правильним. Я просто не можу зрозуміти, чому потрібно вручну робити це, коли є стандартний api, доступний для приховування рядка до char.
додано Автор fayyazkl, джерело
Я не впевнений, що оригінальний рядок має ті ж елементи, чому нам потрібно приховати ascii, щоб отримати числовий еквівалент?
додано Автор fayyazkl, джерело
Хоча це, здається, працює (не може спробувати його в атм), чи є більш стандартний спосіб?
додано Автор oracal, джерело

Ця реалізація використовує вбудовану функцію strtol для обробки фактичного перетворення тексту з байтів, але буде працювати для будь-якого шістнадцяткового рядка рівної довжини.

std::vector HexToBytes(const std::string& hex) {
  std::vector 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
додано
Ну, ви завжди можете попередньо додати '0' для шістнадцяткового рядка непарного розміру
додано Автор user482963, джерело

Якщо ви хочете скористатися 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
додано
Обов'язково BN_free
додано Автор Erik Aronesty, джерело

Так що для задоволення, мені було цікаво, якщо я міг би зробити цей вид перетворення під час компіляції. Вона не має великої кількості перевірок помилок і була виконана у VS2015, яка ще не підтримує C ++ 14 функцій constexpr (таким чином, як виглядає HexCharToInt). Він приймає c-рядковий масив, перетворює пари символів в один байт і розширює ці байти в єдиний список ініціалізації, який використовується для ініціалізації типу T, наданого як параметр шаблону. T може бути замінений на щось на зразок std :: array для автоматичного повернення масиву.

#include 
#include 
#include 
#include 

/* 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 
constexpr T HexString(const char (&Input)[Length], const std::index_sequence&)
{
    return T{HexChar(Input[(Index * 2)], Input[((Index * 2) + 1)])...};
}

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

constexpr auto Y = KS::Utility::HexString>("ABCDEF");
4
додано
Я підвищувався, тому що це було весело.
додано Автор Marco A., джерело
Фантастичний! Я хотів, щоб спосіб ініціалізації масиву був з літерала рядка, і це майже те, що мені потрібно.
додано Автор Martin Bonner, джерело

Ви сказали "змінну довжину". Як ви маєте на увазі змінну?

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

Код може виглядати так:

#include 
std::string str = "01a1";
unsigned long val = strtoul(str.c_str(), 0, 16);
3
додано

Я б використав стандартну функцію, як sscanf , щоб прочитати рядок у ціле без знака, і тоді у вас вже є байти, які вам потрібні в пам'яті. Якщо ви знаходитесь на великій машині endian, ви можете просто написати ( 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
додано

Варіант C ++ 11 (з gcc 4.7 - маленький формат endian):

    #include 
    #include 

    std::vector 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 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 
#include 

#include 
#include 

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

    return std::vector(hexCode.begin(), hexCode.end());
}

Зауважимо, що перший варіант приблизно в два рази швидше другого і в той же час працює з непарним і парним числом грибів (результатом "a56ac" є {0x0a, 0x56, 0xac}). Crypto ++ відкидає останню, якщо є непарне число nibbels (результат "a56ac" {0xa5, 0x6a}) і тихо пропускає недійсні шістнадцяткові символи (результат "a5sac" {0xa5, 0xac}).

1
додано
#include 
#include 
#include 

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 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 і т.д.

1
додано
#include 

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
додано

Якщо вашою метою є швидкість, у мене тут AVX2 SIMD реалізація кодера і декодера тут: .com/zbjornson/fast-hex . Ці тести ~ 12x швидше, ніж найшвидші скалярні реалізації.

0
додано

Складність у перетворенні на шістнадцятку до символу полягає в тому, що шістнадцяткові числа працюють попарно, f.ex: 3132 або A0FF. Тому передбачається парне число шістнадцяткових цифр. Однак, це може бути цілком допустимим мати непарне число цифр, наприклад: 332 і AFF, які слід розуміти як 0332 і 0AFF.

Я пропоную поліпшення функції Niels Keurentjes 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"); 

By the way, to use isxdigit() you'll have to #include .
Once we know how many digits, we can determine if the first one is the higher digit (only pairs) or not (first digit not a pair).

bool ishi = !(countdgts%2);         

Then we can loop digit by digit, combining each pair using bin shift << and bin or, and toggling the 'high' indicator at each iteration:

    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
додано
0
додано
Слід зазначити, що я написав прийняту відповідь як найбільш ефективне повне вирішення питання ОП.
додано Автор Niels Keurentjes, джерело
дуже приємно char2int ()! Але я боюся, що результат не відповідає очікуванням, якщо з незначним числом шістнадцяткових цифр. Наприклад, спробуйте з 6a062a063. Я розумію 6 a0 62 a0 63, але ваш код робить 6a 06 2a 06 3 з нього.
додано Автор Christophe, джерело
Ви маєте рацію щодо непарної кількості шістнадцяткових цифр, @Christophe. Дякую! Я оновлював код для обробки такого випадку добре (до речі, це не так для прийнятої відповіді, все ж краще обробляти такі рядки).
додано Автор xaizek, джерело

Це можна зробити за допомогою 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();
  }
0
додано

Дуже схожий на деякі інші відповіді тут.

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
додано

Якщо ви можете зробити свої дані таким, наприклад, масив "0x01", "0xA1" Потім ви можете повторити ваш масив і використовувати sscanf для створення масиву значень

unsigned int result;
sscanf(data, "%x", &result);         
0
додано
Моя проблема в тому, що я не розумію, що ви намагаєтеся нам розповісти. Існує підказка, є рядок (за яким йде інша версія цього рядка з 0x префіксом), а потім дуже короткий виклад про деяку ітерацію. Сенс всього цього, особливо. в контексті існуючих відповідей мені не зрозуміло. Це буде мати вплив на upvotes/downvotes ви отримаєте для цього.
додано Автор jogojapan, джерело
Це "натяк" або відповідь? А що ви маєте на увазі під "спробувати це"? Чи буде вона працювати? І чи відрізняється він від існуючих відповідей? Як?
додано Автор jogojapan, джерело
Я не впевнений, що нова версія в порядку. Чи не вказує sscanf вказівник на unsigned int у цьому випадку, тому передача вказівника на char може призвести до переповнення? Див., Наприклад, linux.die.net/man/3/sscanf : "< b> x Узгоджується з беззнаковим шістнадцятковим цілим числом, наступний покажчик повинен бути покажчиком на unsigned int . ".
додано Автор TooTone, джерело
@jogojapan Я щасливий написати весь код вам дійсно потрібно? Ви бачите різницю в базовому підході?
додано Автор Anand Rathi, джерело
Завдяки @tooTone для вказівки на це. Я просто виправили це
додано Автор Anand Rathi, джерело

В: "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
додано
IT KPI C/С++ новым годом
IT KPI C/С++ новым годом
747 учасників

Чат обсуждения С/С++. - Вопросы "напишите за меня лабу" - это оффтоп. - Оффтоп, флуд, оскорбления и вбросы здесь не приняты. - За нарушение - предупреждение или mute на неделю. - За спам и рекламу - ban. Все чаты IT KPI: https://t.me/itkpi/1147