L'inizializzazione di un carattere [] con una stringa è una cattiva pratica letterale?


44

Stavo leggendo un thread intitolato "strlen vs sizeof" su CodeGuru e una delle risposte afferma che "è comunque una cattiva pratica inizializzare [sic] un chararray con una stringa letterale".

È vero, o è solo la sua opinione (sebbene un "membro d'élite")?


Ecco la domanda originale:

#include <stdio.h>
#include<string.h>
main()
{
    char string[] = "october";
    strcpy(string, "september");

    printf("the size of %s is %d and the length is %d\n\n", string, sizeof(string), strlen(string));
    return 0;
}

destra. la dimensione dovrebbe essere la lunghezza più 1 sì?

questo è l'output

the size of september is 8 and the length is 9

la dimensione dovrebbe essere sicuramente 10. è come calcolare la dimensione della stringa prima che venga cambiata da strcpy ma la lunghezza dopo.

C'è qualcosa di sbagliato nella mia sintassi o cosa?


Ecco la risposta :

È comunque una cattiva pratica inizializzare un array di caratteri con una stringa letterale. Quindi esegui sempre una delle seguenti operazioni:

const char string1[] = "october";
char string2[20]; strcpy(string2, "september");

Nota la "const" sulla prima riga. Potrebbe essere che l'autore abbia assunto c ++ anziché c? In c ++ è "cattiva pratica", perché un letterale dovrebbe essere const e qualsiasi compilatore c ++ recente darà un avviso (o errore) sull'assegnazione di un letterale const a un array non const.
André,

@ André C ++ definisce i letterali di stringa come array cost, perché è l'unico modo sicuro per gestirli. Che C non è il problema, quindi hai una regola sociale che impone la cosa sicura
Caleth,

@Caleth. Lo so, stavo più cercando di sostenere che l'autore della risposta si stava avvicinando alla "cattiva pratica" da una prospettiva c ++.
André,

@ André non è una cattiva pratica in C ++, perché non è una pratica , è un errore di tipo diretto. Esso dovrebbe essere un errore di tipo in C, ma non lo è, in modo da avere una regola di guida di stile che ti dice "E 'proibito"
Caleth

Risposte:


59

È comunque una cattiva pratica inizializzare un array di caratteri con una stringa letterale.

L'autore di quel commento non lo giustifica mai e trovo sconcertante l'affermazione.

In C (e l'hai etichettato come C), è praticamente l'unico modo per inizializzare un array charcon un valore stringa (l'inizializzazione è diversa dall'assegnazione). Puoi anche scrivere

char string[] = "october";

o

char string[8] = "october";

o

char string[MAX_MONTH_LENGTH] = "october";

Nel primo caso, la dimensione dell'array viene presa dalla dimensione dell'inizializzatore. I letterali di stringa sono memorizzati come matrici di charcon 0 byte di terminazione, quindi la dimensione della matrice è 8 ('o', 'c', 't', 'o', 'b', 'e', ​​'r', 0). Nei secondi due casi, la dimensione dell'array viene specificata come parte della dichiarazione (8 e MAX_MONTH_LENGTH, qualunque cosa accada).

Quello che non puoi fare è scrivere qualcosa del genere

char string[];
string = "october";

o

char string[8];
string = "october";

ecc. Nel primo caso, la dichiarazione di stringè incompleta perché non è stata specificata alcuna dimensione dell'array e non esiste un inizializzatore da cui prendere la dimensione. In entrambi i casi, =non funzionerà perché a) un'espressione di array come stringpotrebbe non essere la destinazione di un compito eb) l' =operatore non è definito per copiare comunque i contenuti di un array su un altro.

Per lo stesso motivo, non puoi scrivere

char string[] = foo;

dov'è fooun altro array di char. Questa forma di inizializzazione funzionerà solo con valori letterali stringa.

MODIFICARE

Dovrei modificare questo per dire che puoi anche inizializzare le matrici per contenere una stringa con un inizializzatore in stile array, come

