Escrevi várias funções C++ simples para converter sequências de bytes em representações de strings.
Foi bem simples, tenho certeza que minha lógica está certa, achei extremamente fácil, até que comecei a imprimir as strings e descobri que a saída era um lixo:
#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♠
÷
Por que isso está acontecendo?
Eu sei que minha lógica está certa, o seguinte é a tradução direta para Python:
digits = "0123456789abcdef"
def bytes_string(data):
s = ""
for i in data:
s += " " + digits[(i & 240) >> 4] + digits[i & 15]
return s[1:]
E funciona:
>>> 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'
Mas por que não funciona em C++?
Estou usando o Visual Studio 2022 V17.9.7, sinalizadores do compilador:
/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
Acabei de descobrir que a saída de lixo só ocorre no modo de depuração após a implementação da correção, mirei o C++ 20 no modo de depuração, de alguma forma o código causa saída de lixo no modo de depuração, mudar para o modo de liberação corrige o problema. Antes da correção ser implementada eu compilei no modo de lançamento e houve esse problema.
Conforme observado nos comentários (também aqui ), o problema aqui está na concatenação de strings ou próximo a ela. O código a seguir não faz concatenação:
Quando você extrai um caractere da string
DIGTS
, ele possui typechar
— um tipo dedicado para caracteres únicos. Por razões históricas (compatibilidade com C), o+
operador interpreta a string literal" "
como um ponteiro e o caractere de dígito como um número inteiro, e faz algumas aritméticas de ponteiro inúteis.Para fazer a concatenação, use uma string literal do tipo
std::string
, como você fez em outras partes do seu código:Aqui,
operator+
encontra os tipos corretosstd::string
echar
, por isso funciona corretamente.O idioma adequado em C++ para fazer concatenação de strings é um fluxo de strings.
A
stringstream
classe é otimizada para construção incremental de strings. Após o código finalizar todas as concatenações, ele converte o stream em umstd::string
tipo regular, que é de uso geral.Além da resposta aceita, é possível usar sua lógica, mas a sintaxe não é Pythonic. Primeiro, um byte sempre tem dois dígitos em hexadecimal, portanto, conhecendo o comprimento da string original, você sabe o comprimento do resultado. É melhor pré-alocar em vez de gerar caracteres individuais para transmitir ou concatenar string e, em seguida, usar uma string como uma matriz, algo assim:
Existem algumas suposições ruins com matemática e elas podem ser otimizadas, mas é fácil ver como as "melhores práticas" em um idioma nativo são diferentes do intérprete que pré-aloca tudo e faz todo o trabalho "sujo" para você.
PS
(c & 0xF)["0123456789abcdef"]
é o mesmo,"0123456789abcdef"[c & 0xF]
mas alguns compiladores identificam com mais frequência uma constante entre colchetes.