Come si costruisce uno std :: string con un null incorporato?


88

Se voglio costruire uno std :: string con una riga come:

std::string my_string("a\0b");

Dove voglio avere tre caratteri nella stringa risultante (a, null, b), ne ottengo solo uno. Qual è la sintassi corretta?


4
Dovrai stare attento con questo. Se sostituisci "b" con qualsiasi carattere numerico, creerai silenziosamente la stringa sbagliata. Vedi: stackoverflow.com/questions/10220401/…
David Stone

Risposte:


128

Dal momento che C ++ 14

siamo stati in grado di creare letterale std::string

#include <iostream>
#include <string>

int main()
{
    using namespace std::string_literals;

    std::string s = "pl-\0-op"s;    // <- Notice the "s" at the end
                                    // This is a std::string literal not
                                    // a C-String literal.
    std::cout << s << "\n";
}

Prima di C ++ 14

Il problema è che il std::stringcostruttore che assume a const char*presuppone che l'input sia una stringa C. Le stringhe C vengono \0terminate e quindi l'analisi si interrompe quando raggiunge il \0carattere.

Per compensare ciò, è necessario utilizzare il costruttore che costruisce la stringa da un array di caratteri (non una C-String). Questo richiede due parametri: un puntatore all'array e una lunghezza:

std::string   x("pq\0rs");   // Two characters because input assumed to be C-String
std::string   x("pq\0rs",5); // 5 Characters as the input is now a char array with 5 characters.

Nota: C ++ NONstd::string è terminato (come suggerito in altri post). Tuttavia, è possibile estrarre un puntatore a un buffer interno che contiene una stringa C con il metodo . \0c_str()

Controlla anche la risposta di Doug T di seguito sull'utilizzo di un file vector<char>.

Controlla anche RiaD per una soluzione C ++ 14.


7
aggiornamento: a partire da c ++ 11 le stringhe sono terminate da null. Detto questo, il post di Loki rimane valido.
matthewaveryusa

14
@mna: sono terminate da null in termini di archiviazione, ma non nel senso che sono terminate da null con una terminazione nulla significativa (cioè con semantica che definisce la lunghezza della stringa), che è il significato usuale del termine.
Gare di leggerezza in orbita

Ben spiegato. Grazie.
Joma

22

Se stai manipolando come faresti con una stringa in stile c (array di caratteri), considera l'utilizzo di

std::vector<char>

Hai più libertà di trattarlo come un array nello stesso modo in cui tratteresti una stringa c. Puoi usare copy () per copiare in una stringa:

std::vector<char> vec(100)
strncpy(&vec[0], "blah blah blah", 100);
std::string vecAsStr( vec.begin(), vec.end());

e puoi usarlo in molti degli stessi posti in cui puoi usare le stringhe c

printf("%s" &vec[0])
vec[10] = '\0';
vec[11] = 'b';

Naturalmente, tuttavia, soffri degli stessi problemi delle stringhe in c. Potresti dimenticare il tuo terminale null o scrivere oltre lo spazio allocato.


Se dici di provare a codificare i byte in una stringa (i byte grpc sono archiviati come stringa) usa il metodo vettoriale come specificato nella risposta; non nel solito modo (vedi sotto) che NON costruirà l'intera stringa byte *bytes = new byte[dataSize]; std::memcpy(bytes, image.data, dataSize * sizeof(byte)); std::string test(reinterpret_cast<char *>(bytes)); std::cout << "Encoded String length " << test.length() << std::endl;
Alex Punnen

13

Non ho idea del motivo per cui vorresti fare una cosa del genere, ma prova questo:

std::string my_string("a\0b", 3);

1
Quali sono le tue preoccupazioni per farlo? Stai mettendo in dubbio la necessità di memorizzare "a \ 0b"? o mettere in discussione l'uso di una stringa std :: per tale archiviazione? In quest'ultimo caso, cosa suggerisci in alternativa?
Anthony Cramp

3
@Constantin allora stai facendo qualcosa di sbagliato se stai memorizzando dati binari come una stringa. Ecco per cosa vector<unsigned char>o unsigned char *sono stati inventati.
Mahmoud Al-Qudsi

2
Mi sono imbattuto in questo mentre cercavo di saperne di più sulla sicurezza delle stringhe. Volevo testare il mio codice per assicurarmi che funzioni ancora anche se legge un carattere nullo durante la lettura da un file / rete di ciò che si aspetta siano dati testuali. Uso std::stringper indicare che i dati devono essere considerati come testo normale, ma sto facendo un po 'di lavoro di hashing e voglio assicurarmi che tutto funzioni ancora con i caratteri nulli coinvolti. Sembra un uso valido di una stringa letterale con un carattere null incorporato.
David Stone

3
@DuckMaestro No, non è vero. Un \0byte in una stringa UTF-8 può essere solo NUL. Un carattere codificato multibyte non conterrà mai - \0né alcun altro carattere ASCII per quella materia.
John Kugelman

1
Mi sono imbattuto in questo quando cercavo di provocare un algoritmo in un caso di test. Quindi ci sono ragioni valide; anche se pochi.
namezero

12

Quali nuove funzionalità aggiungono al C ++ i valori letterali definiti dall'utente? presenta una risposta elegante: Definisci

std::string operator "" _s(const char* str, size_t n) 
{ 
    return std::string(str, n); 
}

quindi puoi creare la tua stringa in questo modo:

std::string my_string("a\0b"_s);

o anche così:

auto my_string = "a\0b"_s;

C'è un modo "vecchio stile":

#define S(s) s, sizeof s - 1 // trailing NUL does not belong to the string

quindi puoi definire

std::string my_string(S("a\0b"));

8

Quanto segue funzionerà ...