char string[] = {'o', 'c', 't', 'o', 'b', 'e', 'r', 0};

o

char string[] = {111, 99, 116, 111, 98, 101, 114, 0}; // assumes ASCII

ma è più facile per gli occhi usare letterali stringa.

MODIFICA 2

Per assegnare il contenuto di un array al di fuori di una dichiarazione, è necessario utilizzare strcpy/strncpy(per stringhe con terminazione 0) o memcpy(per qualsiasi altro tipo di array):

if (sizeof string > strlen("october"))
  strcpy(string, "october");

o

strncpy(string, "october", sizeof string); // only copies as many characters as will
                                           // fit in the target buffer; 0 terminator
                                           // may not be copied, but the buffer is
                                           // uselessly completely zeroed if the
                                           // string is shorter!


@KeithThompson: non in disaccordo, l'ho appena aggiunto per completezza.
John Bode,

16
Si prega di notare che char[8] str = "october";è una cattiva pratica. Ho dovuto letteralmente contare me stesso per assicurarmi che non fosse un overflow e che si rompesse durante la manutenzione ... ad es. Correggere un errore di ortografia da sepratea separatesi interromperà se la dimensione non viene aggiornata.
Djechlin,

1
Sono d'accordo con Djechlin, è una cattiva pratica per i motivi indicati. La risposta di JohnBode non commenta affatto l'aspetto della "cattiva pratica" (che è la parte principale della domanda !!), spiega semplicemente cosa puoi o non puoi fare per inizializzare l'array.
mastov,

Minore: Come 'valore di lunghezza" tornato da strlen()non comprende il carattere nullo, utilizzando MAX_MONTH_LENGTHper contenere la dimensione massima necessaria per char string[]spesso sembra . Sbagliata IMO, MAX_MONTH_SIZEsarebbe meglio qui.
chux - Ripristinare Monica

10

L'unico problema che ricordo è l'assegnazione di stringhe letterali a char *:

char var1[] = "september";
var1[0] = 'S'; // Ok - 10 element char array allocated on stack
char const *var2 = "september";
var2[0] = 'S'; // Compile time error - pointer to constant string
char *var3 = "september";
var3[0] = 'S'; // Modifying some memory - which may result in modifying... something or crash

Ad esempio prendi questo programma:

#include <stdio.h>

int main() {
  char *var1 = "september";
  char *var2 = "september";
  var1[0] = 'S';
  printf("%s\n", var2);
}

Questo sulla mia piattaforma (Linux) si arresta in modo anomalo mentre tenta di scrivere nella pagina contrassegnata come di sola lettura. Su altre piattaforme potrebbe stampare "settembre" ecc.

Detto questo, l'inizializzazione letterale rende l'ammontare specifico della prenotazione in modo che non funzioni:

char buf[] = "May";
strncpy(buf, "September", sizeof(buf)); // Result "Sep"

Ma questo lo farà

char buf[32] = "May";
strncpy(buf, "September", sizeof(buf));

Come ultima osservazione, non userei strcpyaffatto:

char buf[8];
strcpy(buf, "very long string very long string"); // Oops. We overwrite some random memory

Mentre alcuni compilatori possono trasformarlo in chiamata sicura strncpyè molto più sicuro:

char buf[1024];
strncpy(buf, something_else, sizeof(buf)); // Copies at most sizeof(buf) chars so there is no possibility of buffer overrun. Please note that sizeof(buf) works for arrays but NOT pointers.
buf[sizeof(buf) - 1] = '\0';

Esiste ancora il rischio di sovraccarico del buffer in strncpyquanto non annulla la stringa copiata quando la lunghezza di something_elseè maggiore di sizeof(buf). Di solito imposto l'ultimo carattere buf[sizeof(buf)-1] = 0da proteggere da quello, o se bufè inizializzato da zero, usare sizeof(buf) - 1come lunghezza della copia.
Syockit,

Usa strlcpyo strcpy_so anche snprintfse devi.
user253751

