Linizializzazione di un carattere [] con una stringa letterale è una cattiva pratica?

stavo leggendo un thread intitolato “strlen vs sizeof” su CodeGuru e una delle risposte afferma che “è comunque [sic] cattiva pratica inizializzare [sic] un char array con una stringa letterale. “

È vero, o è solo la sua opinione (anche se 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; } 

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

questa è loutput

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 modificata da strcpy ma la lunghezza dopo.

Cè qualcosa che non va 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"); 

Commenti

  • Nota il ” const ” sulla prima riga. Potrebbe essere che lautore abbia assunto c ++ invece di c? In c ++ è ” cattiva pratica “, perché un letterale dovrebbe essere const e qualsiasi compilatore c ++ recente darà un avviso (o errore) sullassegnazione di un letterale const a un array non const.
  • @Andr é C ++ definisce i letterali stringa come array const, perché questo è lunico modo sicuro di trattare con loro. Che C non ‘ t è il problema, quindi hai una regola sociale che impone la cosa sicura
  • @Caleth. Lo so, stavo più cercando di sostenere che lautore della risposta si stava avvicinando alla ” cattiva pratica ” da una prospettiva c ++.
  • @Andr é non è ‘ una cattiva pratica in C ++, perché non è ‘ ta pratica , ‘ è un errore di tipo diretto. dovrebbe essere un errore di tipo in C, ma non è ‘ t, quindi devi avere una regola della guida di stile che ti dice ” È ‘ è vietato ”

Risposta

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

Lautore di quel commento non lo giustifica mai veramente, e trovo laffermazione sconcertante.

In C (e tu “lhai etichettato come C), quello” è praticamente lunico modo per inizializzare un array di char con un valore stringa (linizializzazione è diversa dallassegnazione). Puoi scrivere sia

char string[] = "october"; 

o

char string[8] = "october"; 

o

char string[MAX_MONTH_LENGTH] = "october"; 

Nel primo caso, la dimensione dellarray è presa dalla dimensione dellinizializzatore. Le stringhe letterali sono memorizzate come array di char con 0 byte di terminazione, quindi la dimensione dellarray è 8 (“o”, “c”, “t”, “o”, “b”, “e”, “r”, 0). Nei secondi due casi, la dimensione dellarray è specificata come parte della dichiarazione (8 e MAX_MONTH_LENGTH, qualunque cosa accada).

Quello che non puoi fare è scrivere qualcosa come

char string[]; string = "october"; 

o

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

ecc. Nel primo caso, la dichiarazione di string è incompleta perché non è stata specificata la dimensione dellarray e non cè nessun inizializzatore da cui prendere la dimensione. In entrambi casi, = non funzionerà perché a) unespressione di matrice come string potrebbe non essere lobiettivo di un compito eb) loperatore = non è definito per copiare comunque il contenuto di un array in un altro.

Con lo stesso token, non puoi “scrivere

char string[] = foo; 

dove foo è un altro array di char. Questa forma di inizializzazione funzionerà solo con stringhe letterali.

EDIT

Dovrei modificarlo per dire che puoi anche inizializzare array 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 stringhe letterali.

EDIT 2

Per assegna il contenuto di un array al di fuori di una dichiarazione, dovresti 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! 

Commenti

  • strncpy è raramente la risposta giusta
  • @KeithThompson: non sono in disaccordo, lho aggiunto solo per completezza ‘ sake.
  • Tieni presente che char[8] str = "october"; è una cattiva pratica. Ho dovuto letteralmente scrivere il conteggio per assicurarmi che non fosse ‘ un overflow e si interrompesse durante la manutenzione … ad es. la correzione di un errore di ortografia da seprate a separate si interromperà se le dimensioni non vengono aggiornate.
  • Sono daccordo con djechlin, è una cattiva pratica per i motivi addotti. La risposta di JohnBode ‘ non ‘ commenta affatto la ” cattiva pratica ” aspetto (che è la parte principale della domanda !!), spiega solo cosa puoi o non puoi fare per inizializzare larray.
  • Minor: As ‘ length ” valore restituito da strlen() non include il carattere null, utilizzando MAX_MONTH_LENGTH per contenere la dimensione massima necessaria per char string[] spesso sembra sbagliato. IMO, MAX_MONTH_SIZE sarebbe meglio qui.

