Biorąc pod uwagę powiedz ...
std::string x = "hello";
Uzyskiwanie `char *` lub `const char *` z `string`
Jak uzyskać wskaźnik znaku, który jest prawidłowy, dopóki x
pozostaje w zasięgu i nie jest dalej modyfikowany
C ++ 11 upraszcza rzeczy; następujące zapewniają dostęp do tego samego wewnętrznego bufora ciągów:
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...
Wszystkie powyższe wskaźniki będą miały tę samą wartość - adres pierwszego znaku w buforze. Nawet pusty ciąg ma „pierwszy znak w buforze”, ponieważ C ++ 11 gwarantuje, że zawsze zachowa dodatkowy znak terminatora NUL / 0 po jawnie przypisanej zawartości ciągu (np. std::string("this\0that", 9)
Będzie miał bufor "this\0that\0"
).
Biorąc pod uwagę którykolwiek z powyższych wskaźników:
char c = p[n]; // valid for n <= x.size()
// i.e. you can safely read the NUL at p[x.size()]
Tylko dla non- const
point p_writable_data
i od &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
Napisanie NUL gdzie indziej w ciągu nie zmienia string
's size()
; string
mogą zawierać dowolną liczbę wartości NUL - nie są traktowane przez std::string
(tak samo jak w C ++ 03).
W C ++ 03 rzeczy były znacznie bardziej skomplikowane ( podkreślono kluczowe różnice ):
x.data()
- wraca
const char*
do wewnętrznego bufora łańcucha, który nie był wymagany przez Standard do zakończenia z NUL (tzn. mogą wystąpić ['h', 'e', 'l', 'l', 'o']
wartości niezainicjowane lub śmieci, z przypadkowym dostępem do nich o nieokreślonym zachowaniu ).
x.size()
znaki są bezpieczne do odczytania, tzn. x[0]
poprzezx[x.size() - 1]
- w przypadku pustych ciągów masz gwarancję, że wskaźnik nie jest równy NULL, do którego można bezpiecznie dodać 0 (hurra!), ale nie powinieneś odrywać tego wskaźnika.
&x[0]
- w przypadku pustych ciągów zachowanie to jest niezdefiniowane (21.3.4)
- np. biorąc pod uwagę,
f(const char* p, size_t n) { if (n == 0) return; ...whatever... }
że nie wolno dzwonić, f(&x[0], x.size());
kiedy x.empty()
- po prostu użyj f(x.data(), ...)
.
- w przeciwnym razie, zgodnie z,
x.data()
ale:
- dla nie-
const
x
daje to const
char*
wskaźnik niepowodujący; możesz zastąpić treść ciągu
x.c_str()
- powraca
const char*
do reprezentacji wartości ASCIIZ (zakończonej przez NUL) (tj. [„h”, „e”, „l”, „l”, „o”, „\ 0”]).
- chociaż niewiele, jeśli jakieś implementacje zdecydowały się to zrobić, standard C ++ 03 został sformułowany tak, aby umożliwić implementacji łańcucha swobodę tworzenia w locie odrębnego bufora zakończonego NUL , z potencjalnie nie zakończonego NUL bufora „odsłoniętego” przez i
x.data()
&x[0]
x.size()
+ 1 znaki są bezpieczne do odczytania.
- gwarantowane bezpieczeństwo nawet dla pustych ciągów (['\ 0']).
Konsekwencje dostępu do zewnętrznych wskaźników prawnych
Niezależnie od tego, w jaki sposób uzyskasz wskaźnik, nie możesz uzyskać dostępu do pamięci dalej od wskaźnika niż znaki gwarantowane obecne w powyższych opisach. Próby tego mają nieokreślone zachowanie , z bardzo realną szansą na awarie aplikacji i śmieciowe wyniki nawet dla odczytów, a dodatkowo hurtową sprzedaż danych, uszkodzenie stosu i / lub luki w zabezpieczeniach zapisu.
Kiedy wskaźniki te zostaną unieważnione?
W przypadku wywołania string
funkcji członkowskiej, która modyfikuje string
lub rezerwuje dodatkową pojemność, wszelkie wartości wskaźnika zwrócone wcześniej za pomocą dowolnej z powyższych metod zostaną unieważnione . Możesz użyć tych metod ponownie, aby uzyskać inny wskaźnik. (Zasady są takie same, jak dla iteratorów na string
s).
Zobacz także Jak uzyskać poprawność wskaźnika znaku nawet po x
opuszczeniu zasięgu lub dalszej modyfikacji poniżej ...
Którego lepiej użyć?
Od C ++ 11 używaj .c_str()
danych ASCIIZ i danych .data()
„binarnych” (wyjaśnione poniżej).
W C ++ 03, korzystanie .c_str()
chyba pewne, że .data()
jest odpowiednia, i wolą .data()
nad &x[0]
jak to jest bezpieczne dla pustych strunach ....
... postaraj się zrozumieć program na tyle, aby użyć go w data()
razie potrzeby, w przeciwnym razie prawdopodobnie popełnisz inne błędy ...
Znak ASCII NUL „\ 0” gwarantowany przez .c_str()
jest używany przez wiele funkcji jako wartość wartownika oznaczająca koniec odpowiednich i bezpiecznych danych. Dotyczy to zarówno funkcji C ++, takich jak say, jak fstream::fstream(const char* filename, ...)
i funkcji współdzielonych z C, takich jak strchr()
i printf()
.
Biorąc pod uwagę, .c_str()
że gwarancje C ++ 03 dotyczące zwróconego bufora są super zestawem .data()
, zawsze możesz bezpiecznie używać .c_str()
, ale ludzie czasami nie, ponieważ:
- używanie
.data()
komunikuje się innym programistom odczytującym kod źródłowy, że dane nie są ASCIIZ (raczej używasz ciągu do przechowywania bloku danych (który czasami nie jest nawet tekstowy)), lub że przekazujesz go do kolejna funkcja, która traktuje to jako blok danych „binarnych”. Może to być kluczowy wgląd w zapewnienie, że zmiany kodu innych programistów będą nadal poprawnie obsługiwać dane.
- Tylko C ++ 03: istnieje niewielka szansa, że twoja
string
implementacja będzie musiała dokonać dodatkowej alokacji pamięci i / lub kopiowania danych, aby przygotować bufor zakończony NUL
Jako kolejna wskazówka, jeśli parametry funkcji wymagają ( const
), char*
ale nie nalegają na uzyskanie x.size()
, funkcja prawdopodobnie wymaga wejścia ASCIIZ, więc .c_str()
jest to dobry wybór (funkcja musi wiedzieć, gdzie tekst się kończy, więc jeśli nie jest oddzielnym parametrem może być tylko konwencja, taka jak prefiks długości lub wartownik lub niektóre ustalone oczekiwane długości).
Jak uzyskać poprawność wskaźnika znaku nawet po x
opuszczeniu zasięgu lub dalszej modyfikacji
Musisz skopiować zawartość string
x
do nowego obszaru pamięci na zewnątrz x
. Ten bufor zewnętrzny może znajdować się w wielu miejscach, takich jak inna string
zmienna lub tablica znaków, może mieć inny okres istnienia niż x
w innym zakresie (np. Przestrzeń nazw, globalna, statyczna, sterty, pamięć współdzielona, plik odwzorowany w pamięci) .
Aby skopiować tekst z std::string x
niezależnej tablicy znaków:
// 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);
Inne powody, aby chcieć char*
lub const char*
wygenerowane zstring
Powyżej widziałeś, jak uzyskać ( const
) char*
i jak zrobić kopię tekstu niezależną od oryginału string
, ale co możesz z tym zrobić ? Losowe rozproszenie przykładów ...
- daj kodowi „C” dostęp do
string
tekstu C ++ , jak wprintf("x is '%s'", x.c_str());
- skopiuj
x
tekst do bufora określonego przez program wywołujący twoją funkcję (np. strncpy(callers_buffer, callers_buffer_size, x.c_str())
) lub pamięć ulotną używaną dla urządzeń I / O (np. for (const char* p = x.c_str(); *p; ++p) *p_device = *p;
)
- Dołącz
x
dydaktycznego tekst do tablicy znaków zawierający już jakiś tekst ASCIIZ (np strcat(other_buffer, x.c_str())
) - uważać, by nie przekroczyć bufor (w wielu sytuacjach może zajść potrzeba użycia strncat
)
- zwraca a
const char*
lub char*
z funkcji (być może z powodów historycznych - klient używa istniejącego API - lub ze względu na kompatybilność z C nie chcesz zwracać a std::string
, ale chcesz skopiować string
dane gdzieś dla dzwoniącego)
- uważaj, aby nie zwrócić wskaźnika, który może zostać odwołany przez wywołującego po
string
zmiennej lokalnej , na którą wskazany wskaźnik opuścił zakres
- niektóre projekty ze skompilowanymi / połączonymi obiektami dla różnych
std::string
implementacji (np. STLport i natywny dla kompilatora) mogą przekazywać dane jako ASCIIZ, aby uniknąć konfliktów