Come devono essere usate le stringhe di caratteri come stringhe?


10

Capisco che le stringhe in C sono solo array di caratteri. Quindi ho provato il seguente codice, ma dà risultati strani, come l'output di immondizia o gli arresti anomali del programma:

#include <stdio.h>

int main (void)
{
  char str [5] = "hello";
  puts(str);
}

Perché non funziona?

Si compila in modo pulito con gcc -std=c17 -pedantic-errors -Wall -Wextra.


Nota: questo post deve essere utilizzato come FAQ canonica per problemi derivanti da un errore nell'allocare spazio per un terminatore NUL durante la dichiarazione di una stringa.

Risposte:


12

La stringa CA è una matrice di caratteri che termina con un terminatore nullo .

Tutti i caratteri hanno un valore nella tabella dei simboli. Il terminatore null è il valore del simbolo 0(zero). È usato per segnare la fine di una stringa. Ciò è necessario poiché la dimensione della stringa non è memorizzata da nessuna parte.

Pertanto, ogni volta che si alloca spazio per una stringa, è necessario includere spazio sufficiente per il carattere di terminazione null. Il tuo esempio non lo fa, alloca solo spazio per i 5 caratteri di "hello". Il codice corretto dovrebbe essere:

char str[6] = "hello";

O in modo equivalente, è possibile scrivere un codice di auto-documentazione per 5 caratteri più 1 terminatore null:

char str[5+1] = "hello";

Quando si alloca memoria per una stringa in modo dinamico in fase di runtime, è necessario allocare spazio per il terminatore null:

char input[n] = ... ;
...
char* str = malloc(strlen(input) + 1);

Se non si aggiunge un terminatore null alla fine di una stringa, le funzioni di libreria che prevedono una stringa non funzioneranno correttamente e si otterranno bug di "comportamento indefinito" come l'output di immondizia o arresti anomali del programma.

Il modo più comune per scrivere un carattere null di terminazione in C è quello di utilizzare una cosiddetta "sequenza di escape ottale", cercando in questo modo: '\0'. Questo equivale al 100% alla scrittura 0, ma \serve come codice autocompensante per affermare che lo zero è esplicitamente inteso come un terminatore null. Codice come if(str[i] == '\0')controllerà se il carattere specifico è il terminatore null.

Si noti che il termine null terminator non ha nulla a che fare con i puntatori null o la NULLmacro! Questo può essere fonte di confusione: nomi molto simili ma significati molto diversi. Questo è il motivo per cui il terminatore null viene talvolta indicato come NULcon una L, da non confondere con NULLo puntatori null. Vedi le risposte a questa domanda SO per ulteriori dettagli.

Il "hello"nel tuo codice si chiama letterale stringa . Questa deve essere considerata una stringa di sola lettura. La ""sintassi significa che il compilatore aggiungerà automaticamente un terminatore nullo alla fine della stringa letterale. Quindi se stampi sizeof("hello")otterrai 6, non 5, perché otterrai la dimensione dell'array incluso un terminatore null.


Si compila in modo pulito con gcc

Anzi, nemmeno un avvertimento. Ciò è dovuto a un sottile dettaglio / difetto nel linguaggio C che consente di inizializzare gli array di caratteri con un valore letterale di stringa che contiene esattamente tanti caratteri quanti sono gli spazi nell'array e quindi scartare silenziosamente il terminatore null (C17 6.7.9 / 15). Il linguaggio si sta intenzionalmente comportando in questo modo per ragioni storiche, vedere Diagnostica gcc incoerente per l'inizializzazione della stringa per i dettagli. Si noti inoltre che qui C ++ è diverso e non consente l'utilizzo di questo trucco / difetto.


1
Dovresti menzionare il char str[] = "hello";caso.
Jabberwocky,

@Jabberwocky Questa è una wiki della community, sentiti libero di modificare e contribuire.
Lundin,

1
... e forse anche il char *str = "hello";... str[0] = foo;problema.
Jabberwocky,

Forse estendere le implicazioni dell'uso sizeofal suo uso su un parametro di funzione, specialmente quando definito come un array.
Segnavento

@WeatherVane Dovrebbe essere coperto da un'altra FAQ qui: stackoverflow.com/questions/492384/…
Lundin

4

Dallo standard C (7.1.1 Definizioni dei termini)

1 Una stringa è una sequenza contigua di caratteri terminata da e incluso il primo carattere null. Il termine stringa multibyte viene talvolta utilizzato invece per enfatizzare l'elaborazione speciale data ai caratteri multibyte contenuti nella stringa o per evitare confusione con una stringa ampia. Un puntatore a una stringa è un puntatore al suo carattere iniziale (indirizzato più in basso). La lunghezza di una stringa è il numero di byte che precede il carattere null e il valore di una stringa è la sequenza dei valori dei caratteri contenuti, in ordine.

