Преобразование двоичных данных в печатаемое шестнадцатеричное

В потоке это кто-то заметил, что следующий код следует использовать только в «игрушечных» проектах. К сожалению, он не вернулся, чтобы сказать, почему он не промышленного качества, поэтому я надеялся, что кто-нибудь из сообщества сможет либо заверить меня, что код в порядке (потому что он мне очень нравится), либо определить, что не так.

template< class T1, class T2>
void hexascii( T1& out, const T2& in )
{
    out.resize( in.size() * 2 );
    const char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7','8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
    T1::iterator outit = out.begin();
    for( T2::const_iterator it = in.begin(); it != in.end(); ++it )
    {
        *outit++ = hexDigits[*it >> 4];
        *outit++ = hexDigits[*it & 0xF];
    }
}

template<class T1, class T2>
void asciihex( T1& out, const T2& in )
{
    size_t size = in.size;
    assert( !(size % 2) );

    out.resize( size / 2 );
    T1::iterator outit = out.begin();
    for( T2::const_iterator it = in.begin(); it != in.end(); it += 2, ++outit )
    {
    *outit = ((( (*it > '9' ? *it - 0x07 : *it)  - 0x30) << 4) & 0x00f0) + 
                (((*(it+1) > '9' ? *(it+1) - 0x07 : *(it+1)) - 0x30) & 0x000f);
    }
}

Обновлено: Спасибо за вашу помощь, ребята, вы сделали несколько больших улучшений. Я написал функции в двух предложенных стилях из ваших ответов. Некоторое грубое тестирование предполагает, что второй метод немного быстрее, чем первый, но, IMO, это перевешивается улучшенной читабельностью первого.

template<class T1>
void asciihex2( T1& out, const std::string& in )
{
    dassert( sizeof(T1::value_type)==1 );
    size_t size = in.size();
assert( !(size % 2) );
    out.resize( size / 2 );
    T1::iterator outit = out.begin();
    for( size_t i = 0; i < in.size(); i += 2 )
    {
        int tmp;
        sscanf( in.c_str() + i, "%02X", &tmp );
        *outit++ = tmp;
    }
}

template<class T1>
void asciihex3( T1& out, const std::string& in )
{
    dassert( sizeof(T1::value_type)==1 );
    size_t size = in.size();
assert( !(size % 2) );
    out.resize( size / 2 );
    T1::iterator outit = out.begin();
const char hexDigits[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 
                          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                  0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F};
for( std::string::const_iterator it = in.begin(); it != in.end(); it += 2, ++outit )
    {
    *outit = (hexDigits[(*it - 0x30) & 0x1f] << 4) + 
              hexDigits[((*(it+1) - 0x30) & 0x1f)];
    }
}

