recursivo - ¿Puedo usar con seguridad std:: string para datos binarios en C++ 11?
binario a decimal recursivo c++ (3)
Hay varias publicaciones en Internet que sugieren que debes usar std::vector<unsigned char>
o algo similar para datos binarios.
Pero prefiero una variante std::basic_string
para eso, ya que proporciona muchas funciones de manipulación de cadenas convenientes. Y AFAIK, desde C ++ 11, el estándar garantiza lo que ya hizo cada implementación conocida de C ++ 03: que std::basic_string
almacena sus contenidos de forma contigua en la memoria.
A primera vista, entonces, std::basic_string<unsigned char>
puede ser una buena opción.
No quiero usar std::basic_string<unsigned char>
, sin embargo, porque casi todas las funciones del sistema operativo solo aceptan char*
, lo que hace que sea necesario realizar una std::basic_string<unsigned char>
explícita. Además, los literales de cadena son const char*
, por lo que necesitaría una conversión explícita a const unsigned char*
cada vez que asigno un literal de cadena a mi cadena binaria, lo que también me gustaría evitar. Además, las funciones para leer y escribir en archivos o búferes de red aceptan de forma similar los punteros char*
y const char*
.
Esto deja std::string
, que es básicamente un typedef para std::basic_string<char>
.
El único problema potencial que queda (que puedo ver) con el uso de std::string
para datos binarios es que std::string
usa char
(que se puede firmar).
char
, signed char
y unsigned char
son tres tipos diferentes y char
puede ser unsigned o firm.
Entonces, cuando se devuelve un valor de byte real de 11111111b
desde std::string:operator[]
como char, y desea verificar su valor, su valor puede ser 255
(si char
no está firmado) o podría ser "algo negativo "(si el char
está firmado, según la representación de su número).
De manera similar, si desea agregar explícitamente el valor real del byte 11111111b
a un std::string
, simplemente agregando (char) (255)
podría definirse la implementación (e incluso emitir una señal) si char
está firmado y la conversación int
to char
resulta en un desbordamiento.
Entonces, ¿hay una forma segura de evitar esto, que hace que std::string
binary-safe otra vez?
§3.10 / 15 estados:
Si un programa intenta acceder al valor almacenado de un objeto a través de un glvalue distinto de uno de los siguientes tipos, el comportamiento no está definido:
- [...]
- un tipo que es el tipo con signo o sin signo correspondiente al tipo dinámico del objeto,
- [...]
- un tipo char o unsigned char.
Lo cual, si lo comprendo correctamente, parece permitir el uso de un puntero de unsigned char*
para acceder y manipular el contenido de una std::string
y hace que esto también esté bien definido . Simplemente reinterpreta el patrón de bits como un unsigned char
sin unsigned char
, sin ningún cambio ni pérdida de información; esto último se debe a que todos los bits en un char
, signed char
y unsigned char
deben utilizarse para la representación de valor.
Luego podría usar esta interpretación unsigned char*
del contenido de std::string
como un medio para acceder y cambiar los valores de byte en el rango [0, 255]
, de una manera bien definida y portátil, independientemente de la firmeza de char
sí.
Esto debería resolver cualquier problema que surja de un char
potencialmente firmado.
¿Mis suposiciones y conclusiones son correctas?
Además, ¿se garantiza que la interpretación unsigned char*
del mismo patrón de bits (es decir, 11111111b
o 10101010b
) sea la misma en todas las implementaciones? Dicho de otra manera, ¿el estándar garantiza que "mirando a través de los ojos de un unsigned char
", el mismo patrón de bits siempre lleva al mismo valor numérico (suponiendo que el número de bits en un byte es el mismo)?
Por lo tanto, ¿puedo de forma segura (es decir, sin ningún comportamiento indefinido o definido por la implementación) usar std::string
para almacenar y manipular datos binarios en C ++ 11?
La conversión static_cast<char>(uc)
donde uc
es de tipo is unsigned char
siempre es válida: según 3.9.1 [basic.fundamental] la representación de char
, signed char
, y unsigned char
son idénticas a char
como idéntica a una de los otros dos tipos:
Los objetos declarados como caracteres (caracteres) deben ser lo suficientemente grandes para almacenar cualquier miembro del conjunto de caracteres básico de la implementación. Si un carácter de este conjunto se almacena en un objeto de carácter, el valor integral de ese objeto de carácter es igual al valor de la forma literal de un solo carácter de ese carácter. Está definido por la implementación si un objeto char puede contener valores negativos. Los caracteres pueden ser declarados explícitamente sin firmar o firmados. Los caracteres simples, los caracteres con signo y los caracteres sin signo son tres tipos distintos, denominados colectivamente tipos de caracteres estrechos. Un char, un char firmado y un char no firmado ocupan la misma cantidad de almacenamiento y tienen los mismos requisitos de alineación (3.11); Es decir, tienen la misma representación de objeto. Para los tipos de caracteres estrechos, todos los bits de la representación del objeto participan en la representación del valor. Para tipos de caracteres estrechos sin signo, todos los patrones de bits posibles de la representación del valor representan números. Estos requisitos no son válidos para otros tipos. En cualquier implementación particular, un objeto char simple puede tomar los mismos valores que un char firmado o un char unsigned; cuál es la implementación definida.
La conversión de valores fuera del rango de caracteres unsigned char
a caracteres, por supuesto, será problemática y puede causar un comportamiento indefinido. Es decir, siempre que no intentes almacenar valores divertidos en la std::string
estarás bien. Con respecto a los patrones de bits, puede confiar en que el bit n
traduce a 2 n
. No debería haber un problema para almacenar datos binarios en una std::string
cuando se procesa con cuidado.
Dicho esto, no compro en su premisa: el procesamiento de datos binarios en su mayoría requiere el manejo de bytes que se manipulan mejor utilizando valores unsigned
. Los pocos casos en los que necesitaría convertir entre char*
y unsigned char*
crearán errores convenientes cuando no se traten explícitamente mientras se estropea el uso de char
accidental. Es decir, tratar con caracteres unsigned char
evitará errores. Tampoco creo que obtengas todas esas funciones de cadena agradables: por un lado, generalmente estás mejor usando los algoritmos de todos modos, pero también los datos binarios no son datos de cadena. En resumen: ¡la recomendación para std::vector<unsigned char>
no está saliendo del aire! ¡Es deliberado evitar la construcción difícil de encontrar trampas en el diseño!
El único argumento ligeramente razonable a favor del uso de char
podría ser el de los literales de cadena, pero incluso eso no contiene agua con los literales de cadena definidos por el usuario introducidos en C ++ 11:
#include <cstddef>
unsigned char const* operator""_u (char const* s, size_t)
{
return reinterpret_cast<unsigned char const*>(s);
}
unsigned char const* hello = "hello"_u;
Me he encontrado con problemas al usar std :: string para manejar datos binarios en Microsoft Visual Studio. He visto cómo las cuerdas se truncan inexplicablemente, por lo que no haría esto a pesar de lo que dicen los documentos de estándares.
Sí, tus suposiciones son correctas. Almacene datos binarios como una secuencia de caracteres sin signo en std :: string.