Функции C++ для преобразования последовательности байтов в строковое представление приводят к мусорному выводу

Я написал несколько простых функций C++ для преобразования последовательностей байтов в строковые представления.

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

#include <iostream>
#include <string>
#include <vector>

using std::vector;
typedef vector<uint8_t> bytes;
using std::string;
using std::cout;
using namespace std::literals;

string DIGITS = "0123456789abcdef"s;

static inline string hexlify(bytes arr) {
    string repr = ""s;
    for (auto& chr : arr) {
        repr += " " + DIGITS[(chr & 240) >> 4] + DIGITS[chr & 15];
    }
    repr.erase(0, 1);
    return repr;
}

bytes text = {
    84, 111, 32, 98, 101, 32,
    111, 114, 32, 110, 111, 116,
    32, 116, 111, 32, 98, 101
}; // To be or not to be

int main() {
    cout << hexlify(text);
}
2♠
÷82♠
÷82♠
÷82♠
÷

Почему это происходит?

Я знаю, что моя логика верна, вот прямой перевод на Python:

digits = "0123456789abcdef"
def bytes_string(data):
    s = ""
    for i in data:
        s += " " + digits[(i & 240) >> 4] + digits[i & 15]
    return s[1:]

И это работает:

>>> bytes_string(b"To be or not to be")
'54 6f 20 62 65 20 6f 72 20 6e 6f 74 20 74 6f 20 62 65'

Но почему это не работает в C++?

Я использую Visual Studio 2022 V17.9.7, флаги компилятора:

/permissive- /ifcOutput "hexlify_test\x64\Release\" /GS /GL /W3 /Gy /Zc:wchar_t /Zi /Gm- /O2 /sdl /Fd"hexlify_test\x64\Release\vc143.pdb" /Zc:inline /fp:precise /D "NDEBUG" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /errorReport:prompt /WX- /Zc:forScope /std:c17 /Gd /Oi /MD /std:c++20 /FC /Fa"hexlify_test\x64\Release\" /EHsc /nologo /Fo"hexlify_test\x64\Release\" /Ot /Fp"hexlify_test\x64\Release\hexlify_test.pch" /diagnostics:column 

Я только что узнал, что вывод мусора происходит только в режиме отладки после реализации исправления. Я нацелился на C++ 20 в режиме отладки, каким-то образом код вызывает вывод мусора в режиме отладки, переключение в режим выпуска устраняет проблему. До того, как исправление было реализовано, я скомпилировал в режиме выпуска, и возникла эта проблема.

Что такое отладчик?.
PaulMcKenzie 23.05.2024 13:35

следующее является прямым переводом на Python: -- C++ — это не Python. Использование Python (или любого другого языка) в качестве модели при написании C++ приведет к 1) появлению программ с ошибками, 2) неэффективным программам и 3) программам, которые кажутся программисту C++ странными. Показательный пример: repr += " " + DIGITS[(chr & 240) >> 4] + DIGITS[chr & 15]; — что вы ожидали от этой строки кода C++? Ожидаете ли вы, что + выполнит конкатенацию?

PaulMcKenzie 23.05.2024 13:42

Некоторые компиляторы предупреждают вас wandbox.org/permlink/4o56Uv8t5HUALExE

StoryTeller - Unslander Monica 23.05.2024 13:45

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

PaulMcKenzie 23.05.2024 13:58

Может быть, вы не пересобирали приложение и вместо этого используете ту же глючную версию?

PaulMcKenzie 23.05.2024 14:04
Стоит ли изучать 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
6
99
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

Как отмечалось в комментариях (также здесь), проблема здесь в конкатенации строк или около нее. Следующий код не выполняет конкатенацию:

" " + DIGITS[(chr & 240) >> 4]

Когда вы извлекаете символ из строки DIGTS, он имеет тип char — специальный тип для отдельных символов. По историческим причинам (совместимость с C) оператор + интерпретирует строковый литерал " " как указатель, а цифровой символ как целое число и выполняет некоторые бесполезные арифметические операции с указателями.

Чтобы выполнить конкатенацию, используйте строковый литерал типа std::string, как вы это делали в другом месте вашего кода:

" "s + DIGITS[(chr & 240) >> 4]

Здесь operator+ встречает правильные типы std::string и char, поэтому он работает правильно.


Правильная идиома в C++ для объединения строк — это строковый поток.

#include <sstream>
...
std::ostringstream stream; // "output string stream"
stream << " " << DIGITS[...] << DIGITS[...];
...
return stream.str();

Класс stringstream оптимизирован для постепенного построения строк. После того, как код завершает все конкатенации, он преобразует поток в обычный тип std::string, который является универсальным.

В дополнение к принятому ответу МОЖНО использовать вашу логику, но синтаксис не является Pythonic. Во-первых, байт всегда представляет собой две цифры в шестнадцатеричном формате, поэтому, зная длину исходной строки, вы знаете длину результата. Лучше предварительно выделить вместо вывода отдельных символов для потоковой передачи или объединения строк, а затем использовать строку как массив, что-то вроде:

std::string hexify(const bytes& buf) {
    std::string result;
    auto length = buf.size()*2;
    result.resize(length);
    for(int i = 0, j = 0; i < length; i++, j++) {
        auto c = buf[i];
        result[j++] = (c & 0xF)["0123456789abcdef"];
        result[j]   = (c>>4)["0123456789abcdef"];
    }
    return result;
}

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

P.S. (c & 0xF)["0123456789abcdef"] то же самое, что "0123456789abcdef"[c & 0xF], но некоторые компиляторы чаще обнаруживают константу внутри скобок.

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