Fisso. Sfortunatamente non esiste un modo portatile semplice per farlo a meno che tu non abbia il lusso di lavorare con i compilatori più recenti ( strlcpye snprintfnon sei direttamente accessibile su MSVC, almeno ordini e strcpy_snon sei su * nix).
Maciej Piechotka,

@MaciejPiechotka: Bene, grazie a Dio Unix ha respinto l'allegato k sponsorizzato da Microsoft.
Deduplicatore il

6

Una cosa che nessuno dei due thread fa apparire è questa:

char whopping_great[8192] = "foo";

vs.

char whopping_great[8192];
memcpy(whopping_great, "foo", sizeof("foo"));

Il primo farà qualcosa del tipo:

memcpy(whopping_great, "foo", sizeof("foo"));
memset(&whopping_great[sizeof("foo")], 0, sizeof(whopping_great)-sizeof("foo"));

Quest'ultimo fa solo il memcpy. Lo standard C insiste sul fatto che se una parte di un array viene inizializzata, lo è tutto. Quindi, in questo caso, è meglio farlo da soli. Penso che potrebbe essere stato quello a cui Treuss stava arrivando.

Di sicuro

char whopping_big[8192];
whopping_big[0] = 0;

è meglio di entrambi:

char whopping_big[8192] = {0};

o

char whopping_big[8192] = "";

ps Per i punti bonus, puoi fare:

memcpy(whopping_great, "foo", (1/(sizeof("foo") <= sizeof(whopping_great)))*sizeof("foo"));

per generare una divisione del tempo di compilazione per errore zero se si sta per traboccare l'array.


5

Principalmente perché non avrai la dimensione di char[]in una variabile / costrutto che puoi facilmente usare all'interno del programma.

L'esempio di codice dal link:

 char string[] = "october";
 strcpy(string, "september");

stringè assegnato in pila con una lunghezza di 7 o 8 caratteri. Non riesco a ricordare se è terminato con null in questo modo o meno - il thread a cui hai collegato ha dichiarato che lo è.

Copiare "settembre" su quella stringa è un evidente sovraccarico di memoria.

Un'altra sfida si presenta se si passa stringa un'altra funzione in modo che l'altra funzione possa scrivere nell'array. È necessario indicare all'altra funzione quanto è lungo l'array in modo che non crei un sovraccarico. Potresti passare stringcon il risultato di strlen()ma il thread spiega come questo può esplodere se stringnon è terminato con null.

È meglio allocare una stringa con una dimensione fissa (preferibilmente definita come costante) e quindi passare l'array e la dimensione fissa all'altra funzione. I commenti di @John Bode sono corretti e ci sono modi per mitigare questi rischi. Richiedono anche maggiori sforzi da parte vostra per usarli.

Nella mia esperienza, il valore con cui ho inizializzato il char[]to di solito è troppo piccolo per gli altri valori che devo inserire. L'uso di una costante definita aiuta a evitare questo problema.


sizeof stringti darà la dimensione del buffer (8 byte); usa il risultato di quell'espressione invece che strlenquando sei preoccupato per la memoria.
Allo stesso modo, è possibile effettuare un controllo prima della chiamata a strcpyper verificare se il buffer di destinazione è abbastanza grande per la stringa di origine: if (sizeof target > strlen(src)) { strcpy (target, src); }.
Sì, se si deve passare la matrice a una funzione, sarà necessario passare la sua dimensione fisica così: foo (array, sizeof array / sizeof *array);. - John Bode


2
sizeof stringti darà la dimensione del buffer (8 byte); usa il risultato di quell'espressione invece che strlenquando sei preoccupato per la memoria. Allo stesso modo, è possibile effettuare un controllo prima della chiamata a strcpyper verificare se il buffer di destinazione è abbastanza grande per la stringa di origine: if (sizeof target > strlen(src)) { strcpy (target, src); }. Sì, se si deve passare la matrice a una funzione, sarà necessario passare la sua dimensione fisica così: foo (array, sizeof array / sizeof *array);.
John Bode,

