web-dev-qa-db-fra.com

Comment convertir un std :: string en const char * ou char *?

Comment convertir un std::string en un char* ou un const char*?

853
user37875

Si vous voulez juste passer un std::string à une fonction qui nécessite _const char*_, vous pouvez utiliser

_std::string str;
const char * c = str.c_str();
_

Si vous voulez obtenir une copie accessible en écriture, telle que _char *_, vous pouvez le faire avec ceci:

_std::string str;
char * writable = new char[str.size() + 1];
std::copy(str.begin(), str.end(), writable);
writable[str.size()] = '\0'; // don't forget the terminating 0

// don't forget to free the string after finished using it
delete[] writable;
_

Edit : Notez que ce qui précède n'est pas sans exception. Si quelque chose entre l'appel new et l'appel delete est émis, vous perdrez de la mémoire, car rien n'appellera automatiquement delete. Il y a deux manières immédiates de résoudre ce problème.

boost :: scoped_array

boost::scoped_array effacera la mémoire pour vous en sortant de la portée:

_std::string str;
boost::scoped_array<char> writable(new char[str.size() + 1]);
std::copy(str.begin(), str.end(), writable.get());
writable[str.size()] = '\0'; // don't forget the terminating 0

// get the char* using writable.get()

// memory is automatically freed if the smart pointer goes 
// out of scope
_

std :: vector

C'est la méthode standard (ne nécessite aucune bibliothèque externe). Vous utilisez std::vector , qui gère complètement la mémoire pour vous.

_std::string str;
std::vector<char> writable(str.begin(), str.end());
writable.Push_back('\0');

// get the char* using &writable[0] or &*writable.begin()
_
1007

Étant donné ...

std::string x = "hello";

Obtenir un `char *` ou `const char *` à partir d'une `chaîne`

Comment obtenir un pointeur de caractère valide alors que x reste dans la portée et n'est plus modifié

C++ 11 simplifie les choses; les éléments suivants donnent tous accès au même tampon de chaîne interne:

const char* p_c_str = x.c_str();
const char* p_data  = x.data();
char* p_writable_data = x.data(); // for non-const x from C++17 
const char* p_x0    = &x[0];

      char* p_x0_rw = &x[0];  // compiles iff x is not const...

Tous les pointeurs ci-dessus auront la même valeur - l'adresse du premier caractère de la mémoire tampon. Même une chaîne vide a un "premier caractère dans le tampon", car C++ 11 garantit de toujours conserver un caractère de terminaison NUL/0 supplémentaire après le contenu de chaîne explicitement attribué (par exemple, std::string("this\0that", 9) aura un tampon contenant "this\0that\0") .

Compte tenu de l'un des pointeurs ci-dessus:

char c = p[n];   // valid for n <= x.size()
                 // i.e. you can safely read the NUL at p[x.size()]

Seulement pour le pointeur non -constp_writable_data et à partir de &x[0]:

p_writable_data[n] = c;
p_x0_rw[n] = c;  // valid for n <= x.size() - 1
                 // i.e. don't overwrite the implementation maintained NUL

Écrire une NUL ailleurs dans la chaîne ne ne modifie pas les string 's size(); Les variables string peuvent contenir n'importe quel nombre de NUL. Aucun traitement spécial n'est donné par std::string (idem en C++ 03).

