Gegeben sagen ...
std::string x = "hello";
Abrufen eines "char *" oder "const char *" aus einem "String"
So erhalten Sie einen Zeichenzeiger, der gültig ist, während er x
im Gültigkeitsbereich bleibt und nicht weiter geändert wird
C ++ 11 vereinfacht die Dinge; Die folgenden Optionen bieten Zugriff auf denselben internen Zeichenfolgenpuffer:
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...
Alle oben genannten Zeiger enthalten denselben Wert - die Adresse des ersten Zeichens im Puffer. Sogar eine leere Zeichenfolge hat ein "erstes Zeichen im Puffer", da C ++ 11 garantiert, dass nach dem explizit zugewiesenen Zeichenfolgeninhalt immer ein zusätzliches NUL / 0-Abschlusszeichen beibehalten wird (z. B. std::string("this\0that", 9)
wird ein Puffer gespeichert "this\0that\0"
).
Angesichts eines der oben genannten Hinweise:
char c = p[n]; // valid for n <= x.size()
// i.e. you can safely read the NUL at p[x.size()]
Nur für den nicht - const
Zeiger p_writable_data
und aus &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
Das Schreiben eines NUL an einer anderen Stelle in der Zeichenfolge ändert nichts an den string
's size()
; string
's dürfen eine beliebige Anzahl von NULs enthalten - sie werden von nicht speziell behandelt std::string
(wie in C ++ 03).
In C ++ 03 waren die Dinge erheblich komplizierter (wichtige Unterschiede hervorgehoben ):
x.data()
- kehrt
const char*
zum internen Puffer der Zeichenfolge zurück , der vom Standard nicht benötigt wurde, um mit einem NUL abzuschließen (dh es können nicht ['h', 'e', 'l', 'l', 'o']
initialisierte oder Garbage-Werte folgen, wobei versehentliche Zugriffe darauf ein undefiniertes Verhalten aufweisen ).
x.size()
Zeichen sind sicher zu lesen, dh x[0]
durchx[x.size() - 1]
- Für leere Zeichenfolgen ist ein Nicht-NULL-Zeiger garantiert, zu dem 0 sicher hinzugefügt werden kann (Hurra!), aber Sie sollten diesen Zeiger nicht dereferenzieren.
&x[0]
- für leere Strings hat dies ein undefiniertes Verhalten (21.3.4)
- zB gegeben
f(const char* p, size_t n) { if (n == 0) return; ...whatever... }
darf man nicht anrufen f(&x[0], x.size());
wann x.empty()
- einfach benutzen f(x.data(), ...)
.
- ansonsten wie folgt
x.data()
aber:
- für nicht
const
x
ergibt dies einen Nichtzeiger const
char*
; Sie können den Inhalt von Zeichenfolgen überschreiben
x.c_str()
- kehrt
const char*
zu einer ASCIIZ-Darstellung (NUL-terminiert) des Werts zurück (dh ['h', 'e', 'l', 'l', 'o', '\ 0']).
- obwohl nur wenige , wenn irgendwelche Implementierungen dies wünschen, die C ++ 03 Standard - Wortlaut wurde der String Umsetzung die Freiheit zu ermöglichen , eine zu schaffen verschiedene NUL-terminierten Puffer im Fluge , von der potenziell nicht-NUL beendet „ausgesetzt“ Puffer durch
x.data()
und&x[0]
x.size()
+ 1 Zeichen sind sicher zu lesen.
- garantiert sicher auch für leere Strings (['\ 0']).
Folgen des Zugriffs auf externe Rechtsindizes
Unabhängig davon, wie Sie einen Zeiger erhalten, dürfen Sie nicht weiter vom Zeiger entfernt auf den Speicher zugreifen als auf die in den obigen Beschreibungen garantierten Zeichen. Versuche, dies zu tun, haben ein undefiniertes Verhalten , mit einer sehr realen Wahrscheinlichkeit von Anwendungsabstürzen und Müllergebnissen, selbst bei Lesevorgängen, und zusätzlich Großhandelsdaten, Stapelbeschädigungen und / oder Sicherheitslücken für Schreibvorgänge.
Wann werden diese Zeiger ungültig?
Wenn Sie eine Elementfunktion aufrufen string
, die die string
Kapazität ändert oder weitere Kapazität reserviert, werden alle zuvor von einer der oben genannten Methoden zurückgegebenen Zeigerwerte ungültig . Sie können diese Methoden erneut verwenden, um einen weiteren Zeiger abzurufen. (Die Regeln sind die gleichen wie für Iteratoren in string
s).
Siehe auch So erhalten Sie einen gültigen Zeichenzeiger, auch wenn er den Gültigkeitsbereich x
verlässt oder weiter unten geändert wird ....
Also, was ist besser zu verwenden?
Verwenden .c_str()
Sie ab C ++ 11 ASCIIZ-Daten und .data()
"binäre" Daten (weiter unten erläutert).
Verwenden Sie in C ++ 03, .c_str()
sofern dies nicht .data()
ausreichend ist, und ziehen .data()
Sie es vor, &x[0]
da es für leere Zeichenfolgen sicher ist.
... versuchen Sie, das Programm so gut zu verstehen, data()
dass es gegebenenfalls verwendet werden kann, oder Sie werden wahrscheinlich andere Fehler machen ...
Das von garantierte ASCII-NUL-Zeichen '\ 0' .c_str()
wird von vielen Funktionen als Sentinel-Wert verwendet, der das Ende relevanter und für den Zugriff sicherer Daten angibt. Dies gilt sowohl für C ++ - nur Funktionen wie say fstream::fstream(const char* filename, ...)
und Funktionen, die mit C geteilt werden, wie strchr()
, und printf()
.
Angesichts der Tatsache .c_str()
, dass die Garantien von C ++ 03 für den zurückgegebenen Puffer sehr hoch sind .data()
, können Sie sie immer sicher verwenden .c_str()
, aber manchmal nicht, weil:
- Die Verwendung von
.data()
kommuniziert mit anderen Programmierern, die den Quellcode lesen, dass die Daten nicht ASCIIZ sind (stattdessen verwenden Sie die Zeichenfolge zum Speichern eines Datenblocks (der manchmal nicht einmal wirklich textuell ist)) oder an den Sie ihn weitergeben eine weitere Funktion, die es als Block von "binären" Daten behandelt. Dies kann eine wichtige Erkenntnis sein, um sicherzustellen, dass die Codeänderungen anderer Programmierer die Daten weiterhin ordnungsgemäß verarbeiten.
- Nur C ++ 03: Es besteht eine geringe Wahrscheinlichkeit, dass Ihre
string
Implementierung zusätzliche Speicherzuweisung und / oder Datenkopie durchführen muss, um den NUL-terminierten Puffer vorzubereiten
Als weiterer Hinweis: Wenn die Parameter einer Funktion das ( const
) erfordern, char*
aber nicht darauf bestehen, dass sie abgerufen werden x.size()
, benötigt die Funktion wahrscheinlich eine ASCIIZ-Eingabe. Dies .c_str()
ist eine gute Wahl (die Funktion muss wissen, wo der Text irgendwie endet, wenn dies nicht der Fall ist Als separater Parameter kann es sich nur um eine Konvention wie ein Längenpräfix oder einen Sentinel oder eine feste erwartete Länge handeln.
So erhalten Sie einen Zeichenzeiger, der auch dann gültig ist, wenn er den Gültigkeitsbereich x
verlässt oder weiter geändert wird
Sie müssen den Inhalt von in einen neuen Speicherbereich außerhalb kopieren . Dieser externe Puffer kann sich an vielen Stellen befinden, z. B. in einer anderen oder einer Zeichenarray-Variablen. Er kann eine andere Lebensdauer haben oder nicht als aufgrund eines anderen Bereichs (z. B. Namespace, global, statisch, Heap, gemeinsam genutzter Speicher, Speicherzuordnungsdatei). .string
x
x
string
x
So kopieren Sie den Text std::string x
in ein unabhängiges Zeichenarray:
// 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);
Andere Gründe, um ein zu wollen char*
oder const char*
aus einem generiertstring
Oben haben Sie gesehen, wie Sie ein ( const
) erhalten char*
und wie Sie eine Kopie des Textes unabhängig vom Original string
erstellen. Aber was können Sie damit tun ? Ein paar Beispiele ...
- Geben Sie "C" -Code Zugriff auf den
string
Text von C ++ , wie inprintf("x is '%s'", x.c_str());
- Kopieren Sie
x
den Text in einen Puffer, der vom Aufrufer Ihrer Funktion angegeben wurde (z. B. strncpy(callers_buffer, callers_buffer_size, x.c_str())
), oder in einen flüchtigen Speicher, der für Geräte-E / A verwendet wird (z. B. for (const char* p = x.c_str(); *p; ++p) *p_device = *p;
).
- Hängen Sie
x
den Text an ein Zeichenarray an, das bereits ASCIIZ-Text enthält (z. B. strcat(other_buffer, x.c_str())
). Achten Sie darauf, den Puffer nicht zu überlaufen (in vielen Situationen müssen Sie ihn möglicherweise verwenden strncat
).
- eine
const char*
oder char*
von einer Funktion zurückgeben (möglicherweise aus historischen Gründen - der Client verwendet Ihre vorhandene API - oder aus C-Kompatibilität möchten Sie keine zurückgeben std::string
, aber Ihre string
Daten für den Anrufer irgendwo kopieren )
- Achten Sie darauf, keinen Zeiger zurückzugeben, der vom Aufrufer möglicherweise dereferenziert wird, nachdem eine lokale
string
Variable, auf die dieser Zeiger zeigt, den Gültigkeitsbereich verlassen hat
- Einige Projekte mit gemeinsam genutzten Objekten, die für verschiedene
std::string
Implementierungen kompiliert / verknüpft wurden (z. B. STLport und Compiler-native), übergeben möglicherweise Daten als ASCIIZ, um Konflikte zu vermeiden