Dato dire ...
std::string x = "hello";
Ottenere un `char *` o `const char *` da una `stringa`
Come ottenere un puntatore a caratteri valido mentre x
rimane 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 const
puntatore p_writable_data
e 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 string
s' size()
; string
possono 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
x
si 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 string
funzione membro che modifica string
o 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 string
s).
Vedi anche Come ottenere un puntatore a caratteri valido anche dopo aver x
lasciato 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'
string
implementazione 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 x
lasciato l'ambito o modificato ulteriormente
Dovrai copiare il contenuto di string
x
una nuova area di memoria all'esterno x
. Questo buffer esterno potrebbe trovarsi in molti punti come un altro string
o una variabile di array di caratteri, potrebbe avere o meno una durata diversa da quella x
di 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 x
in 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
x
il 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
x
il 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 string
dati da qualche parte per il chiamante)
- fare attenzione a non restituire un puntatore che può essere dereferenziato dal chiamante dopo una
string
variabile locale a cui quel puntatore ha puntato ha lasciato l'ambito
- alcuni progetti con oggetti condivisi compilati / collegati per diverse
std::string
implementazioni (ad es. STLport e nativo del compilatore) possono trasmettere dati come ASCIIZ per evitare conflitti