web-dev-qa-db-fra.com

stringstream, chaîne et confusion de conversion char *

Ma question peut être résumée comme suit: où la chaîne renvoyée par stringstream.str().c_str() réside-t-elle en mémoire et pourquoi ne peut-elle pas être affectée à un const char*?

Cet exemple de code l'expliquera mieux que moi

#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 voulant que stringstream.str().c_str() puisse être affectée à un const char* A conduit à un bogue qui m'a pris un certain temps à localiser.

Pour les points bonus, quelqu'un peut-il expliquer pourquoi remplacer l'instruction cout par

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

imprime les chaînes correctement?

Je compile dans Visual Studio 2008.

134
Graphics Noob

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

Vous pouvez copier cet objet chaîne temporaire dans un autre objet chaîne et prendre la chaîne C de celui-ci:

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

Notez que j'ai créé la chaîne temporaire const, car toute modification apportée pourrait entraîner une réallocation et rendre ainsi cstr invalide. Il est donc préférable de ne pas stocker le résultat de l'appel à str() et d'utiliser cstr seulement jusqu'à la fin de l'expression complète:

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

Bien sûr, la dernière solution n’est peut-être pas facile et la copie trop chère. Au lieu de cela, vous pouvez lier le temporaire à une référence const. Cela étendra sa durée de vie à celle de la référence:

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

OMI c'est la meilleure solution. Malheureusement, ce n'est pas très connu.

192
sbi

Ce que vous faites est la création d'un temporaire. Ce temporaire existe dans une portée déterminée par le compilateur, de sorte qu'elle est suffisamment longue pour satisfaire les exigences de l'endroit où elle se dirige.

Dès que l'instruction const char* cstr2 = ss.str().c_str(); est terminée, le compilateur ne voit aucune raison de conserver la chaîne temporaire, elle est détruite et votre const char * Pointe donc sur de la mémoire libre.

Votre déclaration string str(ss.str()); signifie que le temporaire est utilisé dans le constructeur pour la variable string que str que vous avez mise sur la pile locale et qui reste aussi longtemps que vous vous attendez à: jusqu'à la fin du bloc ou de la fonction que vous avez écrite. Par conséquent, la const char * Au sein de la mémoire est toujours une bonne mémoire lorsque vous essayez la cout.

13
Jared Oberhaus

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 vous référerez à des données légitimes, mais après cette ligne, la chaîne sera détruite, laissant votre char* Pour pointer sur une mémoire non possédée.

5
fbrereto

L'objet std :: string renvoyé par ss.str () est un objet temporaire dont la durée de vie est limitée à l'expression. Vous ne pouvez donc pas affecter un pointeur à un objet temporaire sans récupérer la corbeille.

Il existe toutefois une exception: si vous utilisez une référence const pour obtenir l'objet temporaire, il est légal de l'utiliser pendant une durée de vie plus longue. 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 obtenez la chaîne plus longtemps.

Maintenant, vous devez savoir qu’il existe une sorte d’optimisation appelée 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 simplement que la valeur assignée soit 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’elle ne sera pas copiée. Donc faire:

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

serait mieux et plus simple.

5
Klaim

La ss.str() temporaire est détruite une fois l'initialisation de cstr2 Terminée. Ainsi, lorsque vous l’imprimez avec cout, la chaîne de caractères associée à ce std::string Temporaire a longtemps été détruite et vous aurez donc de la chance si elle se bloque et s’affirme, et pas si il imprime des ordures ou semble fonctionner.

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

La chaîne de caractères où cstr1 Pointe sur, est cependant associée à une chaîne qui existe toujours au moment où vous effectuez la cout - afin qu'elle imprime correctement le résultat.

Dans le code suivant, le premier cstr est correct (je suppose que c’est cstr1 Dans le code réel?). La seconde affiche la chaîne de caractères associée à l'objet chaîne temporaire ss.str(). L'objet est détruit à la fin de l'évaluation de la pleine expression dans laquelle il apparaît. L'expression complète est la totalité de l'expression cout << .... Ainsi, si la chaîne de caractères c est générée, l'objet chaîne associé existe toujours. Pour cstr2 - c’est purement mauvais qu’il réussisse. Il choisit très probablement en interne le même emplacement de stockage pour le nouveau temporaire qu'il avait déjà choisi pour le temporaire utilisé pour initialiser 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() ne fera généralement que pointer vers le tampon de chaîne interne - 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ë).

En GCC, les chaînes utilisent le comptage de références et la copie sur écriture. Ainsi, vous constaterez que ce qui suit est vrai (c'est le cas, du moins sur ma version de GCC)

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

Les deux chaînes partagent le même tampon ici. Au moment où vous en changez un, le tampon sera copié et chacun conservera sa copie séparée. D'autres implémentations de chaîne font les choses différentes, cependant.

5