Dato dire ...
std::string x = "hello";
Ottenere un `char *` o `const char *` da una `stringa`
Come ottenere un puntatore a caratteri valido mentre xrimane nell'ambito e non viene ulteriormente modificato
C ++ 11 semplifica le cose; tutti i seguenti danno accesso allo stesso buffer di stringhe interno:
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...
Tutti i puntatori sopra avranno lo stesso valore : l'indirizzo del primo carattere nel buffer. Anche una stringa vuota ha un "primo carattere nel buffer", poiché C ++ 11 garantisce di mantenere sempre un carattere di terminazione NUL / 0 aggiuntivo dopo il contenuto della stringa assegnato in modo esplicito (ad esempio std::string("this\0that", 9), avrà un buffer "this\0that\0").
Dato uno dei puntatori sopra:
char c = p[n]; // valid for n <= x.size()
// i.e. you can safely read the NUL at p[x.size()]
Solo per il non constpuntatore p_writable_datae da &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
Scrivi NUL altrove nella stringa non non cambia il strings' size(); stringpossono contenere un numero qualsiasi di NUL - non ricevono alcun trattamento speciale da std::string(lo stesso in C ++ 03).
In C ++ 03 , le cose erano considerevolmente più complicate (differenze chiave evidenziate ):
x.data()
- ritorna
const char*al buffer interno della stringa che non era richiesto dallo Standard per concludere con un NUL (cioè potrebbe essere ['h', 'e', 'l', 'l', 'o']seguito da valori non inizializzati o di immondizia, con accessi accidentali ad esso con comportamento indefinito ).
x.size()i personaggi sono sicuri da leggere, cioè x[0]attraversox[x.size() - 1]
- per stringhe vuote, hai la garanzia di un puntatore diverso da NULL a cui è possibile aggiungere in modo sicuro 0 (evviva!), ma non dovresti dedurre tale puntatore.
&x[0]
- per stringhe vuote questo ha un comportamento indefinito (21.3.4)
- ad es. dato
f(const char* p, size_t n) { if (n == 0) return; ...whatever... }che non devi chiamare f(&x[0], x.size());quando x.empty(): basta usare f(x.data(), ...).
- altrimenti, come da
x.data()ma:
- in caso contrario,
const xsi ottiene un non const char*puntatore; puoi sovrascrivere il contenuto delle stringhe
x.c_str()
- ritorna
const char*a una rappresentazione ASCIIZ (terminata con NUL) del valore (cioè ['h', 'e', 'l', 'l', 'o', '\ 0']).
- sebbene poche o nessuna implementazione abbia scelto di farlo, lo standard C ++ 03 è stato formulato per consentire all'implementazione della stringa la libertà di creare al volo un buffer distinto NUL distinto , dal buffer terminato potenzialmente non NUL "esposto" da e
x.data()&x[0]
x.size() + 1 caratteri sono sicuri da leggere.
- garantito sicuro anche per stringhe vuote (['\ 0']).
Conseguenze dell'accesso a indici legali esterni
Indipendentemente dal modo in cui si ottiene un puntatore, non è necessario accedere alla memoria oltre il puntatore rispetto ai caratteri garantiti presenti nelle descrizioni sopra. I tentativi di farlo hanno un comportamento indefinito , con una reale possibilità di arresti anomali dell'applicazione e risultati di immondizia anche per le letture e inoltre dati all'ingrosso, corruzione dello stack e / o vulnerabilità di sicurezza per le scritture.
Quando vengono invalidati quei puntatori?
Se si chiama una stringfunzione membro che modifica stringo riserva ulteriore capacità, tutti i valori del puntatore restituiti in anticipo da uno dei metodi sopra indicati vengono invalidati . È possibile utilizzare nuovamente questi metodi per ottenere un altro puntatore. (Le regole sono le stesse per gli iteratori in strings).
Vedi anche Come ottenere un puntatore a caratteri valido anche dopo aver xlasciato l'ambito o modificato ulteriormente di seguito ....
Quindi, quale è meglio usare?
Da C ++ 11, utilizzare .c_str()per i dati ASCIIZ e .data()per i dati "binari" (spiegato più avanti).
In C ++ 03, utilizzare a .c_str()meno che non .data()sia adeguato, e preferire .data()oltre &x[0]perché è sicuro per stringhe vuote ....
... cerca di capire il programma abbastanza da usare data()quando appropriato, o probabilmente commetterai altri errori ...
Il carattere ASCII NUL '\ 0' garantito da .c_str()viene utilizzato da molte funzioni come valore sentinella che indica la fine dei dati rilevanti e di accesso sicuro. Questo vale sia per C ++ - solo funzioni come say fstream::fstream(const char* filename, ...)e funzioni condivise con C come strchr(), e printf().
Date le .c_str()garanzie di C ++ 03 sul buffer restituito sono un super set di .data(), puoi sempre usare in sicurezza .c_str(), ma le persone a volte no perché:
- l'utilizzo
.data()comunica ad altri programmatori che leggono il codice sorgente che i dati non sono ASCIIZ (piuttosto, stai usando la stringa per memorizzare un blocco di dati (che a volte non è nemmeno realmente testuale)), o che lo stai passando a un'altra funzione che lo considera come un blocco di dati "binari". Questa può essere una visione cruciale per garantire che le modifiche al codice di altri programmatori continuino a gestire correttamente i dati.
- Solo C ++ 03: esiste una leggera possibilità che l'
stringimplementazione necessiti di allocazione di memoria aggiuntiva e / o copia dei dati per preparare il buffer terminato NUL
Come ulteriore suggerimento, se i parametri di una funzione richiedono il ( const) char*ma non insistono per ottenere x.size(), la funzione probabilmente ha bisogno di un input ASCIIZ, quindi .c_str()è una buona scelta (la funzione deve sapere dove il testo termina in qualche modo, quindi se non lo è un parametro separato può essere solo una convenzione come un prefisso di lunghezza o sentinella o una lunghezza prevista fissa).
Come ottenere un puntatore a caratteri valido anche dopo aver xlasciato l'ambito o modificato ulteriormente
Dovrai copiare il contenuto di string xuna nuova area di memoria all'esterno x. Questo buffer esterno potrebbe trovarsi in molti punti come un altro stringo una variabile di array di caratteri, potrebbe avere o meno una durata diversa da quella xdi essere in un ambito diverso (ad esempio spazio dei nomi, globale, statico, heap, memoria condivisa, file mappato in memoria) .
Per copiare il testo da std::string xin una matrice di caratteri indipendente:
// 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);
Altri motivi per volere un char*o const char*generato da unstring
Quindi, sopra hai visto come ottenere un ( const) char*e come rendere una copia del testo indipendente dall'originale string, ma cosa puoi farci ? Una sbandata casuale di esempi ...
- dare accesso al codice "C" al testo del C ++
string, come inprintf("x is '%s'", x.c_str());
- copia
xil testo in un buffer specificato dal chiamante della tua funzione (ad es. strncpy(callers_buffer, callers_buffer_size, x.c_str())) o memoria volatile utilizzata per l'I / O del dispositivo (ad es. for (const char* p = x.c_str(); *p; ++p) *p_device = *p;)
- aggiungi
xil testo a una matrice di caratteri che contiene già del testo ASCIIZ (ad es. strcat(other_buffer, x.c_str())) - fai attenzione a non sovraccaricare il buffer (in molte situazioni potresti dover usare strncat)
- restituire una
const char*o char*da una funzione (forse per motivi storici - il client utilizza l'API esistente - o per compatibilità C non si desidera restituire una std::string, ma si desidera copiare i propri stringdati da qualche parte per il chiamante)
- fare attenzione a non restituire un puntatore che può essere dereferenziato dal chiamante dopo una
stringvariabile locale a cui quel puntatore ha puntato ha lasciato l'ambito
- alcuni progetti con oggetti condivisi compilati / collegati per diverse
std::stringimplementazioni (ad es. STLport e nativo del compilatore) possono trasmettere dati come ASCIIZ per evitare conflitti