Некоторые из предположений, относящихся к этому коду: 1: Они не предназначены как общие, но используются в анонимном пространстве имен для перевода данных для определенного класса. 2: Шаблон требуется, поскольку используются два отдельных типа контейнеров (один - std :: vector, другой - контейнер с аналогичным типом байтового массива из сторонней библиотеки. 3: Цель состоит в том, чтобы иметь возможность преобразовывать двоичные данные неопределенной длины в строки и обратно (0x1234abcd <-> "1234abcd") 4. Утверждение ошибок ловушек как в режиме отладки, так и в режиме выпуска 5: к моменту вызова этих функций размер строки уже будет проверен, assert используется для прекращения обработки, если что-то серьезное пошло не так 6: Это нуждается в комментариях

Любые другие идеи приветствуются.

Ничего не имею против вашего вопроса, Патрик, но название довольно расплывчатое, к сожалению, я не могу придумать ничего более точного и значимого, но было бы неплохо, если бы у кого-то была хорошая идея, в конце концов, вопрос может означать буквально что угодно.

Robert Gould 05.01.2009 16:02

Разве первая строка asciihex не должна быть такой: size_t size = in.size ();

jrb 05.01.2009 16:07

У меня были те же мысли по поводу вопроса, Роберт, но я также не мог придумать ничего лаконичного и значимого.

Patrick 05.01.2009 16:13

Возможно, HexToBinary и BinaryToHex были бы более значимыми именами, термин ASCII здесь вводит в заблуждение, особенно потому, что использование шаблонов указывает на возможный Unicode. В противном случае шестнадцатеричный код ASCII.

SmacL 05.01.2009 17:10

Пожалуйста, попробуйте сделать заголовок, соответствующий вашему вопросу, а теперь и вашему ответу. Никто и никогда не сможет точно найти это

mmcdole 06.01.2009 02:10

Возможно, почему бы мне не использовать эти функции для преобразования шестнадцатеричного кода в C?

mmcdole 06.01.2009 02:16

@Patrick: assert можно отключить, определив NDEBUG, что многие люди делают для производственного кода.

Bklyn 10.01.2009 01:02
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
7
3 921
9
Перейти к ответу Данный вопрос помечен как решенный

Ответы 9

Ответ принят как подходящий

Похоже, что много шаблонного кода позволяет достичь очень небольшого, учитывая, что у вас есть прямое шестнадцатеричное преобразование в стандартных функциях C сканф и printf. Зачем беспокоиться?

Я думал о том же.

Daniel Sloof 05.01.2009 15:33

sscanf / printf небезопасны по типу. Операторы stream << и >> есть.

xtofl 05.01.2009 15:37

Верно, но вы можете обернуть sscanf и sprintf очень простыми функциями для обеспечения безопасности типов. Вышеупомянутый шаблонный код отлично подходит для чего-то столь простого, а использование шаблонов неверно предполагает, что код удобен для типов.

SmacL 05.01.2009 15:51

Я думаю, что этот ответ исходит из неправильного понимания цели функций (ошибка заключается в вопросе, а не в том, что вы ее читаете - я добавил немного к вопросу). Я не вижу никакого «приятного» способа использования printf для преобразования байтового массива в пригодный для печати ascii и обратно.

Patrick 05.01.2009 16:44

На самом деле я вообще не понимаю, как с помощью sprintf превратить 0x30 в 0x00. Вы согласны или я туплю?

Patrick 05.01.2009 16:48

проверьте квалификатор printf% x по следующей ссылке, cplusplus.com/reference/clibrary/cstdio/printf.html, например sprintf (str, "% 02x", i). Аналогично sscanf cplusplus.com/reference/clibrary/cstdio/scanf.html, как и sscanf ("% x", & i);

SmacL 05.01.2009 17:05

Что превращает 0x00 в 0x30, но как сделать наоборот?

Patrick 05.01.2009 17:25

учитывая char s [3], int i = 15, sprintf (s, "% 02x", i) устанавливает s в "0f", теперь скажите s = "fe", sscanf (s, "% x", & i) устанавливает i к 254.

SmacL 05.01.2009 17:36

Printf великолепен, но бывают случаи, когда такие функции могут понадобиться в среде, где sscanf / printf нежелательны (или, возможно, недоступны): встроенная, низкоуровневая отладка и т. д.

jwfearn 05.01.2009 18:53

@jwfearn Верно, но есть способы лучше, чем в примере выше. В частности, во встроенных, где ресурсы являются проблемой, я бы предпочел потоковый вывод, а не изменение размера буфера.

SmacL 05.01.2009 19:20

@smacl: что, если s = "12abdc34d5ba990345326234656". Когда я кодирую это с помощью scanf, это выглядит очень неправильно.

Patrick 05.01.2009 19:56

@smacl: не изменять размер буфера во встроенном из-за ресурсов? Я в замешательстве. Поток предоставляет аккуратный API для буфера, но поток по-прежнему хранит данные в буфере.

Patrick 05.01.2009 19:59

@smacl: игнорировать scanf выглядит неверным комментарием. Мне просто нужно было немного подумать об этом

Patrick 05.01.2009 20:11

Мой главный комментарий по этому поводу - это очень трудно читать.

Особенно:

*outit = ((( (*it > '9' ? *it - 0x07 : *it)  - 0x30) << 4) & 0x00f0) + 
            (((*(it+1) > '9' ? *(it+1) - 0x07 : *(it+1)) - 0x30) & 0x000f)

Моему мозгу потребовалось бы немного времени, чтобы разобраться в этом, и меня раздражало бы, если бы я унаследовал код.

Точно так же сбивает с толку.

SmacL 05.01.2009 15:56

"Некоторое время"? Либо гений, либо недосказанность.

Anthony 05.01.2009 16:01

Эта строка определенно нуждается в комментарии, но я не вижу возможности ее упрощения, если выбран такой подход к преобразованию. Разделение на несколько строк ...

Patrick 05.01.2009 16:18

Согласился, непонятно как улучшить. Возможно перемещение некоторых проверок в подметоды. Тем не менее, у меня глаза разбегаются.

unknownuser 05.01.2009 16:26

Я бы обязательно заменил "(* it> '9'? * It - 0x07: * it) - 0x30)" встроенной функцией 'getHexValue' и вызвал бы ее дважды. Я также не уверен в значении масок: если входная строка - мусор, кого волнует, какое значение выводится? Если входная строка действительна, маски не действуют.

Steve Jessop 06.01.2009 17:10

Результатом будет «* outit = (getHexValue (it) << 4) + getHexValue (it + 1);», на что я бы согласился.

Steve Jessop 06.01.2009 17:16

Я не возражаю против этого. Он общий (в определенных пределах), в нем используются константы, ссылки там, где это необходимо, и т. д. В нем отсутствует небольшая документация, а назначение asciihex*outit не совсем понятно на первый взгляд.

resize инициализирует ненужные элементы вывода (вместо этого используйте reserve).

Возможно, универсальность слишком гибкая: вы можете кормить алгоритмы любым типом данных, который вам нравится, в то время как вы должны давать ему только шестнадцатеричные числа (не, например, vector из double)

И действительно, это может быть немного перебор, учитывая наличие хороших библиотечных функций.

Код не будет работать, если вы используете резерв. Если out.size () равен 0 до того, как вы вызываете резерв, он все равно будет равен 0 после того, как вы вызовете резерв, поэтому циклы не будут выполняться. См. gotw.ca/gotw/074.htm

Joel 05.01.2009 20:59

Действительно, вам потребуются дополнительные приспособления. Я хотел сказать, что объекты создавались без надобности.

xtofl 07.01.2009 12:55

Что он должен делать? Общеизвестного общепринятого значения hexascii или asciihex не существует, поэтому названия следует изменить.

[редактировать] Преобразование из двоичной в шестнадцатеричную нотацию часто не следует называть ascii ..., поскольку ascii - это 7-битный формат.

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

SmacL 05.01.2009 15:59

Отредактировал вопрос, изменил двоичный массив в формат для печати и обратно.

Patrick 05.01.2009 16:31

Некоторые проблемы, которые я вижу:

Это будет отлично работать, если оно используется только для контейнера ввода, который хранит 8-битные типы - например, символ или беззнаковый символ. Например, следующий код завершится ошибкой при использовании с 32-битным типом, значение которого после сдвига вправо больше 15 - рекомендуется всегда использовать маску, чтобы индекс поиска всегда находился в пределах диапазона.

*outit++ = hexDigits[*it >> 4];

Каково ожидаемое поведение, если вы передадите контейнер, содержащий беззнаковые длинные числа - для того, чтобы это был общий класс, он, вероятно, также должен иметь возможность обрабатывать преобразование 32-битных чисел в шестнадцатеричные строки.

Это работает только тогда, когда ввод - это контейнер - что, если я просто хочу преобразовать один байт? Предлагается преобразовать код в базовую функцию, которая может скрыть один байт (шестнадцатеричный => ascii и ascii => шестнадцатеричный), а затем предоставить дополнительные функции для использования этой базовой функции для покрытия контейнеров байтов и т. д.

В asciihex () могут произойти неприятности, если размер входного контейнера не делится на 2. Использование:

it != in.end(); it += 2

опасен, поскольку, если размер контейнера не делится на 2, то увеличение на два продвинет итератор за конец контейнера, и сравнение с end () никогда не сработает. Это в некоторой степени защищено с помощью вызова assert, но assert может быть скомпилирован (например, он часто компилируется в сборках релиза), поэтому было бы намного лучше сделать это оператором if.

Утверждение во второй строке asciihex () проверяет, что размер ввода делится на 2, поэтому + = 2 в этом случае безопасен - я согласен, хотя это, по крайней мере, выглядит опасно, и я думаю, что я бы немного его закодировал по-другому я.

jrb 05.01.2009 16:11

@jrbushell, assert будет включен только в отладочные версии. Он не влияет на код выпуска и помогает при тестировании, а не во время выполнения.

SmacL 05.01.2009 16:20

assert не только проверяет код отладки в нашей среде сборки (хотя вы можете настроить его для этого)

Patrick 05.01.2009 16:27

Что случилось с

*outit = hexDigits[*it]

Почему эти две функции не могут использовать общий список hexDigits и устранить сложное (и медленное) вычисление символа ASCII?

Как связать 0x30 с 0x00? Я полагаю, вы могли бы использовать карту, но это кажется излишним.

Patrick 05.01.2009 16:28

Карта представляет собой тривиальный поиск по массиву. Будет всего несколько байтов (256 в наихудшем случае; 128 более реалистично). И он будет работать мгновенно, используя сложение и умножение, и ничего более.

S.Lott 05.01.2009 16:50
  • Код имеет утверждения утверждения вместо правильной обработки состояния ошибки (и если ваше утверждение отключено, код может взорваться)

  • цикл for имеет опасное двойное увеличение итератора (it + = 2). Особенно в случае, если ваше утверждение не сработало. Что происходит, когда ваш итератор уже находится в конце, а вы ++ это делаете?

  • Код является шаблоном, но вы просто преобразуете символы в числа или наоборот. Это карго культ программирование. Вы надеетесь, что использование шаблонов принесет вам плоды программирования. Вы даже отметили это как вопрос шаблона, хотя аспект шаблона совершенно не имеет отношения к вашим функциям.

  • строка * outit = слишком сложна.

  • код изобретает колесо. По большому счету.

Спасибо, Торстен, я надеялся, что ты это увидишь. Что касается культовой точки карго, код позволяет мне обрабатывать 2 контейнера из разных библиотек без необходимости писать 2 функции, которые отличаются только своими параметрами, одной из целей шаблонов. Все остальные контейнеры не рассматривал :(.

Patrick 05.01.2009 17:44

Я ценю ваши мысли о возможности взаимодействия между библиотеками. Но почему вы возитесь с символьными константами и непонятными значениями ASCII? Это код «на полпути между желобом и звездами». Он пытается быть элегантным, но не выполняет этого обещания. Здесь вы можете легко использовать строки C++.

Thorsten79 05.01.2009 18:47

Причина, по которой я считаю это игрушечным кодом, заключается в отсутствии проверки ошибок.

Я мог бы передать ему два вектора, и он с радостью попробовал бы что-нибудь сделать и устроить полный беспорядок, генерируя случайную тарабарщину.

Проблемы, которые я обнаруживаю:

hexascii не проверяет наличие sizeof(T2::value_type)==1

hexascii дважды разыменовывает it, а еще больше - asciihex. Для этого нет причин, так как вы можете сохранить результат. Это означает, что вы не можете использовать istream_iterator.

asciihex требует случайного итератора в качестве входных данных, потому что используются (it + 1) и (it + = 2). Алгоритм мог бы работать на прямом итераторе, если вы используете только (++ it).

(*it > '9' ? *it - 0x07 : *it) - 0x30 можно упростить до *it - (*it > '9' ? 0x37 : 0x30), так что остается только одно безусловное вычитание. Тем не менее поиск по массиву был бы более эффективным. Вычтите 0x30. «0» станет 0; «A» станет 0x11, а «a» станет 0x31. Замаскируйте с 0x1f, чтобы сделать регистр нечувствительным, и вы можете выполнить поиск в char [0x20] без риска переполнения. Не-шестнадцатеричные символы просто дадут вам странные значения.

Другие вопросы по теме