¿Cómo se construye un std :: string con un nulo incrustado?

88

Si quiero construir un std :: string con una línea como:

std::string my_string("a\0b");

Donde quiero tener tres caracteres en la cadena resultante (a, nulo, b), solo obtengo uno. ¿Cuál es la sintaxis adecuada?

Cuenta
fuente
4
Tendrás que tener cuidado con esto. Si reemplaza 'b' con cualquier carácter numérico, creará silenciosamente la cadena incorrecta. Ver: stackoverflow.com/questions/10220401/…
David Stone

Respuestas:

128

Desde C ++ 14

hemos podido crear literal std::string

#include <iostream>
#include <string>

int main()
{
    using namespace std::string_literals;

    std::string s = "pl-\0-op"s;    // <- Notice the "s" at the end
                                    // This is a std::string literal not
                                    // a C-String literal.
    std::cout << s << "\n";
}

Antes de C ++ 14

El problema es que el std::stringconstructor que toma const char*asume que la entrada es una C-string. Las cadenas C \0terminan y, por lo tanto, el análisis se detiene cuando llega al \0carácter.

Para compensar esto, necesita usar el constructor que construye la cadena a partir de una matriz de caracteres (no una C-String). Esto toma dos parámetros: un puntero a la matriz y una longitud:

std::string   x("pq\0rs");   // Two characters because input assumed to be C-String
std::string   x("pq\0rs",5); // 5 Characters as the input is now a char array with 5 characters.

Nota: C ++ NOstd::string está terminado (como se sugiere en otras publicaciones). Sin embargo, puede extraer un puntero a un búfer interno que contiene una C-String con el método . \0c_str()

También consulte la respuesta de Doug T a continuación sobre el uso de a vector<char>.

Consulte también RiaD para obtener una solución C ++ 14.

Martin York
fuente
7
actualización: a partir de c ++ 11, las cadenas terminan en nulo. Dicho esto, la publicación de Loki sigue siendo válida.
matthewaveryusa
14
@mna: tienen terminación nula en términos de almacenamiento, pero no en el sentido de que tienen terminación nula con terminación nula significativa (es decir, con semántica que define la longitud de la cadena), que es el significado habitual del término.
Lightness Races in Orbit
Bien explicado. Gracias.
Joma
22

Si está haciendo manipulación como lo haría con una cadena de estilo c (matriz de caracteres), considere usar

std::vector<char>

Tiene más libertad para tratarlo como una matriz de la misma manera que trataría una cadena c. Puede usar copy () para copiar en una cadena:

std::vector<char> vec(100)
strncpy(&vec[0], "blah blah blah", 100);
std::string vecAsStr( vec.begin(), vec.end());

y puedes usarlo en muchos de los mismos lugares donde puedes usar c-strings

printf("%s" &vec[0])
vec[10] = '\0';
vec[11] = 'b';

Sin embargo, naturalmente, sufre los mismos problemas que las cuerdas c. Puede olvidar su terminal nulo o escribir más allá del espacio asignado.

Doug T.
fuente
Si dice que está tratando de codificar bytes en una cadena (los bytes grpc se almacenan como una cadena), use el método vectorial como se especifica en la respuesta; no de la forma habitual (ver más abajo) que NO construirá la cadena completa byte *bytes = new byte[dataSize]; std::memcpy(bytes, image.data, dataSize * sizeof(byte)); std::string test(reinterpret_cast<char *>(bytes)); std::cout << "Encoded String length " << test.length() << std::endl;
Alex Punnen
13

No tengo idea de por qué querrías hacer tal cosa, pero prueba esto:

std::string my_string("a\0b", 3);
17 de 26
fuente
1
¿Cuáles son sus preocupaciones al hacer esto? ¿Está cuestionando la necesidad de almacenar "a \ 0b" alguna vez? o cuestionando el uso de un std :: string para tal almacenamiento? Si es lo último, ¿qué sugiere como alternativa?
Anthony Cramp
3
@Constantin, entonces estás haciendo algo mal si estás almacenando datos binarios como una cadena. Para eso se inventaron vector<unsigned char>o unsigned char *.
Mahmoud Al-Qudsi
2
Me encontré con esto mientras intentaba aprender más sobre la seguridad de las cadenas. Quería probar mi código para asegurarme de que todavía funciona incluso si lee un carácter nulo mientras lee de un archivo / red lo que espera que sean datos textuales. Utilizo std::stringpara indicar que los datos deben considerarse como texto sin formato, pero estoy haciendo un trabajo de hash y quiero asegurarme de que todo funcione con los caracteres nulos involucrados. Eso parece un uso válido de una cadena literal con un carácter nulo incrustado.
David Stone
3
@DuckMaestro No, eso no es cierto. Un \0byte en una cadena UTF-8 solo puede ser NUL. Un carácter codificado de varios bytes nunca contendrá, ni \0ningún otro carácter ASCII para el caso.
John Kugelman
1
Me encontré con esto al intentar provocar un algoritmo en un caso de prueba. Entonces hay razones válidas; aunque pocos.
namezero
12

¿Qué nuevas capacidades agregan los literales definidos por el usuario a C ++? presenta una respuesta elegante: Definir

std::string operator "" _s(const char* str, size_t n) 
{ 
    return std::string(str, n); 
}

entonces puedes crear tu cadena de esta manera:

std::string my_string("a\0b"_s);

o aun así:

auto my_string = "a\0b"_s;

Hay una forma de "estilo antiguo":

#define S(s) s, sizeof s - 1 // trailing NUL does not belong to the string

entonces puedes definir

std::string my_string(S("a\0b"));
seudónimo
fuente
8

Lo siguiente funcionará ...

