2012-04-06 7 views
11

Sono uno studente che apprende il linguaggio C++ e sto cercando di capire come funzionano gli array di caratteri con terminazione null. Supponiamo che io definisco un array di caratteri in questo modo:Ubicazione del terminatore null dell'array di caratteri C++

char* str1 = "hello world"; 

Come previsto, strlen(str1) è uguale a 11, ed è terminata da null.

Dove C++ inserisce il terminatore null, se tutti gli 11 elementi del char array sopra sono riempiti con i caratteri "ciao mondo"? Assegna in realtà una matrice di lunghezza 12 anziché 11, con il 12 ° carattere '\0'? CPlusPlus.com sembra suggerire che uno dei 11 sarebbe necessaria '\0', a meno che sia effettivamente assegnando 12.

Supponiamo che procedere nel seguente modo:

// Create a new char array 
char* str2 = (char*) malloc(strlen(str1)); 

// Copy the first one to the second one 
strncpy(str2, str1, strlen(str1)); 

// Output the second one 
cout << "Str2: " << str2 << endl; 

Questo fornisce in uscita Str2: hello worldatcomY╗°g♠↕, che presumo sia C++ leggere la memoria nella posizione indicata dal puntatore char* str2 finché non incontra ciò che interpreta come un carattere nullo.

Tuttavia, se poi faccio:

// Null-terminate the second one 
str2[strlen(str1)] = '\0'; 

// Output the second one again 
cout << "Terminated Str2: " << str2 << endl; 

Produce Terminated Str2: hello world come previsto.

Ma non iscritto al str2[11] implica che stiamo scrivendo al di fuori dello spazio di memoria allocato di str2, dal momento che str2[11] è il 12 ° di byte, ma assegnati solo 11 byte?

L'esecuzione di questo codice non sembra causare avvisi del compilatore o errori di runtime. È sicuro farlo in pratica? Sarebbe meglio usare malloc(strlen(str1) + 1) anziché malloc(strlen(str1))?

+1

No, uno degli 11 caratteri è effettivamente '\ 0' ... sto scherzando :-) – hirschhornsalz

+1

Dato che stai imparando, è importante che tu sappia dall'inizio i nomi delle cose che stai imparando. L'espressione 'char * str1 =" ciao mondo ";' non definisce un array di caratteri, ma un * puntatore * a un valore letterale (incidentalmente la conversione da 'const char *' a 'char *' è deprecata, quindi il compilatore dovrebbe ti ho avvertito). Il * letteral * stesso è un * array * di * constant * caratteri con un terminatore null, ma la variabile che hai definito è un * pointer *. –

risposta

11

Nel caso di una stringa letterale il compilatore in realtà riserva un elemento char aggiuntivo per l'elemento \0.

// Create a new char array 
char* str2 = (char*) malloc(strlen(str1)); 

Questo è un errore comune che fanno i nuovi programmatori C. Quando si assegna l'archiviazione per uno char*, è necessario assegnare il numero di caratteri + 1 altro per memorizzare \0. Non allocare la capacità di archiviazione qui significa questa linea è anche illegale

// Null-terminate the second one 
str2[strlen(str1)] = '\0'; 

Qui si sta effettivamente scrivendo oltre la fine della memoria allocata. Quando si assegnano gli elementi X, l'ultimo byte legale a cui è possibile accedere è l'offset dell'indirizzo di memoria di X - 1. La scrittura sull'elemento X causa un comportamento non definito. Funzionerà spesso ma è una bomba a orologeria.

Il modo corretto di scrivere questo è il seguente

size_t size = strlen(str1) + sizeof(char); 
char* str2 = (char*) malloc(size); 
strncpy(str2, str1, size); 

// Output the second one 
cout << "Str2: " << str2 << endl; 

In questo esempio il str2[size - 1] = '\0' non è realmente necessario. La funzione strncpy riempirà tutti gli spazi aggiuntivi con il terminatore null. Qui ci sono solo size - 1 elementi str1 modo l'elemento finale della matrice è non necessarie e saranno riempiti con \0

+0

Qual è lo scopo di definire esplicitamente 'size_t size = strlen (str1) + sizeof (char);' nel tuo esempio? Va bene usare semplicemente 'malloc (strlen (str1) +1)', dal momento che sappiamo che un char è 1 byte? –

+1

@JohnMahoney ci sono due ragioni per cui ho usato il locale 'size'. Il primo è la prestazione. La funzione 'strlen' mentre non è costosa è O (N) e dato che la stringa non cambia non c'è motivo di eseguirla più volte. La porzione '+ sizeof (char)' è per lo più stile. Un '+ 1' fa la stessa cosa, preferisco la notazione' sizeof (char) 'più esplicita. – JaredPar

+1

