Étant donné dire ...
std::string x = "hello";
Obtenir un `char *` ou `const char *` à partir d'une `chaîne`
Comment obtenir un pointeur de caractère valide alors qu'il x
reste dans la portée et n'est pas modifié davantage
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 contiendront la même valeur - l'adresse du premier caractère dans le tampon. Même une chaîne vide a un "premier caractère dans le tampon", car C ++ 11 garantit de toujours garder un caractère de terminaison NUL / 0 supplémentaire après le contenu de la chaîne explicitement attribué (par exemple, std::string("this\0that", 9)
aura un tampon contenant"this\0that\0"
).
Étant donné 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()]
Uniquement pour le non const
pointeur p_writable_data
et 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
L'écriture d'un NUL ailleurs dans la chaîne ne change pas le string
s size()
; string
sont autorisés à contenir un nombre illimité de NUL - ils ne reçoivent aucun traitement spécial parstd::string
(idem en C ++ 03).
En C ++ 03 , les choses étaient considérablement plus compliquées (les principales différences ont été mises en évidence ):
x.data()
- retourne
const char*
au tampon interne de la chaîne qui n'était pas requis par la norme pour conclure avec un NUL (c'est-à-dire qu'il pourrait être ['h', 'e', 'l', 'l', 'o']
suivi de valeurs non initialisées ou de déchets, avec des accès accidentels à celui-ci ayant un comportement non défini ).
x.size()
les caractères sont sûrs à lire, c.- x[0]
à-d.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 devez pas déréférencer ce pointeur.
&x[0]
- pour les chaînes vides, cela a un comportement indéfini (21.3.4)
- par exemple, étant donné que
f(const char* p, size_t n) { if (n == 0) return; ...whatever... }
vous ne devez pas appeler f(&x[0], x.size());
quand x.empty()
- utilisez simplement f(x.data(), ...)
.
- sinon, selon
x.data()
mais:
- pour non
const
x
cela donne un non const
char*
pointeur; vous pouvez remplacer le contenu de la chaîne
x.c_str()
- renvoie
const char*
à une représentation ASCIIZ (terminée par NUL) de la valeur (c'est-à-dire ['h', 'e', 'l', 'l', 'o', '\ 0']).
- bien que peu ou pas d'implémentations aient choisi de le faire, la norme C ++ 03 a été formulée pour permettre à l'implémentation de chaîne de créer à la volée un tampon distinct terminé par NUL , à partir du tampon potentiellement non-NUL "exposé" par et
x.data()
&x[0]
x.size()
+ 1 caractères sont lisibles en toute sécurité.
- sécurité garantie même pour les chaînes vides (['\ 0']).
Conséquences de l'accès à des indices juridiques externes
Quelle que soit la façon 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 de le faire ont un comportement indéfini , avec une chance très réelle de plantages d'application et de résultats inutiles même pour les lectures, et en outre des données en gros, une corruption de pile et / ou des vulnérabilités de sécurité pour les écritures.
Quand ces pointeurs sont-ils invalidés?
Si vous appelez une string
fonction membre qui modifie string
ou réserve une capacité supplémentaire, toutes les valeurs de pointeur renvoyées au préalable par l'une des méthodes ci-dessus sont invalidées . Vous pouvez à nouveau utiliser ces méthodes pour obtenir un autre pointeur. (Les règles sont les mêmes que pour les itérateurs en string
s).
Voir aussi Comment obtenir un pointeur de caractère valide même après avoir x
quitté la portée ou modifié plus loin ci-dessous ....
Alors, quel est le 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 loin ci-dessous).
En C ++ 03, utilisez à .c_str()
moins que ce .data()
soit suffisant, et préférez .data()
plutôt &x[0]
que c'est sûr pour les chaînes vides ....
... essayez de comprendre suffisamment le programme pour l'utiliser le data()
cas échéant, ou 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 sécurisées d'accès. Cela s'applique à la fois aux fonctions C ++ - uniquement comme les fonctions say fstream::fstream(const char* filename, ...)
et shared-with-C comme strchr()
, etprintf()
.
Étant donné que les .c_str()
garanties de C ++ 03 sur le tampon retourné sont un super-ensemble de .data()
, vous pouvez toujours les utiliser en toute sécurité .c_str()
, mais les gens ne le font parfois pas parce que:
- l'utilisation
.data()
communique à d'autres programmeurs en lisant 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 les transmettez à une autre fonction qui le traite comme un bloc de données "binaires". Cela peut être un élément crucial pour garantir que les modifications de code des autres programmeurs continuent de gérer correctement les données.
- C ++ 03 uniquement: il est possible que votre
string
implémentation doive effectuer une allocation de mémoire supplémentaire et / ou une copie des données afin de préparer le tampon terminé par NUL
Comme autre indice, si les paramètres d'une fonction nécessitent le ( const
) char*
mais n'insistent pas pour l'obtenir x.size()
, la fonction a probablement besoin d'une entrée ASCIIZ, c'est donc .c_str()
un bon choix (la fonction doit savoir où le texte se termine d'une manière ou d'une autre, donc si ce n'est pas le cas) 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 certaine longueur attendue fixe).
Comment obtenir un pointeur de caractère valide même après avoir x
quitté la portée ou être modifié davantage
Vous devrez copier le contenu du string
x
dans une nouvelle zone de mémoire à l'extérieur x
. Ce tampon externe peut être à de nombreux endroits, comme une autre string
variable de tableau de caractères, il peut ou non avoir une durée de vie différente de celle x
due à être dans une portée différente (par exemple, espace de noms, global, statique, 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'unstring
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 en faire ? Une poignée d'exemples aléatoires ...
- donne accès au code "C" au
string
texte du C ++ , comme dansprintf("x is '%s'", x.c_str());
- copier
x
le texte dans un tampon spécifié par l'appelant de votre fonction (par exemple strncpy(callers_buffer, callers_buffer_size, x.c_str())
), ou une mémoire volatile utilisée pour les E / S du périphérique (par exemple for (const char* p = x.c_str(); *p; ++p) *p_device = *p;
)
- ajouter
x
le texte à un tableau de caractères contenant déjà du texte ASCIIZ (par exemple strcat(other_buffer, x.c_str())
) - attention à ne pas surcharger le tampon (dans de nombreuses situations, vous devrez peut-être utiliser strncat
)
- renvoyer un
const char*
ou à char*
partir d'une fonction (peut-être pour des raisons historiques - le client utilise votre API existante - ou pour la compatibilité C, vous ne voulez pas retourner un std::string
, mais vous voulez copier vos string
données quelque part pour l'appelant)
- veillez à ne pas renvoyer un pointeur qui pourrait être déréférencé par l'appelant après qu'une
string
variable locale vers laquelle ce pointeur pointé a laissé la portée
- certains projets avec des objets partagés compilés / liés pour différentes
std::string
implémentations (par exemple STLport et natif du compilateur) peuvent transmettre des données en ASCIIZ pour éviter les conflits