c++ - resueltos - leer cadena de caracteres en c
cadena de caracteres, cadena y char*confusión de conversión (5)
Mi pregunta se puede stringstream.str().c_str()
en: ¿dónde está la cadena retornada de stringstream.str().c_str()
en vivo en la memoria, y por qué no se puede asignar a un const char*
?
Este ejemplo de código lo explicará mejor que yo
#include <string>
#include <sstream>
#include <iostream>
using namespace std;
int main()
{
stringstream ss("this is a string/n");
string str(ss.str());
const char* cstr1 = str.c_str();
const char* cstr2 = ss.str().c_str();
cout << cstr1 // Prints correctly
<< cstr2; // ERROR, prints out garbage
system("PAUSE");
return 0;
}
La suposición de que stringstream.str().c_str()
podría asignarse a un const char*
condujo a un error que me llevó un tiempo rastrear.
Para obtener puntos de bonificación, ¿alguien puede explicar por qué se reemplazó la declaración cout
con
cout << cstr // Prints correctly
<< ss.str().c_str() // Prints correctly
<< cstr2; // Prints correctly (???)
imprime las cuerdas correctamente?
Estoy compilando en Visual Studio 2008.
El ss.str()
temporal se destruye después de que se completa la inicialización de cstr2
. Entonces, cuando lo imprimes con cout
, la cadena que se asoció con ese std::string
temporal ha sido desestimada hace tiempo, y por lo tanto tendrás suerte si falla y afirma, y no tiene suerte si imprime basura o si parece trabajo.
const char* cstr2 = ss.str().c_str();
Sin embargo, la cadena cstr1
apunta cstr1
está asociada con una cadena que aún existe en el momento en que se realiza el cout
- para que imprima correctamente el resultado.
En el siguiente código, el primer cstr
es correcto (supongo que es cstr1
en el código real?). El segundo imprime la cadena c asociada con el objeto de cadena temporal ss.str()
. El objeto se destruye al final de la evaluación de la expresión completa en la que aparece. La expresión completa es toda la expresión cout << ...
modo que mientras se emite la cadena c, el objeto cadena asociado todavía existe. Para cstr2
, es pura maldad que tiene éxito. Es muy posible que internamente elija la misma ubicación de almacenamiento para el nuevo temporal que ya eligió para el temporal utilizado para inicializar cstr2
. Podría también colapsar.
cout << cstr // Prints correctly
<< ss.str().c_str() // Prints correctly
<< cstr2; // Prints correctly (???)
El retorno de c_str()
generalmente solo apunta al buffer de cadena interno, pero eso no es un requisito. La cadena podría formar un búfer si su implementación interna no es contigua, por ejemplo (eso es muy posible, pero en el próximo estándar de C ++, las cadenas deben almacenarse contiguamente).
En GCC, las cadenas usan recuento de referencias y copy-on-write. Por lo tanto, encontrará que lo siguiente es verdadero (lo hace, al menos en mi versión de GCC)
string a = "hello";
string b(a);
assert(a.c_str() == b.c_str());
Las dos cadenas comparten el mismo búfer aquí. En el momento en que cambie uno de ellos, se copiará el búfer y cada uno tendrá su copia por separado. Sin embargo, otras implementaciones de cadenas hacen cosas diferentes.
El objeto std :: string devuelto por ss.str () es un objeto temporal que tendrá un tiempo de vida limitado a la expresión. Por lo tanto, no puede asignar un puntero a un objeto temporal sin obtener basura.
Ahora, hay una excepción: si usa una referencia constante para obtener el objeto temporal, es legal usarlo por un tiempo de vida más amplio. Por ejemplo, debes hacer:
#include <string>
#include <sstream>
#include <iostream>
using namespace std;
int main()
{
stringstream ss("this is a string/n");
string str(ss.str());
const char* cstr1 = str.c_str();
const std::string& resultstr = ss.str();
const char* cstr2 = resultstr.c_str();
cout << cstr1 // Prints correctly
<< cstr2; // No more error : cstr2 points to resultstr memory that is still alive as we used the const reference to keep it for a time.
system("PAUSE");
return 0;
}
De esa forma obtienes la cadena por más tiempo.
Ahora, debes saber que hay un tipo de optimización llamada RVO que dice que si el compilador ve una inicialización a través de una llamada a función y esa función devuelve una temporal, no hará la copia sino que simplemente hará que el valor asignado sea el temporal . De esta forma, no necesita usar una referencia, solo si quiere asegurarse de que no se copie que sea necesaria. Así que haciendo:
std::string resultstr = ss.str();
const char* cstr2 = resultstr.c_str();
sería mejor y más simple.
En esta línea:
const char* cstr2 = ss.str().c_str();
ss.str()
hará una copia de los contenidos del stringstream. Cuando llame a c_str()
en la misma línea, estará haciendo referencia a datos legítimos, pero después de esa línea, la cadena se destruirá, dejando su char*
para apuntar a la memoria sin propietario.
Lo que estás haciendo es crear un temporal. Ese temporal existe en un ámbito determinado por el compilador, de modo que sea lo suficientemente largo como para satisfacer los requisitos de hacia dónde se dirige.
Tan pronto como la declaración const char* cstr2 = ss.str().c_str();
está completo, el compilador no ve razón para mantener la secuencia temporal alrededor, y se destruye, y así su const char *
está apuntando a la memoria libre.
Su instrucción string str(ss.str());
significa que el temporal se usa en el constructor para la variable de string
str
que has puesto en la pila local, y que permanece todo el tiempo que esperas: hasta el final del bloque o la función que has escrito. Por lo tanto, el const char *
dentro sigue siendo buena memoria cuando intentas el cout
.
stringstream.str()
devuelve un objeto de cadena temporal que se destruye al final de la expresión completa. Si obtiene un puntero a una cadena C de eso ( stringstream.str().c_str()
), apuntará a una cadena que se elimina donde termina la instrucción. Es por eso que tu código imprime basura.
Podrías copiar ese objeto de cadena temporal a algún otro objeto de cadena y tomar la cadena C de ese:
const std::string tmp = stringstream.str();
const char* cstr = tmp.c_str();
Tenga en cuenta que hice la const
cadena temporal, porque cualquier cambio en ella podría ocasionar que se vuelva a asignar y, por lo tanto, haga que cstr
no cstr
válido. Por lo tanto, es más seguro no almacenar el resultado de la llamada a str()
en absoluto y usar cstr
solo hasta el final de la expresión completa:
use_c_str( stringstream.str().c_str() );
Por supuesto, este último podría no ser fácil y copiar podría ser demasiado costoso. Lo que puede hacer en su lugar es vincular el temporal a una referencia const
. Esto extenderá su vida útil a la duración de la referencia:
{
const std::string& tmp = stringstream.str();
const char* cstr = tmp.c_str();
}
IMO esa es la mejor solución. Desafortunadamente no es muy conocido.