In questa dichiarazione

char str [5] = "hello";

la stringa letterale "hello"ha la rappresentazione interna simile

{ 'h', 'e', 'l', 'l', 'o', '\0' }

quindi ha 6 caratteri incluso lo zero finale. I suoi elementi vengono utilizzati per inizializzare la matrice di caratteri strche riservano spazio solo per 5 caratteri.

Lo standard C (opposto allo standard C ++) consente tale inizializzazione di una matrice di caratteri quando lo zero finale di un valore letterale di stringa non viene utilizzato come inizializzatore.

Tuttavia, di conseguenza l'array di caratteri strnon contiene una stringa.

Se si desidera che l'array contenga una stringa, è possibile scrivere

char str [6] = "hello";

o solo

char str [] = "hello";

Nell'ultimo caso la dimensione dell'array di caratteri è determinata dal numero di inizializzatori della stringa letterale che è uguale a 6.


0

Tutte le stringhe possono essere considerate una matrice di caratteri ( ), tutte le matrici di caratteri possono essere considerate stringhe ( No ).

Perchè no? e perché è importante?

Oltre alle altre risposte che spiegano che la lunghezza di una stringa non è memorizzata da nessuna parte come parte della stringa e i riferimenti allo standard in cui è definita una stringa, il rovescio della medaglia è "In che modo le funzioni della libreria C gestiscono le stringhe?"

Mentre una matrice di caratteri può contenere gli stessi caratteri, è semplicemente una matrice di caratteri a meno che l'ultimo carattere sia seguito dal carattere che termina con null . Quel carattere che termina l'annullamento è ciò che consente alla matrice di caratteri di essere considerata (gestita come) una stringa.

Tutte le funzioni in C che prevedono una stringa come argomento prevedono che la sequenza di caratteri venga annullata . Perché?

Ha a che fare con il modo in cui funzionano tutte le funzioni di stringa. Poiché la lunghezza non è inclusa come parte di un array, funzioni stringa, scansiona in avanti nell'array fino a trovare il carattere nullo (es. '\0'- equivalente al decimale 0). Vedi tabella e descrizione ASCII . Indipendentemente se si sta utilizzando strcpy, strchr, strcspn, ecc .. Tutte le funzioni di stringa si affidano alla nul-terminazione carattere essere presenti per definire dove alla fine di quella stringa è.

Un confronto tra due funzioni simili string.henfatizzerà l'importanza del carattere nulconsente . Prendi ad esempio:

    char *strcpy(char *dest, const char *src);

La strcpyfunzione copia semplicemente i byte da srca destfino a quando non viene trovato il carattere che termina con il nulla che dice strcpydove interrompere la copia dei caratteri. Ora prendi la funzione simile memcpy:

    void *memcpy(void *dest, const void *src, size_t n);

La funzione esegue un'operazione simile, ma non considera o richiede che il srcparametro sia una stringa. Poiché memcpynon è possibile eseguire semplicemente la scansione in avanti durante la srccopia dei byte destfino a quando non viene raggiunto un carattere che termina con null , è necessario copiare un numero esplicito di byte come terzo parametro. Questo terzo parametro fornisce memcpyinformazioni della stessa dimensione in strcpygrado di derivare semplicemente scansionando in avanti fino a trovare un carattere che termina con null .

(che sottolinea anche cosa non strcpyfunziona (o qualsiasi funzione che si aspetta una stringa) se non si riesce a fornire alla funzione una stringa con terminazione nulla - non ha idea di dove fermarsi e correrà felicemente attraverso il resto del segmento di memoria invocare Undefined Behaviour finché non viene trovato un carattere nullo da qualche parte nella memoria o si verifica un errore di segmentazione)

Ecco perché le funzioni che prevedono una stringa con terminazione null devono passare una stringa con terminazione null e perché sono importanti .


0

Intuitivamente...

Pensa a un array come una variabile (contiene cose) e una stringa come un valore (può essere inserito in una variabile).

Non sono certamente la stessa cosa. Nel tuo caso la variabile è troppo piccola per contenere la stringa, quindi la stringa viene tagliata. ("stringhe tra virgolette" in C hanno un carattere null implicito alla fine.)

Tuttavia è possibile memorizzare una stringa in un array molto più grande della stringa.

Si noti che i soliti operatori di assegnazione e confronto ( = == <ecc.) Non funzionano come ci si potrebbe aspettare. Ma la strxyzfamiglia di funzioni si avvicina molto, una volta che sai cosa stai facendo. Consulta le domande frequenti su C su stringhe e array .

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.