2009-12-31 6 views
31

sto facendo alcuni lavori di manutenzione e sono imbattuto in qualcosa di simile al seguente:"& s [0]" punta a caratteri contigui in una stringa: std ::?

std::string s; 
s.resize(strLength); 
// strLength is a size_t with the length of a C string in it. 

memcpy(&s[0], str, strLength); 

So utilizzando & s [0] sarebbe stato sicuro se fosse uno std :: vector, ma questo è un uso sicuro di std :: string?

+3

L'uso di & s [0] è OK, memcpy() probabilmente meno. Perché non eseguire semplicemente un compito o utilizzare la funzione membro assign() della stringa? –

+1

@Neil Butterworth, questo è quello che mi sto chiedendo guardando questo codice ...;) – paxos1977

+0

Man mano che acquisisci esperienza nella programmazione in C++, ti asterrai sempre più dall'uso di 'memset' e' memcpy', e imparerai il ragionamento Questo è uno da aggiungere alla tua esperienza. –

risposta

34

Un'assegnazione di std :: string non è garantita per essere contigui secondo lo standard C++ 98/03, ma C++ 11 lo impone. In pratica, né I né Herb Sutter sono a conoscenza di un'implementazione che non utilizza archiviazione contigua.

Si noti che la funzione &s[0] è sempre garantita dallo standard C++ 11, anche nel caso di stringa di lunghezza 0. Non sarebbe garantito se avete fatto str.begin() o &*str.begin(), ma per &s[0] lo standard definisce operator[] come:

Returns: *(begin() + pos) se pos < size(), altrimenti un riferimento a un oggetto di tipo T con valore charT(); il valore di riferimento non deve essere modificata

Proseguendo, data() è definito come:

Restituisce: Un puntatore p tale che per ogni p + i == &operator[](i)i in [0,size()].

(notare le parentesi quadre ad entrambe le estremità della gamma)


Avviso: pre-standardizzazione C++ 0x non garantiva &s[0] a lavorare con le stringhe di lunghezza zero (in realtà, era un comportamento esplicitamente indefinito) e una revisione più vecchia di questa risposta spiegava questo; questo è stato corretto in bozze standard successive, quindi la risposta è stata aggiornata di conseguenza.

+0

Non ho seguito lo standard per gli ultimi mesi, ma ho avuto l'impressione che fosse ancora nella bozza 0x e quindi non ancora richiesto (o lo sarà se una libreria sceglie di implementare solo '03). –

+3

Sutter dice in un commento a questo post, "l'attuale ISO C++ richiede & str [0] di criptare un puntatore a dati di stringa contigui (ma non necessariamente a terminazione nulla!)", Che in effetti renderebbero corretto l'utilizzo dell'OP. Tuttavia, non riesco a trovare nulla che lo dica nello standard (almeno non è in 21.3.4 lib.string.access). –

+0

Penso che potrebbe essere giusto; il difetto std 530 dice che l'operatore [] è contiguo ma l'interfaccia iteratore non è garantito e cita 23.4.4. Sto scavando il mio standard per controllare. –

6

Tecnicamente, no, dal std::string non è necessario per archiviarne il contenuto in modo contiguo nella memoria.

Tuttavia, in quasi tutte le implementazioni (tutte le implementazioni di cui sono a conoscenza), i contenuti vengono archiviati in modo contiguo e questo "funzionerebbe".

+0

Puoi identificare alcune implementazioni in cui non avrebbe funzionato? –

+2

No. Ma potresti realizzare un'implementazione del genere se lo volessi. –

+0

@Neil: hai un link/riferimento a quel TC? –

2

I lettori devono notare che questa domanda è stata posta nel 2009, quando lo standard C++ 03 era la pubblicazione corrente. Questa risposta è basata su quella versione dello standard, in cui std::string s è non garantito per l'utilizzo di archiviazione contigua. Dato che questa domanda non è stata posta nel contesto di una piattaforma particolare (come gcc), non faccio ipotesi sulla piattaforma di OP - in particolare, tempo o non ha utilizzato memorizzazione contagiosa per lo string.

Note legali? Forse sì forse no. Sicuro? Probabilmente, ma forse no. Buon codice? Bene, non andiamo là ...

Perché non basta fare:

std::string s = str; 

... o:

std::string s(str); 

... o:

std::string s; 
std::copy(&str[0], &str[strLen], std::back_inserter(s)); 

... o:

std::string s; 
s.assign(str, strLen); 

?

+0

o s.assign (str, strLen); –

+0

buono, aggiornato con assegnazione –

+1

'std :: string s (str, strLen);' (La forma più breve identica, in caso di null incorporati o mancante di terminazione null, al comportamento originale della domanda.) –

