Я написал несколько простых функций 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 в режиме отладки, каким-то образом код вызывает вывод мусора в режиме отладки, переключение в режим выпуска устраняет проблему. До того, как исправление было реализовано, я скомпилировал в режиме выпуска, и возникла эта проблема.
следующее является прямым переводом на Python: -- C++ — это не Python. Использование Python (или любого другого языка) в качестве модели при написании C++ приведет к 1) появлению программ с ошибками, 2) неэффективным программам и 3) программам, которые кажутся программисту C++ странными. Показательный пример: repr += " " + DIGITS[(chr & 240) >> 4] + DIGITS[chr & 15]; — что вы ожидали от этой строки кода C++? Ожидаете ли вы, что + выполнит конкатенацию?
Некоторые компиляторы предупреждают вас wandbox.org/permlink/4o56Uv8t5HUALExE
проблема не решена -- Невозможно дублировать Даже в этом случае код будет более эффективным, если вы печатаете пробел последним, а не первым. Затем в конце цикла простой repr.pop_back() удаляет лишнее пространство в конце. Вызов erase для первого элемента неэффективен, поскольку всю строку приходится «смещать влево» на один пробел.
Может быть, вы не пересобирали приложение и вместо этого используете ту же глючную версию?
Дюп: C++ добавление строкового литерала к символьному литералу





Как отмечалось в комментариях (также здесь), проблема здесь в конкатенации строк или около нее. Следующий код не выполняет конкатенацию:
" " + 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], но некоторые компиляторы чаще обнаруживают константу внутри скобок.