Migliore:' char * str2 = malloc (str1) + 1); if (str2 == NULL) {/ * gestisce l'allocazione fallita * /} strcpy (str2, str1); '' sizeof (char) è 1 per definizione. 'strncpy' capita di funzionare in questo caso, ma non è * semplicemente * una versione" più sicura "di' strcpy'. –

6

Assegna in realtà una matrice di lunghezza 12 anziché 11, con il carattere 12 "\ 0"?

Sì.

Ma non iscritto al str2[11] implica che stiamo scrivendo al di fuori dello spazio di memoria allocato di str2, dal momento che str2[11] è il 12 ° di byte, ma assegnati solo 11 byte?

Sì.

Sarebbe meglio usare malloc(strlen(str1) + 1) anziché malloc(strlen(str1))?

Sì, perché il secondo modulo non è abbastanza lungo da copiare la stringa in.

L'esecuzione di questo codice non sembra causare avvisi del compilatore o errori di runtime.

Rilevare questo in tutti i casi tranne i più semplici è un problema molto difficile. Quindi gli autori dei compilatori semplicemente non si preoccupano.


Questo tipo di complessità è esattamente il motivo si dovrebbe utilizzare std::string piuttosto che stringhe C-stile crudo, se si sta scrivendo C++. E 'semplice come questo:

std::string str1 = "hello world"; 
std::string str2 = str1; 
1

Il letterale "hello world" è un array char che assomiglia:

{ 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '\0' } 

Quindi, sì, il letterale è 12 char s in termini di dimensioni.

Inoltre, malloc(strlen(str1)) alloca memoria per 1 byte in meno del necessario, poiché strlen restituisce la lunghezza della stringa, escluso il terminatore NUL. Scrivere su str[strlen(str1)] sta scrivendo 1 byte oltre la quantità di memoria che hai allocato.

Il compilatore non lo dirà, ma se si esegue il programma tramite valgrind o un programma simile disponibile sul sistema, verrà indicato se si sta accedendo alla memoria che non si dovrebbe.

1

Per una stringa C standard lunghezza della matrice cui è memorizzata la stringa è sempre un carattere più lunghi del limite della stringa in caratteri. Quindi la tua stringa "hello world" ha una lunghezza di stringa di 11 ma richiede un array di supporto con 12 voci.

Il motivo è semplicemente il modo in cui tali stringhe vengono lette. Le funzioni che gestiscono queste stringhe basicamente leggono i caratteri della stringa uno alla volta finché non trovano il carattere di terminazione '\0' e si fermano a questo punto. Se questo carattere manca queste funzioni continua a leggere la memoria fino a che non colpiscono un'area di memoria protetta che fa sì che il sistema operativo host uccida l'applicazione o finché non trova il carattere di terminazione.

Inoltre, se si inizializza un array di caratteri con la lunghezza 11 e si scrive la stringa "hello world", si verificheranno problemi enormi. Poiché la matrice dovrebbe contenere almeno 12 caratteri. Ciò significa che il byte che segue la matrice nella memoria viene sovrascritto. Con effetti collaterali imprevedibili.

Anche mentre si sta lavorando con C++, si potrebbe voler esaminare std:string. Questa classe è accessibile se si utilizza C++ e offre una migliore gestione delle stringhe. Potrebbe valere la pena di esaminarlo.

2

Penso che tu sia confuso dal valore restituito di strlen. Restituisce la lunghezza della stringa e non deve essere confusa con la dimensione dell'array che contiene la stringa. Considerate questo esempio:

char* str = "Hello\0 world"; 

ho aggiunto un carattere null nel mezzo della corda, che è perfettamente valida. Qui la matrice avrà una lunghezza di 13 (12 caratteri + il carattere null finale), ma strlen(str) restituirà 5, perché ci sono 5 caratteri prima del primo carattere null. strlen conta solo i caratteri finché non viene trovato un carattere nullo.

Quindi, se io uso il codice:

char* str1 = "Hello\0 world"; 
char* str2 = (char*) malloc(strlen(str1)); // strlen(str1) will return 5 
strncpy(str2, str1, strlen(str1)); 
cout << "Str2: " << str2 << endl; 

La matrice str2 avrà una lunghezza di 5, e non saranno terminati da un carattere null (perché strlen non conta esso). È questo che ti aspettavi?

+0

[Domanda simile] (https://stackoverflow.com/questions/10050228/c-char-array-null-terminator-location) – user3583535

0

Penso che ciò che è necessario sapere è che gli array di caratteri iniziano da 0 e vanno fino alla lunghezza dell'array-1 e sulla lunghezza dell'array di posizione ha il terminatore ('\ 0').
Nel tuo caso:

str1[0] == 'h'; 
str1[10] == 'd'; 
str1[11] == '\0'; 

Questo è il motivo per cui è corretto str2 [strlen (str1)] = '\ 0';
Il problema con l'output dopo strncpy è perché copia 11 elementi (0..10) quindi è necessario inserire manualmente il terminatore (str2 [11] = '\ 0').

Problemi correlati