0

Questo è generalmente non sicuro, indipendentemente dal fatto che la sequenza di stringhe interna sia archiviata in memoria in modo continuo o meno. Potrebbero esserci molti altri dettagli di implementazione relativi al modo in cui la sequenza controllata viene archiviata dall'oggetto std::string, oltre alla continuità.

Un vero problema pratico potrebbe essere il seguente. La sequenza controllata di std::string non deve essere memorizzata come stringa a terminazione zero. Tuttavia, in pratica, molte (la maggior parte?) Implementazioni scelgono di sovradimensionare il buffer interno di 1 e memorizzano comunque la sequenza come una stringa a terminazione zero perché semplifica l'implementazione del metodo c_str(): basta restituire un puntatore al buffer interno e si fatto.

Il codice indicato nella domanda non fa alcuno sforzo per terminare a zero i dati copiati nel buffer interno. Molto probabilmente semplicemente non sa se è necessaria la terminazione zero in questa implementazione di std::string. Molto probabilmente si basa sul buffer interno riempito di zeri dopo la chiamata a resize, quindi il carattere extra assegnato per lo zero-terminator dall'implementazione è convenientemente preimpostato su zero. Tutto questo è dettaglio di implementazione, il che significa che questa tecnica dipende da alcune ipotesi piuttosto fragili.

In altre parole, in alcune implementazioni probabilmente dovresti usare strcpy, non memcpy per forzare i dati nella sequenza controllata del genere. Mentre in alcune altre implementazioni dovresti usare memcpy e non strcpy.

+1

Dopo la chiamata a "ridimensionare", si può essere certi che la stringa interna sia o non sia terminata in modo nullo come richiede l'implementazione. Dopo una chiamata a "ridimensionare", dopo tutto è necessario avere una stringa valida di n caratteri (riempita con zero caratteri se necessario). - Tuttavia, mostra una mancanza di comprensione per la classe 'std :: string': memcpy viene usata per ignoranza o come tentativo errato di esecuzione (a causa della chiamata' resize' il codice finisce per assegnare valori al buffer due volte). – UncleBens

+0

@UncleBens: non capisco la tua prima frase. In ogni caso, sì, lo standard della lingua garantisce che il call ridimensiona la dimensione aumentando la stringa con zero. Tuttavia, lo standard garantisce il riempimento solo fino alla dimensione richiesta ('strLength' in questo caso), ma non vi è alcuna garanzia nello standard per quel carattere extra, se l'implementazione ne assegna uno. – AnT

0

Il codice potrebbe funzionare, ma più per fortuna che per giudizio, fa supposizioni sull'implementazione che non sono garantite. Suggerisco di determinare la validità del codice è irrilevante, mentre si tratta di una inutile complicazione sopra che può essere facilmente ridotta a poco:

std::string s(str) ; 

o se assegnare a un oggetto std :: stringa esistente, basta:

s = str ; 

e quindi lasciare che std :: string stesso determini come ottenere il risultato. Se hai intenzione di ricorrere a questo genere di assurdità, allora potresti anche non usare std :: string e stick in quanto stai reintroducendo tutti i pericoli associati alle stringhe C.

+0

In realtà non posso essere sicuro che la stringa assegnata sia terminata con null. Quindi il meglio che potrei fare sarà probabilmente s.assign (ptr, ptrLength); che è ancora un miglioramento, penso. – paxos1977

+0

Utilizza il modulo di costruzione: 'std :: string s (str, strLen);' – GManNickG

6

È sicuro da usare. Penso che la maggior parte delle risposte siano corrette una volta, ma lo standard è cambiato. Citando dal C++ 11 di serie, basic_string requisiti generali [string.require], 21.4.1.5, dice:

Gli oggetti char-come in un oggetto basic_string devono essere conservati in modo contiguo.Cioè, per qualsiasi basic_string oggetto s, l'identità & * (s.begin() + n) == & * s.begin() + n deve valere per tutti i valori di n tale che 0 < = n < s .dimensione().

Un po 'prima, dice che tutti gli iteratori sono iteratori ad accesso casuale. Entrambi i bit supportano l'uso della tua domanda. (Inoltre, Stroustrup lo usa apparentemente nel suo ultimo libro;))

Non è improbabile che questo cambiamento sia stato fatto in C++ 11. Mi sembra di ricordare che la stessa garanzia è stata aggiunta per il vettore, che ha anche ottenuto l'utilissimo puntatore data() con quella versione.

Spero che questo aiuti.

+2

La domanda era pre-C++ 11 (è contrassegnata come tale). Hai ragione, C++ 11 ha reso ufficialmente sicuro farlo. – paxos1977