stringstream, string, et char* confusion de conversion

ma question peut être ramenée à, où la chaîne retournée de stringstream.str().c_str() vit-elle dans la mémoire, et pourquoi ne peut-elle pas être assignée à un const char* ?

cet exemple de code expliquera mieux que je ne peux

#include <string>
#include <sstream>
#include <iostream>

using namespace std;

int main()
{
    stringstream ss("this is a stringn");

    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() pouvait être assigné à un const char* a conduit à un bogue qui m'a pris un certain temps à traquer.

pour les points bonus, est-ce que quelqu'un peut expliquer pourquoi remplacer le cout déclaration avec

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

imprime les cordes correctement?

Je compilais dans Visual Studio 2008.

126
demandé sur jww 2009-09-03 20:22:10

5 réponses

stringstream.str() renvoie un objet chaîne de caractères temporaire qui est détruit à la fin de l'expression complète. Si vous obtenez un pointeur vers une chaîne C à partir de cela ( stringstream.str().c_str() ), il pointera vers une chaîne qui est supprimée où la déclaration se termine. C'est pour ça que ton code imprime des ordures.

vous pouvez copier cet objet temporaire string vers un autre objet string 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 fait le chaîne de caractères temporaire const , car tout changement à cette chaîne de caractères pourrait entraîner sa réattribution et donc rendre cstr invalide. Il est donc plus sûr de ne pas stocker le résultat de l'appel à str() et l'utilisation cstr jusqu'à la fin de l'expression complète:

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

bien sûr, cette dernière solution n'est peut-être pas facile et la copie peut être trop coûteuse. Ce que vous pouvez faire à la place est de lier le temporaire à une référence const . Cette mesure permettra d'étendre son durée de vie à durée de vie de la référence:

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

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

177
répondu sbi 2016-04-04 12:45:00

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

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

votre déclaration string str(ss.str()); signifie que le est utilisé dans le constructeur pour la variable string str que vous avez mis sur la pile locale, et qui reste aussi longtemps que vous vous y attendiez: jusqu'à la fin du bloc, ou la fonction que vous avez écrite. Par conséquent, le const char * intérieur est encore une bonne mémoire lorsque vous essayez le cout .

12
répondu Jared Oberhaus 2009-09-03 16:25:18

dans cette ligne:

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

ss.str() fera une copie du contenu du stringstream. Lorsque vous appelez c_str() sur la même ligne, vous référencerez des données légitimes, mais après cette ligne la chaîne sera détruite, laissant votre char* pour pointer vers la mémoire inconnue.

5
répondu fbrereto 2009-09-03 16:27:46

std::string objet retourné par la ss.str() est un objet temporaire qui ont une durée de vie limitée à l'expression. Ainsi, vous ne pouvez pas assigner un pointeur à un objet temporaire sans recevoir de déchets.

Maintenant, il y a une exception: si vous utilisez un const référence pour obtenir l'objet temporaire, il est possible de l'utiliser pour une plus grande durée de vie. 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 pour un temps plus long.

maintenant, vous devez savoir qu'il y a une sorte d'optimisation appelée RVO qui dit que si le compilateur voit une initialisation via un appel de fonction et que la fonction retourne une temporaire, elle ne fera pas la copie mais juste la Valeur assignée sera la 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 ne copie pas que c'est nécessaire. Ce faisant:

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

serait mieux et plus simple.

5
répondu Klaim 2009-09-03 16:30:28

Le ss.str() temporaire est détruit après l'initialisation de cstr2 est terminée. Donc, quand vous l'imprimez avec cout , la chaîne de caractères c qui était associée à ce std::string temporaire a été longtemps désorientée, et donc vous serez chanceux si elle s'écrase et affirme, et pas de chance si elle imprime des ordures ou semble fonctionner.

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

la chaîne C où cstr1 pointe, cependant, est associée à une chaîne qui existe encore au moment où vous faites le cout - donc il imprime correctement le résultat.

dans le code suivant, le premier cstr est correct (je suppose qu'il est cstr1 dans le code réel?). La seconde imprime la chaîne c 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. La pleine expression est l'expression entière cout << ... -donc pendant que la chaîne c est sortie, la chaîne associée objet existe toujours. Pour cstr2 - c'est la méchanceté pure qu'il réussit. Elle choisit très probablement en interne le même lieu de stockage pour le nouveau temporaire qu'elle a déjà choisi pour le temporaire utilisé pour initialiser cstr2 . Il pourrait aswell crash.

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

le retour de c_str() pointera habituellement simplement vers le tampon de chaîne interne - mais ce n'est pas une exigence. La chaîne pourrait constituer un tampon si son l'implémentation n'est pas contiguë par exemple (c'est bien possible - mais dans la prochaine norme C++, les chaînes doivent être contiguës stockées).

dans GCC, les chaînes utilisent le comptage de référence et le copy-on-write. Ainsi, vous constaterez que ce qui suit est vrai (du moins dans ma version 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 changez l'un d'eux, le buffer sera copié et chacun tiendra son séparé copie. D'autres implémentations de chaînes font des choses différentes, cependant.

5
répondu Johannes Schaub - litb 2009-09-03 16:33:34