1
@JohnBode - grazie, e quelli sono buoni punti. Ho incorporato il tuo commento nella mia risposta.

1
Più precisamente, la maggior parte dei riferimenti al nome dell'array stringdanno come risultato una conversione implicita char*, che punta al primo elemento dell'array. Ciò perde le informazioni sui limiti dell'array. Una chiamata di funzione è solo uno dei tanti contesti in cui ciò accade. char *ptr = string;è un altro. Anche ne string[0]è un esempio; l' []operatore lavora su puntatori, non direttamente su array. Lettura consigliata: sezione 6 della comp.lang.c FAQ .
Keith Thompson,

Finalmente una risposta che in realtà si riferisce alla domanda!
mastov,

2

Penso che l'idea di "cattiva pratica" derivi dal fatto che questa forma:

char string[] = "october is a nice month";

rende implicitamente uno strcpy dal codice macchina sorgente allo stack.

È più efficiente gestire solo un collegamento a quella stringa. Come con:

char *string = "october is a nice month";

o direttamente:

strcpy(output, "october is a nice month");

(ma ovviamente nella maggior parte del codice probabilmente non importa)


Non ne farebbe una copia solo se provassi a modificarla? Penso che il compilatore sarebbe più intelligente di così
Cole Johnson,

1
Che dire di casi come char time_buf[] = "00:00";dove stai per modificare un buffer? Un char *inizializzato su una stringa letterale viene impostato sull'indirizzo del primo byte, quindi il tentativo di modificarlo comporta un comportamento indefinito perché il metodo di archiviazione della stringa letterale è sconosciuto (implementazione definita), mentre la modifica dei byte di a char[]è perfettamente legale perché l'inizializzazione copia i byte in uno spazio scrivibile allocato nello stack. Dire che è "meno efficiente" o "cattiva pratica" senza approfondire le sfumature di char* vs char[]è fuorviante.
Braden Best

-3

Non è mai molto tempo, ma dovresti evitare l'inizializzazione char [] su string, perché "string" è const char * e lo stai assegnando a char *. Quindi, se passi questo carattere [] al metodo che modifica i dati, puoi avere un comportamento interessante.

Come ha detto lode, ho mescolato un po 'char [] con char *, non va bene perché differiscono un po'.

Non c'è nulla di sbagliato nell'assegnare i dati all'array char, ma poiché l'intenzione di utilizzare questo array è di usarlo come 'string' (char *), è facile dimenticare che non si dovrebbe modificare questo array.


3
Non corretto. L'inizializzazione copia il contenuto della stringa letterale nell'array. L'oggetto array non è a constmeno che non venga definito in questo modo. (E i letterali di stringa in C non lo sono const, sebbene qualsiasi tentativo di modificare un letterale di stringa abbia un comportamento indefinito.) char *s = "literal";Ha il tipo di comportamento di cui stai parlando; è meglio scritto comeconst char *s = "literal";
Keith Thompson il

davvero colpa mia, ho mescolato char [] con char *. Ma non sarei così sicuro di copiare il contenuto nell'array. Il controllo rapido con il compilatore MS C mostra che 'char c [] = "asdf";' creerà 'stringa' nel segmento const e quindi assegnerà questo indirizzo alla variabile array. Questo è in realtà un motivo per cui ho detto di evitare assegnazioni all'array di caratteri non const.
Dainius,

Sono scettico Prova questo programma e fammi sapere quale output ottieni.
Keith Thompson,

2
"E in generale" asdf "è una costante, quindi dovrebbe essere dichiarata come const." - Lo stesso ragionamento richiederebbe un conston int n = 42;, perché 42è una costante.
Keith Thompson,

1
Non importa su quale macchina ti trovi. Lo standard linguistico garantisce che cè modificabile. È esattamente una garanzia forte come quella che 1 + 1valuta 2. Se il programma che ho collegato sopra fa qualcosa di diverso dalla stampa EFGH, indica un'implementazione C non conforme.
Keith Thompson,
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.