std::string s;
s.push_back('a');
s.push_back('\0');
s.push_back('b');

Devi usare le parentesi invece delle parentesi quadre.
jk.

5

Dovrai stare attento con questo. Se sostituisci "b" con qualsiasi carattere numerico, creerai silenziosamente la stringa sbagliata utilizzando la maggior parte dei metodi. Vedere: Regole per i valori letterali stringa C ++ carattere di escape .

Ad esempio, ho lasciato cadere questo frammento dall'aspetto innocente nel mezzo di un programma

// Create '\0' followed by '0' 40 times ;)
std::string str("\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00", 80);
std::cerr << "Entering loop.\n";
for (char & c : str) {
    std::cerr << c;
    // 'Q' is way cooler than '\0' or '0'
    c = 'Q';
}
std::cerr << "\n";
for (char & c : str) {
    std::cerr << c;
}
std::cerr << "\n";

Ecco cosa mi ha prodotto questo programma:

Entering loop.
Entering loop.

vector::_M_emplace_ba
QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ

Questa è stata la mia prima dichiarazione di stampa due volte, diversi caratteri non stampabili, seguiti da una nuova riga, seguita da qualcosa nella memoria interna, che ho appena sovrascritto (e poi stampato, dimostrando che è stato sovrascritto). Peggio ancora, anche la compilazione di questo con avvertimenti dettagliati e dettagliati di gcc non mi dava alcuna indicazione di qualcosa che non andava, e l'esecuzione del programma tramite valgrind non si lamentava di alcun modello di accesso alla memoria improprio. In altre parole, è completamente non rilevabile dagli strumenti moderni.

Puoi ottenere lo stesso problema con il molto più semplice std::string("0", 100);, ma l'esempio sopra è un po 'più complicato e quindi più difficile da vedere cosa c'è che non va.

Fortunatamente, C ++ 11 ci fornisce una buona soluzione al problema utilizzando la sintassi dell'elenco di inizializzatori. Questo ti evita di dover specificare il numero di caratteri (che, come ho mostrato sopra, puoi fare in modo errato) ed evita di combinare numeri con escape. std::string str({'a', '\0', 'b'})è sicuro per qualsiasi contenuto di stringa, a differenza delle versioni che accettano un array di chare una dimensione.


2
Come parte della mia preparazione per questo post, ho inviato una segnalazione di bug a gcc nella speranza che aggiungessero un avviso per renderlo un po 'più sicuro: gcc.gnu.org/bugzilla/show_bug.cgi?id=54924
David Stone

4

In C ++ 14 ora puoi usare i letterali

using namespace std::literals::string_literals;
std::string s = "a\0b"s;
std::cout << s.size(); // 3

1
e la seconda riga può essere scritta in alternativa, più piacevolmente imho, comeauto s{"a\0b"s};
underscore_d

Bella risposta Grazie.
Joma

1

Meglio usare std :: vector <char> se questa domanda non è solo per scopi educativi.


1

la risposta di anonym è eccellente, ma c'è anche una soluzione non macro in C ++ 98:

template <size_t N>
std::string RawString(const char (&ch)[N])
{
  return std::string(ch, N-1);  // Again, exclude trailing `null`
}

Con questa funzione, RawString(/* literal */)produrrà la stessa stringa di S(/* literal */):

std::string my_string_t(RawString("a\0b"));
std::string my_string_m(S("a\0b"));
std::cout << "Using template: " << my_string_t << std::endl;
std::cout << "Using macro: " << my_string_m << std::endl;

Inoltre, c'è un problema con la macro: l'espressione non è effettivamente std::stringcome scritta, e quindi non può essere utilizzata, ad esempio, per una semplice inizializzazione dell'assegnazione:

std::string s = S("a\0b"); // ERROR!

... quindi potrebbe essere preferibile utilizzare:

#define std::string(s, sizeof s - 1)

Ovviamente dovresti usare solo l'una o l'altra soluzione nel tuo progetto e chiamarla come ritieni appropriato.


-5

So che è da molto tempo che questa domanda viene posta. Ma per chiunque abbia un problema simile potrebbe essere interessato al seguente codice.

CComBSTR(20,"mystring1\0mystring2\0")

Questa risposta è troppo specifica per le piattaforme Microsoft e non risponde alla domanda originale (che chiedeva informazioni su std :: string).
Giugno Rodi

-8

Quasi tutte le implementazioni di std :: strings hanno terminazione null, quindi probabilmente non dovresti farlo. Notare che "a \ 0b" è in realtà lungo quattro caratteri a causa del carattere di terminazione null automatico (a, null, b, null). Se vuoi davvero farlo e rompere il contratto di std :: string, puoi fare:

std::string s("aab");
s.at(1) = '\0';

ma se lo fai, tutti i tuoi amici rideranno di te, non troverai mai la vera felicità.


1
std :: string NON deve essere terminato con NULL.
Martin York

2
Non è necessario, ma in quasi tutte le implementazioni lo è, probabilmente a causa della necessità per l'accessor c_str () di fornire l'equivalente con terminazione null.
Jurney

2
Per efficienza, è possibile mantenere un carattere nullo sul retro del buffer dei dati. Ma nessuna delle operazioni (cioè i metodi) su una stringa utilizza questa conoscenza o è influenzata da una stringa contenente un carattere NULL. Il carattere NULL verrà manipolato esattamente allo stesso modo di qualsiasi altro carattere.
Martin York

Questo è il motivo per cui è così divertente che la stringa sia std :: - il suo comportamento non è definito su NESSUNA piattaforma.

Vorrei che l'utente595447 fosse ancora qui in modo da poter chiedere loro di cosa diavolo pensavano di parlare.
underscore_d
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.