std::string s;
s.push_back('a');
s.push_back('\0');
s.push_back('b');
Andrew Stein
fuente
Debe utilizar paréntesis en lugar de los corchetes.
jk.
5

Tendrás que tener cuidado con esto. Si reemplaza 'b' con cualquier carácter numérico, creará silenciosamente la cadena incorrecta utilizando la mayoría de los métodos. Consulte: Reglas para caracteres de escape de literales de cadena de C ++ .

Por ejemplo, dejé caer este fragmento de aspecto inocente en medio de un programa

// Create '\0' followed by '0' 40 times ;)
std::string str("\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00", 80);
std::cerr << "Entering loop.\n";
for (char & c : str) {
    std::cerr << c;
    // 'Q' is way cooler than '\0' or '0'
    c = 'Q';
}
std::cerr << "\n";
for (char & c : str) {
    std::cerr << c;
}
std::cerr << "\n";

Esto es lo que me genera este programa:

Entering loop.
Entering loop.

vector::_M_emplace_ba
QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ

Esa fue mi primera declaración impresa dos veces, varios caracteres que no se imprimen, seguidos de una línea nueva, seguida de algo en la memoria interna, que simplemente sobrescribí (y luego imprimí, mostrando que se ha sobrescrito). Lo peor de todo, incluso compilar esto con advertencias de gcc detalladas y detalladas no me dio ninguna indicación de que algo estuviera mal, y ejecutar el programa a través de valgrind no se quejó de ningún patrón de acceso a la memoria incorrecto. En otras palabras, es completamente indetectable por las herramientas modernas.

Puede obtener este mismo problema con el mucho más simple std::string("0", 100);, pero el ejemplo anterior es un poco más complicado y, por lo tanto, más difícil de ver qué está mal.

Afortunadamente, C ++ 11 nos da una buena solución al problema usando la sintaxis de la lista de inicializadores. Esto le evita tener que especificar la cantidad de caracteres (que, como mostré anteriormente, puede hacerlo incorrectamente) y evita combinar números de escape. std::string str({'a', '\0', 'b'})es seguro para cualquier contenido de cadena, a diferencia de las versiones que toman una variedad chary un tamaño.

David Stone
fuente
2
Como parte de mi preparación para esta publicación, envié un informe de error a gcc con la esperanza de que agreguen una advertencia para hacerlo un poco más seguro: gcc.gnu.org/bugzilla/show_bug.cgi?id=54924
David Stone
4

En C ++ 14 ahora puede usar literales

using namespace std::literals::string_literals;
std::string s = "a\0b"s;
std::cout << s.size(); // 3
RiaD
fuente
1
y la segunda línea se puede escribir alternativamente, mejor en mi humilde opinión, comoauto s{"a\0b"s};
underscore_d
Buena respuesta Gracias.
Joma
1

Es mejor usar std :: vector <char> si esta pregunta no es solo para fines educativos.

Harold Ekstrom
fuente
1

La respuesta de anonym es excelente, pero también hay una solución no macro en C ++ 98:

template <size_t N>
std::string RawString(const char (&ch)[N])
{
  return std::string(ch, N-1);  // Again, exclude trailing `null`
}

Con esta función, RawString(/* literal */)producirá la misma cadena que S(/* literal */):

std::string my_string_t(RawString("a\0b"));
std::string my_string_m(S("a\0b"));
std::cout << "Using template: " << my_string_t << std::endl;
std::cout << "Using macro: " << my_string_m << std::endl;

Además, hay un problema con la macro: la expresión no es en realidad std::stringtal como está escrita y, por lo tanto, no se puede usar, por ejemplo, para una inicialización de asignación simple:

std::string s = S("a\0b"); // ERROR!

... por lo que sería preferible utilizar:

#define std::string(s, sizeof s - 1)

Obviamente, solo debe usar una u otra solución en su proyecto y llamarla como crea apropiado.

Kyle Strand
fuente
-5

Sé que hace mucho que se hace esta pregunta. Pero para cualquiera que tenga un problema similar, podría estar interesado en el siguiente código.

CComBSTR(20,"mystring1\0mystring2\0")
Dil09
fuente
Esta respuesta es demasiado específica para las plataformas de Microsoft y no aborda la pregunta original (que preguntaba sobre std :: string).
Junio ​​Rodas
-8

Casi todas las implementaciones de std :: strings tienen terminación nula, por lo que probablemente no debería hacer esto. Tenga en cuenta que "a \ 0b" tiene en realidad cuatro caracteres debido al terminador nulo automático (a, nulo, b, nulo). Si realmente desea hacer esto y romper el contrato de std :: string, puede hacer:

std::string s("aab");
s.at(1) = '\0';

pero si lo haces, todos tus amigos se reirán de ti, nunca encontrarás la verdadera felicidad.

Jurney
fuente
1
std :: string NO es necesario para terminar en NULL.
Martin York
2
No es obligatorio, pero en casi todas las implementaciones, probablemente se deba a la necesidad de que el descriptor de acceso c_str () le proporcione el equivalente terminado en nulo.
Jurney
2
Para mayor eficiencia, se puede mantener un carácter nulo en la parte posterior del búfer de datos. Pero ninguna de las operaciones (es decir, métodos) en una cadena utiliza este conocimiento o se ve afectada por una cadena que contiene un carácter NULL. El carácter NULL se manipulará exactamente de la misma forma que cualquier otro carácter.
Martin York
Es por eso que es tan gracioso que la cadena sea std :: - su comportamiento no está definido en NINGUNA plataforma.
Ojalá el usuario595447 estuviera todavía aquí para poder preguntarles de qué demonios creían que estaban hablando.
underscore_d