2 votes

Opérations sur les chaînes et gestion de la mémoire

Je veux écrire une enveloppe pratique pour une fonction de style C strftime . Et j'ai trouvé quelques options pour convertir un tableau de caractères en chaîne de caractères et vice-versa. Voici mon code :

std::string Time::getAsFormattedString ( const std::string& format , const size_t& maxStringSize = 999 )
{

    char* timeArray = 0;
    std::string timeString;

    //  [OPTION_0]
    timeArray = reinterpret_cast <char*> (malloc(sizeof(char)*maxStringSize)));

    //  [OPTION_1]
    timeArray = const_cast <char*> (timeString.c_str());

    //  [OPTION_2]
    timeArray = &(*(timeString.begin()));

    strftime(timeArray,maxStringSize,format.c_str(),&this->time);
    timeString = timeArray;

    //  [OPTION_0]
    free(timeArray);

    return timeString;

}

L'option 0 semble sûre car aucune exception ne peut être levée avant la libération de la mémoire (Edit : timeString = timeArray peut en lancer un, try-catch nécessaire autour de cette ligne)

1 le const-casting ressemble toujours à un hack

2 semble être le meilleur par je ne sais pas si il pourrait y avoir des problèmes avec elle

Pouvez-vous me dire, s'il vous plaît, laquelle est la plus sûre, la plus correcte, la plus optimale et peut-être une sorte de meilleure pratique.

Merci.

3voto

James Kanze Points 96599

Aucune des options que vous proposez n'est vraiment acceptable ; la la deuxième et la troisième ne fonctionneront même pas. Globalement, il y a deux solutions "acceptables".

Le plus simple est :

char buffer[ 1000 ];
size_t n = strftime( buffer, sizeof( buffer ), format.c_str(), &time );
if ( n == 0 ) {
    throw SomeError;    //  or you might just abort...
}
return std::string( buffer );

Cela a l'avantage de la simplicité, mais vous devez documenter la taille maximale comme une contrainte dans votre interface. (Cela me semble être une contrainte raisonnable).

Vous pouvez également supprimer la contrainte :

std::vector<char> buffer( 100 );
size_t n = strftime( &buffer[0], buffer.size(), format.c_str(), &time );
while ( n == 0 ) {
    buffer.resize( 2 * buffer.size() );
    n = strftime( &buffer[0], buffer.size(), format.c_str(), &time );
}
return std::string( buffer.begin(), buffer.begin() + n );

(En C++11, et en pratique en C++03, vous pouvez faire cela avec std::string directement, plutôt que std::vector . Dans ce cas vous devez appeler resize( n ) sur la chaîne de caractères résultante avant de la renvoyer).

2voto

Salgar Points 4803

Pour commencer, la seule option qui ne va pas se planter est l'option 0 ici. Les autres vont se planter car vous dites que vous avez alloué 999 octets mais en fait la chaîne interne n'aura probablement qu'un seul octet alloué, et des choses tristes vont se produire.

Cependant, je le ferais probablement en allouant un grand nombre de caractères sur la pile ici.

char timeArray[2048];
strftime(timeArray,2048,format.c_str(),&this->time);
return string(timeArray);

De cette façon, vous n'avez pas à effectuer de casting ou d'allocations dynamiques et vous serez presque certainement plus propre et plus rapide.

1voto

jalf Points 142628

Option 2 (ou mieux encore, &timeString[0] ) devraient être préférés.

Vous avez raison au sujet de la const_cast étant mauvais dans l'option 1, et dans l'option 0, vous pourriez, à tout le moins, nettoyer un peu le code en utilisant new au lieu de malloc (et éviter le casting)

Mais je préfère l'option 2.

(Oh, et comme les commentateurs l'ont souligné, si vous écrivez dans la chaîne elle-même, vous devez évidemment la redimensionner pour qu'elle soit suffisamment grande pour ne pas écrire hors limites. Vu les votes négatifs, j'aurais probablement dû être plus explicite à ce sujet).

1voto

Mats Petersson Points 70074

Les options 1 et 2 ne sont pas bonnes, car vous n'êtes pas censé modifier la chaîne de caractères que vous obtenez à partir du site Web de l'UE. std::string::c_str() (c est pour constant ). L'option 2 nécessitera un "redimensionnement" de la chaîne avant que vous puissiez l'utiliser. Mais je ne suis pas sûr que les chaînes soient garanties de se copier depuis leur même tampon...

Ma solution serait d'avoir :

char timeArray[1000];     

(Bien que ce soit très excessif. À moins que vous ne répétiez plusieurs fois le même spécificateur de format, il est peu probable que vous atteigniez plus de 100 caractères, et c'est très long - donc les combinaisons "saines" n'atteindront pas près de 1000 caractères).

Notez que timeString = timeArray peut lancer une exception pour bad_alloc Si vous voulez éviter les fuites de mémoire dans cette situation, vous devez utiliser un stockage basé sur la pile (comme dans ma suggestion), un pointeur intelligent ou un bloc try/catch autour de certaines parties du code.

1voto

Baltasarq Points 5164

La seule solution possible est l'option 0 (bien que quelques ajustements mineurs puissent être effectués). Comme d'autres l'ont souligné, la norme stipule que l'espace retourné par la fonction c_str() n'est pas destinée à être utilisée à des fins d'écriture, il s'agit en fait d'un moyen de permettre à la std::string pour être lu par la bibliothèque standard C (qui fait partie de la bibliothèque standard C++).

La norme se lit comme suit :

Renvoie à : Un pointeur sur l'élément initial d'un tableau de longueur size() + 1 dont les premiers éléments de size() sont égaux aux éléments correspondants de la chaîne contrôlée par *this et dont le dernier élément est un caractère null spécifié par charT().

Nécessite : Le programme ne doit pas modifier aucune des valeurs stockées dans le tableau. Le programme ne doit pas non plus ne doit pas non plus traiter la valeur renvoyée comme un pointeur valide après tout appel ultérieur à une fonction membre non-const de la classe basic_string qui désigne le même objet que celui-ci.

Donc, je ferais juste une correction rapide de votre code :

const char * Time::getAsFormattedString(const std::string& format)
{
    static char timeArray[256];

    std::strftime( timeArray, 256, format.c_str(), &this->time );

    return timeArray;
}

Ainsi, le tampon de votre méthode est créé au démarrage du programme et réutilisé en permanence, de sorte qu'aucune erreur de mémoire n'est à craindre de ce côté (puisque le tas n'est pas touché).

Le seul problème est qu'il y a de la place dans la pile pour créer la chaîne de caractères dans laquelle vous stockerez le résultat de la fonction, mais de toute façon cela se produira après avoir appelé la fonction, la fonction elle-même ne touchera pas le tas, et seulement un minimum de la pile.

En termes pratiques, l'utilité de la fonction n'est pas touchée, puisqu'il y a une conversion automatique de la fonction const char * a std::string Vous pouvez donc l'appeler de la manière habituelle :

std::string strTime = time.getAsFormattedString( "%F %T" );

J'espère que cela vous aidera.

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