Dans C++ , les choses étaient considérablement plus compliquées (différences principales mises en évidence ):

  • x.data()

    • renvoie const char* dans le tampon interne de la chaîne qui n'était pas requis par la norme pour conclure par un NUL (c'est-à-dire qu'il pourrait s'agir de ['h', 'e', 'l', 'l', 'o'] suivies de valeurs non initialisées ou parasites, avec des accès accidentels ayant un comportement indéfini ).
      • Les caractères x.size() peuvent être lus en toute sécurité, à savoir x[0] à x[x.size() - 1]
      • pour les chaînes vides, vous avez la garantie d'un pointeur non NULL auquel 0 peut être ajouté en toute sécurité (hourra!), mais vous ne devriez pas déréférencer ce pointeur.
  • &x[0]

    • pour les chaînes vides, comportement indéfini (21.3.4)
      • par exemple. Si vous avez donné f(const char* p, size_t n) { if (n == 0) return; ...whatever... }, vous ne devez pas appeler f(&x[0], x.size()); lorsque x.empty() - utilisez simplement f(x.data(), ...).
    • sinon, conformément à x.data() mais:
      • pour non -constx, on obtient un pointeur non -constchar*; vous pouvez écraser le contenu d'une chaîne
  • x.c_str()

    • renvoie const char* en une représentation ASCIIZ (terminée par NUL) de la valeur (c'est-à-dire ['h', 'e', ​​'l', 'l', 'l', 'o', '\ 0']).
    • bien que peu ou pas d'implémentations aient choisi de le faire, le standard C++ 03 a été libellé de manière à permettre à l'implémentation de chaînes de créer un tampon distinct, terminé par NUL à la volée , à partir du tampon potentiellement terminé non NUL "exposé" par x.data() et &x[0]
    • x.size() + 1 caractères sont sûrs à lire.
    • garantie sûre même pour les chaînes vides (['\ 0']).

Conséquences de l'accès à des index juridiques extérieurs

Quelle que soit la manière dont vous obtenez un pointeur, vous ne devez pas accéder à la mémoire plus loin du pointeur que les caractères garantis présents dans les descriptions ci-dessus. Les tentatives en ce sens ont un comportement indéfini , avec un risque réel de plantages de l'application et des résultats erronés, même pour les lectures, ainsi que des vulnérabilités de données, de corruption de pile et/ou de sécurité en gros pour les écritures.

Quand ces pointeurs sont-ils invalidés?

Si vous appelez une fonction membre string modifiant le string ou réservant une capacité supplémentaire, les valeurs de pointeur renvoyées auparavant par l'une des méthodes ci-dessus sont invalidées . Vous pouvez utiliser ces méthodes à nouveau pour obtenir un autre pointeur. (Les règles sont les mêmes que pour les itérateurs dans strings).

Voir aussi Comment obtenir un pointeur de caractère valide même après que x ait quitté la portée ou soit modifié ci-dessous ....

Alors, quel est meilleur à utiliser?

A partir de C++ 11, utilisez .c_str() pour les données ASCIIZ et .data() pour les données "binaires" (expliquées plus en détail ci-dessous).

En C++ 03, utilisez .c_str() sauf si vous êtes certain que .data() est adéquate et préférez .data() à &x[0] car il est sans danger pour les chaînes vides ....

... essayez de comprendre suffisamment le programme pour utiliser data() le cas échéant, sinon vous ferez probablement d'autres erreurs ...

Le caractère ASCII NUL '\ 0', garanti par .c_str(), est utilisé par de nombreuses fonctions comme valeur sentinelle indiquant la fin des données pertinentes et accessibles en toute sécurité. Cela s'applique à la fois aux fonctions C++ - comme par exemple fstream::fstream(const char* filename, ...) et aux fonctions partagées avec C comme strchr() et printf().

Étant donné les garanties de .c_str() de C++ 03 concernant le tampon renvoyé sont un super ensemble de .data(), vous pouvez toujours utiliser .c_str() en toute sécurité, mais certaines personnes ne le font pas parfois car:

  • utiliser .data() communique aux autres programmeurs qui lisent le code source que les données ne sont pas ASCIIZ (vous utilisez plutôt la chaîne pour stocker un bloc de données (qui parfois n'est même pas vraiment textuel)), ou que vous transmettez à une autre fonction qui le traite comme un bloc de données "binaires". Cela peut constituer un élément crucial pour garantir que les modifications de code des autres programmeurs continuent à gérer les données correctement.
  • C++ 03 uniquement: votre implémentation string nécessitera une allocation supplémentaire de mémoire et/ou une copie des données afin de préparer le tampon de terminaison NUL.

Comme indice supplémentaire, si les paramètres d'une fonction nécessitent le (const) char* mais n'insistez pas pour obtenir x.size(), la fonction probablement nécessite une entrée ASCIIZ, alors .c_str() est un bon choix (la fonction doit savoir où le texte se termine, donc si ce n'est pas un paramètre séparé, il ne peut s'agir que d'une convention comme un préfixe de longueur ou une sentinelle ou une longueur attendue fixe).

Comment obtenir un pointeur de caractère valide même après que x ait quitté la portée ou ait été modifié

Vous aurez besoin de copier le contenu de stringx dans une nouvelle zone mémoire en dehors de x. Ce tampon externe peut se trouver à de nombreux endroits, comme une autre variable string ou un tableau de caractères, sa durée de vie peut être différente de celle de x en raison de sa portée différente (espace de noms, global, statique, etc.). , tas, mémoire partagée, fichier mappé en mémoire).

Pour copier le texte de std::string x dans un tableau de caractères indépendant:

// USING ANOTHER STRING - AUTO MEMORY MANAGEMENT, EXCEPTION SAFE
std::string old_x = x;
// - old_x will not be affected by subsequent modifications to x...
// - you can use `&old_x[0]` to get a writable char* to old_x's textual content
// - you can use resize() to reduce/expand the string
//   - resizing isn't possible from within a function passed only the char* address

std::string old_x = x.c_str(); // old_x will terminate early if x embeds NUL
// Copies ASCIIZ data but could be less efficient as it needs to scan memory to
// find the NUL terminator indicating string length before allocating that amount
// of memory to copy into, or more efficient if it ends up allocating/copying a
// lot less content.
// Example, x == "ab\0cd" -> old_x == "ab".

// USING A VECTOR OF CHAR - AUTO, EXCEPTION SAFE, HINTS AT BINARY CONTENT, GUARANTEED CONTIGUOUS EVEN IN C++03
std::vector<char> old_x(x.data(), x.data() + x.size());       // without the NUL
std::vector<char> old_x(x.c_str(), x.c_str() + x.size() + 1);  // with the NUL

// USING STACK WHERE MAXIMUM SIZE OF x IS KNOWN TO BE COMPILE-TIME CONSTANT "N"
// (a bit dangerous, as "known" things are sometimes wrong and often become wrong)
char y[N + 1];
strcpy(y, x.c_str());

// USING STACK WHERE UNEXPECTEDLY LONG x IS TRUNCATED (e.g. Hello\0->Hel\0)
char y[N + 1];
strncpy(y, x.c_str(), N);  // copy at most N, zero-padding if shorter
y[N] = '\0';               // ensure NUL terminated

// USING THE STACK TO HANDLE x OF UNKNOWN (BUT SANE) LENGTH
char* y = alloca(x.size() + 1);
strcpy(y, x.c_str());

// USING THE STACK TO HANDLE x OF UNKNOWN LENGTH (NON-STANDARD GCC EXTENSION)
char y[x.size() + 1];
strcpy(y, x.c_str());

// USING new/delete HEAP MEMORY, MANUAL DEALLOC, NO INHERENT EXCEPTION SAFETY
char* y = new char[x.size() + 1];
strcpy(y, x.c_str());
//     or as a one-liner: char* y = strcpy(new char[x.size() + 1], x.c_str());
// use y...
delete[] y; // make sure no break, return, throw or branching bypasses this

// USING new/delete HEAP MEMORY, SMART POINTER DEALLOCATION, EXCEPTION SAFE
// see boost shared_array usage in Johannes Schaub's answer

// USING malloc/free HEAP MEMORY, MANUAL DEALLOC, NO INHERENT EXCEPTION SAFETY
char* y = strdup(x.c_str());
// use y...
free(y);

Autres raisons de vouloir un char* ou const char* généré à partir d'un string

Donc, ci-dessus, vous avez vu comment obtenir un (const) char*, et comment faire une copie du texte indépendamment de l'original string, mais que pouvez-vous faire avec elle? Une sélection aléatoire d'exemples ...

  • donnez au code "C" l'accès au texte C++ string, comme dans printf("x is '%s'", x.c_str());
  • copie le texte de x dans un tampon spécifié par l'appelant de votre fonction (par exemple, strncpy(callers_buffer, callers_buffer_size, x.c_str())) ou par la mémoire volatile utilisée pour les entrées/sorties du périphérique (par exemple, for (const char* p = x.c_str(); *p; ++p) *p_device = *p;)
  • ajoutez le texte de x à un tableau de caractères contenant déjà du texte ASCIIZ (par exemple, strcat(other_buffer, x.c_str())) - veillez à ne pas saturer le tampon (dans de nombreux cas, vous devrez peut-être utiliser strncat).
  • renvoyer un const char* ou char* d'une fonction (peut-être pour des raisons historiques (le client utilise votre API existante) ou pour des raisons de compatibilité C, vous ne voulez pas renvoyer un std::string, mais vous voulez copier vos données string quelque part pour l'appelant)
    • veillez à ne pas renvoyer un pointeur qui peut être déréférencé par l'appelant après une variable locale string à laquelle ce pointeur a pointé
    • certains projets avec des objets partagés compilés/liés pour différentes implémentations std::string (par exemple, STLport et le compilateur natif) peuvent transmettre des données en tant que ASCIIZ pour éviter les conflits
186
Tony Delroy

Utilisez la méthode .c_str() pour const char *.

Vous pouvez utiliser &mystring[0] pour obtenir un pointeur char *, mais il y a quelques pièges: vous n'obtiendrez pas nécessairement une chaîne terminée par zéro et vous ne pourrez pas modifier la taille de la chaîne. Faites particulièrement attention de ne pas ajouter de caractères après la fin de la chaîne, sinon vous obtiendrez un dépassement de la mémoire tampon (et un crash probable).

Rien ne garantissait que tous les caractères feraient partie du même tampon contigu avant C++ 11, mais en pratique, toutes les implémentations connues de std::string fonctionnaient de cette façon. voir “& s [0]” pointe-t-il sur des caractères contigus dans un std :: string? .

Notez que de nombreuses fonctions membres string réaffecteront le tampon interne et invalideront les pointeurs que vous avez éventuellement enregistrés. Le mieux est de les utiliser immédiatement et de les jeter.

33
Mark Ransom

C++ 17

C++ 17 (norme à venir) modifie le synopsis du modèle _basic_string_ en ajoutant une surcharge non constante de data():

charT* data() noexcept;

Retourne: Un pointeur p tel que p + i == & opérateur pour chaque i dans [0, size ()].


_CharT const *_ de _std::basic_string<CharT>_

_std::string const cstr = { "..." };
char const * p = cstr.data(); // or .c_str()
_

_CharT *_ de _std::basic_string<CharT>_

_std::string str = { "..." };
char * p = str.data();
_

C++ 11

_CharT const *_ de _std::basic_string<CharT>_

_std::string str = { "..." };
str.c_str();
_

_CharT *_ de _std::basic_string<CharT>_

A partir de C++ 11, le standard dit:

  1. Les objets de type caractère dans un objet _basic_string_ doivent être stockés de manière contiguë. Autrement dit, pour tout objet _basic_strings, l'identité &*(s.begin() + n) == &*s.begin() + n doit être conservée pour toutes les valeurs de n telles que 0 <= n < s.size().

  1. const_reference operator[](size_type pos) const;
    reference operator[](size_type pos);

    Renvoie: *(begin() + pos) si pos < size(), sinon une référence à un objet de type CharT avec la valeur CharT(); la valeur de référence ne doit pas être modifiée.


  1. const charT* c_str() const noexcept;
    const charT* data() const noexcept;

    Retourne: Un pointeur p tel que p + i == &operator[](i) pour chaque i dans [0,size()].

Il existe différentes manières possibles d'obtenir un pointeur de caractère non const.

1. Utilisez le stockage contigu de C++ 11

_std::string foo{"text"};
auto p = &*foo.begin();
_

Pro

  • Simple et court
  • Rapide (seule méthode sans copie impliquée)

inconvénients

  • Final _'\0'_ ne doit pas être modifié/ne fait pas nécessairement partie de la mémoire non const.

2. Utilisez _std::vector<CharT>_

_std::string foo{"text"};
std::vector<char> fcv(foo.data(), foo.data()+foo.size()+1u);
auto p = fcv.data();
_

Pro

  • Facile
  • Gestion automatique de la mémoire
  • Dynamique

inconvénients

  • Requiert une copie de chaîne

3. Utilisez _std::array<CharT, N>_ si N est une constante de temps de compilation (et suffisamment petite)

_std::string foo{"text"};
std::array<char, 5u> fca;
std::copy(foo.data(), foo.data()+foo.size()+1u, fca.begin());
_

Pro

  • Facile
  • Gestion de la mémoire de pile

inconvénients

  • Statique
  • Requiert une copie de chaîne

4. Allocation de mémoire brute avec suppression automatique de la mémoire

_std::string foo{ "text" };
auto p = std::make_unique<char[]>(foo.size()+1u);
std::copy(foo.data(), foo.data() + foo.size() + 1u, &p[0]);
_

Pro

  • Petite empreinte mémoire
  • Suppression automatique
  • Facile

inconvénients

  • Requiert une copie de chaîne
  • Statique (l'utilisation dynamique nécessite beaucoup plus de code)
  • Moins de caractéristiques que les vecteurs ou les tableaux

5. Allocation de mémoire brute avec traitement manuel

_std::string foo{ "text" };
char * p = nullptr;
try
{
  p = new char[foo.size() + 1u];
  std::copy(foo.data(), foo.data() + foo.size() + 1u, p);
  // handle stuff with p
  delete[] p;
}
catch (...)
{
  if (p) { delete[] p; }
  throw;
}
_

Pro

  • 'Contrôle' maximum

Con

  • Requiert une copie de chaîne
  • Responsabilité maximale/susceptibilité aux erreurs
  • Complexe
20
Pixelchemist

Je travaille avec une API avec de nombreuses fonctions qui reçoivent en entrée un char*.

J'ai créé une petite classe pour faire face à ce genre de problème, j'ai implémenté l'idiome RAII.

class DeepString
{
        DeepString(const DeepString& other);
        DeepString& operator=(const DeepString& other);
        char* internal_; 

    public:
        explicit DeepString( const string& toCopy): 
            internal_(new char[toCopy.size()+1]) 
        {
            strcpy(internal_,toCopy.c_str());
        }
        ~DeepString() { delete[] internal_; }
        char* str() const { return internal_; }
        const char* c_str()  const { return internal_; }
};

Et vous pouvez l'utiliser comme:

void aFunctionAPI(char* input);

//  other stuff

aFunctionAPI("Foo"); //this call is not safe. if the function modified the 
                     //literal string the program will crash
std::string myFoo("Foo");
aFunctionAPI(myFoo.c_str()); //this is not compiling
aFunctionAPI(const_cast<char*>(myFoo.c_str())); //this is not safe std::string 
                                                //implement reference counting and 
                                                //it may change the value of other
                                                //strings as well.
DeepString myDeepFoo(myFoo);
aFunctionAPI(myFoo.str()); //this is fine

J'ai appelé la classe DeepString car elle crée une copie profonde et unique (la DeepString n'est pas copiable) d'une chaîne existante.

9
Alessandro Teruzzi
char* result = strcpy((char*)malloc(str.length()+1), str.c_str());
7
cegprakash

Il suffit de voir ceci:

string str1("stackoverflow");
const char * str2 = str1.c_str();

Cependant, notez que ceci retournera un const char *. Pour un char *, utilisez strcpy pour le copier dans un autre tableau char.

7
devsaw