Risposta

Lunico problema che ricordo è lassegnazione di una stringa letterale 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 su una pagina contrassegnata come di sola lettura. Su altre piattaforme potrebbe stampare “Settembre”, ecc.

Detto questo, linizializzazione per letterale rende la quantità specifica di prenotazione, quindi non funzionerà:

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

Ma questo

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

Come ultima osservazione, non “userei strcpy affatto:

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

Anche se alcuni compilatori possono cambiarlo 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"; 

Commenti

  • ‘ è ancora un rischio di sovraccarico del buffer su quel strncpy perché ‘ n null termina la stringa copiata quando la lunghezza di something_else è maggiore di sizeof(buf). Di solito imposto lultimo carattere buf[sizeof(buf)-1] = 0 per proteggerlo, oppure se buf è inizializzato a zero, utilizza sizeof(buf) - 1 come lunghezza della copia.
  • Utilizza strlcpy o strcpy_s o anche snprintf se necessario.
  • Risolto. Sfortunatamente non esiste un modo facile e portatile per farlo a meno che tu non abbia il lusso di lavorare con i compilatori più recenti (strlcpy e snprintf non sono direttamente accessibili su MSVC, almeno gli ordini e strcpy_s non sono su * nix).
  • @MaciejPiechotka: Beh, grazie a Dio Unix ha rifiutato lallegato k sponsorizzato da Microsoft.

Risposta

Principalmente perché “non avrai la dimensione del char[] in una variabile / costrutto che puoi usare facilmente allinterno del programma.

Il codice di esempio dal link:

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

string è allocato nello stack con una lunghezza di 7 o 8 caratteri. Non riesco “a ricordare se è terminato da null in questo modo o meno: il thread a cui hai collegato ha dichiarato che lo è .

Copiare “september” su quella stringa è un ovvio sovraccarico di memoria.

Unaltra sfida si presenta se si passa string a unaltra funzionecosì laltra funzione può scrivere nellarray. Devi dire allaltra funzione quanto è lungo larray in modo che non crei un sovraccarico. Puoi passare string insieme al risultato di strlen() ma il thread spiega come questo può saltare in aria se string non è terminato da null.

Stai meglio allocare una stringa con una dimensione fissa (preferibilmente definita come costante) e quindi passare la matrice e la dimensione fissa allaltra funzione. I commenti di @John Bode sono corretti e ci sono modi per mitigare questi rischi. Richiedono inoltre uno sforzo maggiore da parte tua per utilizzarli.

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


sizeof string ti darà la dimensione del buffer (8 byte); utilizza il risultato di tale espressione invece di strlen quando sei “preoccupato per la memoria.
Allo stesso modo, puoi fare un controllo prima della chiamata a strcpy per vedere se il buffer di destinazione è abbastanza grande per la stringa di origine: if (sizeof target > strlen(src)) { strcpy (target, src); }.
Sì, se devi passare larray a una funzione, devi è necessario trasmettere anche la sua dimensione fisica: foo (array, sizeof array / sizeof *array);. – John Bode

Commenti

  • sizeof string ti darà la dimensione del buffer (8 byte); usa il risultato di quellespressione invece di strlen quando ‘ sei preoccupato per la memoria. Allo stesso modo, puoi fare un controllo prima della chiamata a strcpy per vedere se il buffer di destinazione è abbastanza grande per la stringa di origine: if (sizeof target > strlen(src)) { strcpy (target, src); }. Sì, se devi passare larray a una funzione, ‘ dovrai passare anche la sua dimensione fisica: foo (array, sizeof array / sizeof *array);.
  • @JohnBode – grazie, e questi sono buoni punti. Ho incorporato il tuo commento nella mia risposta.
  • Più precisamente, la maggior parte dei riferimenti al nome dellarray string risulta in una conversione implicita in char*, che punta al primo elemento dellarray. Ciò perde le informazioni sui limiti dellarray. Una chiamata di funzione è solo uno dei tanti contesti in cui ciò avviene. char *ptr = string; è un altro. Anche string[0] ne è un esempio; loperatore [] funziona sui puntatori, non direttamente sugli array. Lettura consigliata: Sezione 6 delle comp.lang.c FAQ .
  • Finalmente una risposta che si riferisce effettivamente alla domanda!

