137 votes

Confusion dans la conversion de stringstream, string et char* en C++

Ma question peut être résumée ainsi : où la chaîne de caractères retournée par stringstream.str().c_str() vivre dans la mémoire, et pourquoi ne peut-elle pas être assignée à une const char* ?

Cet exemple de code l'explique mieux que je ne peux le faire.

#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;
}

L'hypothèse selon laquelle stringstream.str().c_str() pourrait être attribué à un const char* a conduit à un bug qui m'a pris un certain temps pour le retrouver.

Pour les points bonus, quelqu'un peut-il expliquer pourquoi le remplacement du cout déclaration avec

cout << cstr            // Prints correctly
    << ss.str().c_str() // Prints correctly
    << cstr2;           // Prints correctly (???)

imprime les chaînes de caractères correctement ?

Je compile avec Visual Studio 2008.

196voto

sbi Points 100828

stringstream.str() renvoie un objet chaîne temporaire qui est détruit à la fin de l'expression complète. Si vous obtenez un pointeur vers une chaîne de caractères C à partir de cette expression ( stringstream.str().c_str() ), il pointera vers une chaîne de caractères qui sera supprimée à la fin de l'instruction. C'est pourquoi votre code imprime des déchets.

Il faut donc soit copier cette chaîne vers une autre chaîne et prendre la chaîne C dans celle-ci :

const std::string tmp = stringstream.str();
const char* cstr = tmp.c_str();

Notez que j'ai fait la chaîne temporaire const car toute modification de celui-ci pourrait entraîner une réallocation et ainsi rendre cstr invalide. Il est donc plus sûr de ne pas stocker du tout le résultat de et d'utiliser cstr seulement jusqu'à la fin de l'expression complète :

use_c_str( stringstream.str().c_str() );

Bien entendu, cette dernière solution n'est pas toujours facile à mettre en œuvre et la copie peut s'avérer trop coûteuse. Ce que vous pouvez faire à la place est de lier le temporaire à un fichier const référence. Cela permettra d'étendre sa durée de vie à celle de la référence :

{
  const std::string& tmp = stringstream.str();
  const char* cstr = tmp.c_str();
}

IMO c'est la meilleure solution. Malheureusement, elle n'est pas très connue.

13voto

Jared Oberhaus Points 8877

Ce que vous faites, c'est créer un temporaire. Ce temporaire existe dans une portée déterminée par le compilateur, de telle sorte qu'il est suffisamment long pour satisfaire les exigences de l'endroit où il va.

Dès que la déclaration const char* cstr2 = ss.str().c_str(); est terminée, le compilateur ne voit aucune raison de conserver la chaîne temporaire, et celle-ci est détruite, et donc votre fichier const char * pointe vers la mémoire libre.

Votre déclaration string str(ss.str()); signifie que le temporaire est utilisé dans le constructeur de l'objet string variable str que vous avez placé sur la pile locale, et qui reste en place aussi longtemps que vous le souhaitez : jusqu'à la fin du bloc ou de la fonction que vous avez écrite. Par conséquent, le const char * au sein est toujours un bon souvenir lorsque vous essayez le cout .

6voto

fbrereto Points 21711

Dans cette ligne :

const char* cstr2 = ss.str().c_str();

ss.str() fera un copie du contenu du stringstream. Lorsque vous appelez c_str() sur la même ligne, vous ferez référence à des données légitimes, mais après cette ligne, la chaîne de caractères sera détruite, laissant votre fichier char* pour pointer vers une mémoire non possédée.

5voto

El ss.str() temporaire est détruit après l'initialisation de cstr2 est complet. Ainsi, lorsque vous l'imprimez avec cout la chaîne c qui a été associée à cette std::string temporaire a été détruit depuis longtemps, et donc vous aurez de la chance s'il s'écrase et affirme, et pas de chance s'il imprime des déchets ou semble fonctionner.

const char* cstr2 = ss.str().c_str();

La chaîne de caractères C où cstr1 est associé à une chaîne de caractères qui existe toujours au moment où vous effectuez l'opération. cout - pour qu'il imprime correctement le résultat.

Dans le code suivant, le premier cstr est correcte (je suppose qu'elle l'est cstr1 dans le vrai code ?). La seconde imprime la chaîne c-string associée à l'objet string temporaire ss.str() . L'objet est détruit à la fin de l'évaluation de l'expression complète dans laquelle il apparaît. L'expression complète est l'ensemble du cout << ... Ainsi, même si la chaîne c est produite, l'objet chaîne associé existe toujours. Pour cstr2 - c'est une pure méchanceté qu'il réussisse. Il est fort possible qu'il choisisse en interne le même emplacement de stockage pour le nouveau temporaire que celui qu'il a déjà choisi pour le temporaire utilisé pour initialiser l'application cstr2 . Il pourrait aussi bien s'écraser.

cout << cstr            // Prints correctly
    << ss.str().c_str() // Prints correctly
    << cstr2;           // Prints correctly (???)

Le retour de c_str() se contentera généralement de pointer vers le tampon interne de la chaîne de caractères - mais ce n'est pas une obligation. La chaîne pourrait constituer un tampon si son implémentation interne n'est pas contiguë par exemple (c'est bien possible - mais dans la prochaine norme C++, les chaînes doivent être stockées de manière contiguë).

Dans GCC, les chaînes de caractères utilisent le comptage de référence et la copie sur l'écriture. Ainsi, vous trouverez que ce qui suit est vrai (c'est le cas, au moins sur ma version de GCC)

string a = "hello";
string b(a);
assert(a.c_str() == b.c_str());

Les deux cordes partagent ici le même tampon. Au moment où vous modifiez l'une d'entre elles, le tampon sera copié et chacune détiendra sa propre copie. D'autres implémentations de chaînes de caractères font cependant les choses différemment.

5voto

Klaim Points 24511

L'objet std::string renvoyé par ss.str() est un objet temporaire qui aura une durée de vie limitée à l'expression. On ne peut donc pas assigner un pointeur à un objet temporaire sans obtenir des déchets.

Maintenant, il y a une exception : si vous utilisez une référence const pour obtenir l'objet temporaire, il est légal de l'utiliser pour une durée de vie plus large. Par exemple, vous devriez faire :

#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 cette façon, vous bénéficiez de la ficelle pendant une plus longue période.

Maintenant, vous devez savoir qu'il existe un type d'optimisation appelé RVO qui dit que si le compilateur voit une initialisation via un appel de fonction et que cette fonction renvoie un temporaire, il ne fera pas la copie mais fera juste en sorte que la valeur assignée soit le temporaire. De cette façon, vous n'avez pas besoin d'utiliser une référence, c'est seulement si vous voulez être sûr qu'il n'y aura pas de copie que c'est nécessaire. Donc, vous le faites :

 std::string resultstr = ss.str();
 const char* cstr2 = resultstr.c_str();

serait meilleur et plus simple.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X