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 daseprate
aseparate
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, utilizzandoMAX_MONTH_LENGTH
per contenere la dimensione massima necessaria perchar 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 disomething_else
è maggiore disizeof(buf)
. Di solito imposto lultimo caratterebuf[sizeof(buf)-1] = 0
per proteggerlo, oppure sebuf
è inizializzato a zero, utilizzasizeof(buf) - 1
come lunghezza della copia. - Utilizza
strlcpy
ostrcpy_s
o anchesnprintf
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
esnprintf
non sono direttamente accessibili su MSVC, almeno gli ordini estrcpy_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 distrlen
quando ‘ sei preoccupato per la memoria. Allo stesso modo, puoi fare un controllo prima della chiamata astrcpy
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 inchar*
, 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. Anchestring[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? Unchar *
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 unchar[]
è 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 dichar* 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 sonoconst
, 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 comeconst char *s = "literal";
- ” E in generale ” asdf ” è una costante, quindi dovrebbe essere dichiarato const. ” – Lo stesso ragionamento richiederebbe un
const
suint 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 che1 + 1
valuta2
. Se il programma a cui ho collegato sopra fa qualcosa di diverso dalla stampa diEFGH
, 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).