Risposta

Una cosa che nessuno dei due thread solleva è 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")); 

Questultimo fa solo memcpy. Lo standard C insiste sul fatto che se una qualsiasi parte di un array viene inizializzata, lo è tutto. Quindi, in questo caso, è meglio farlo da soli. Penso che potrebbe essere stato ciò a cui treuss voleva arrivare.

Di sicuro

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

è meglio di:

char whopping_big[8192] = {0}; 

o

char whopping_big[8192] = ""; 

ps For punti bonus, puoi fare:

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

per generare un errore di divisione per zero del tempo di compilazione se stai per sovraccaricare larray.

Risposta

Penso che lidea di “cattiva pratica” derivi dal fatto che questo modulo:

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

crea implicitamente uno strcpy dal codice sorgente della macchina 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 codice probabilmente non importa)

Commenti

  • ‘ ne farebbe solo una copia se tenti di modificarlo? Penso che il compilatore sarebbe più intelligente di così
  • E i casi come char time_buf[] = "00:00"; dove ‘ sta per modificare un buffer? Un char * inizializzato con una stringa letterale è impostato sullindirizzo del primo byte, quindi provare a modificarlo ha un comportamento indefinito perché il metodo di archiviazione della stringa letterale ‘ è sconosciuto (implementazione definita), mentre la modifica dei byte di un char[] è perfettamente legale perché il linizializzazione copia i byte in uno spazio scrivibile allocato nello stack. Per dire che ‘ è ” meno efficiente o ” cattiva pratica ” senza elaborare le sfumature di char* vs char[] è fuorviante.

Risposta

Non è mai molto tempo, ma dovresti evitare di inizializzare char [] to string, perché “string” è const char * e lo stai assegnando a char *. Quindi se passi questo char [] al metodo che modifica i dati puoi avere un comportamento interessante.

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

Non cè niente di sbagliato nellassegnare dati a un array di caratteri, ma poiché lintenzione di usare questo array è usarlo come “stringa” (char *), è facile dimenticare che non dovresti modificarlo array.

Commenti

  • Non corretto. Linizializzazione copia il contenuto della stringa letterale nellarray. Loggetto array non è ‘ t const a meno che tu non lo definisca in questo modo.(E le stringhe letterali in C non sono const, sebbene qualsiasi tentativo di modificare una stringa letterale abbia un comportamento indefinito.) char *s = "literal"; ha il tipo di comportamento di cui ‘ stai parlando; ‘ è meglio scritto come const char *s = "literal";
  • ” E in generale ” asdf ” è una costante, quindi dovrebbe essere dichiarato const. ” – Lo stesso ragionamento richiederebbe un const su int n = 42;, perché 42 è una costante.
  • Non ‘ importa quale macchina ‘ utilizzi. Lo standard del linguaggio garantisce che c sia modificabile. È ‘ una garanzia esattamente altrettanto forte di quella che 1 + 1 valuta 2. Se il programma a cui ho collegato sopra fa qualcosa di diverso dalla stampa di EFGH, indica unimplementazione C non conforme.
  • @Dainus: il compilatore MSVC ha unottimizzazione chiamata ‘ string pooling ‘ che metterà una singola copia di stringhe identiche in un segmento di sola lettura se può garantire che gli usi di esse siano di sola lettura. Disattiva lottimizzazione per visualizzare il comportamento ‘ normale ‘. Cordiali saluti, ” Modifica e continua ” richiede che questa opzione sia attiva. Maggiori informazioni qui: msdn.microsoft.com/en-us/library/s0s0asdt.aspx
  • Penso che Dainius stia suggerendo che in molti nei casi lerrore è che la variabile stessa dovrebbe essere contrassegnata const char *const per impedire la modifica dei byte o del puntatore stesso, ma in molti casi i programmatori lasceranno uno o entrambi mutabili consentendo ad alcuni codici di runtime di modificare quella che sembra essere una costante digitata (ma non è costante).

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *