Citeam un subiect intitulat „strlen vs sizeof” pe CodeGuru și unul dintre răspunsuri afirmă că „este” oricum [sic] o practică proastă de a inițializa [sic] o char
cu un șir literal. „
Este adevărat sau este doar părerea sa (deși un” membru de elită „)?
Iată întrebarea inițială:
#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; }
corect. dimensiunea ar trebui să fie lungimea plus 1 da?
acesta este ieșirea
the size of september is 8 and the length is 9
dimensiunea ar trebui să fie cu siguranță 10. este ca și cum se calculează dimensiunea șirului înainte de a fi modificată de strcpy, dar lungimea după.
Există ceva în neregulă cu sintaxa mea sau ce?
Iată răspunsul :
Este oricum o practică proastă să inițializezi o matrice de caractere cu un literal șir. Deci, efectuați întotdeauna una dintre următoarele:
const char string1[] = "october"; char string2[20]; strcpy(string2, "september");
Comentarii
- Rețineți ” const ” pe prima linie. Ar putea fi că autorul și-a asumat c ++ în loc de c? În c ++ este ” practică proastă „, deoarece un literal ar trebui să fie const și orice compilator c ++ recent va da un avertisment (sau o eroare) despre atribuirea unui literal const unei matrici non-const.
- @Andr é C ++ definește literele șirului ca matrice de const, deoarece acesta este singurul mod sigur de tratare cu ei. Că C nu ‘ t este problema, deci aveți o regulă socială care impune siguranța
- @Caleth. Știu, încercam mai mult să susțin că autorul răspunsului se apropia de ” practica proastă ” dintr-o perspectivă c ++.
- @Andr é nu este ‘ o practică proastă în C ++, deoarece nu este ‘ ta practice , este ‘ o eroare de tip direct. Ar trebui să fie o eroare de tip în C, dar nu este ‘ t, deci trebuie să aveți o regulă de ghid de stil care să vă spună ” Este ‘ interzis ”
Răspuns
Este oricum o practică proastă să inițializezi o matrice de caractere cu un șir literal.
Autorul acelui comentariu nu o justifică niciodată cu adevărat, iar declarația mi se pare nedumeritoare.
În C (și ați „etichetat acest lucru ca C), că„ Este cam singurul mod de a inițializa o matrice de char
cu o valoare șir (inițializarea este diferită de atribuire). Puteți scrie fie
char string[] = "october";
sau
char string[8] = "october";
sau
char string[MAX_MONTH_LENGTH] = "october";
În primul caz, dimensiunea tabloului este preluată de la dimensiunea inițializatorului. Literalele șirului sunt stocate ca matrice de char
cu un octet de terminare 0, deci dimensiunea matricei este 8 („o”, „c”, „t”, „o”, „b”, „e”, „r”, 0). În cel de-al doilea caz, dimensiunea matricei este specificată ca parte a declarației (8 și MAX_MONTH_LENGTH
, indiferent de ce se întâmplă).
Ce nu puteți face este să scrieți ceva de genul
char string[]; string = "october";
sau
etc. În primul caz, declarația string
este incompletă deoarece nu a fost specificată nicio dimensiune a matricei și nu există inițializator pentru a lua dimensiunea. În ambele cazuri, =
nu va funcționa deoarece a) o expresie matrice precum string
nu poate fi ținta unei misiuni și b) operatorul =
nu este definit pentru a copia oricum conținutul unei matrici la alta.
Prin același simbol, nu puteți „scrie
char string[] = foo;
unde foo
este o altă matrice de char
. Această formă de inițializare va funcționa numai cu literele șirului.
EDIT
Ar trebui să modific acest lucru pentru a spune că puteți inițializa și tablouri pentru a ține un șir cu un inițialist în stil matrice, cum ar fi
char string[] = {"o", "c", "t", "o", "b", "e", "r", 0};
sau
char string[] = {111, 99, 116, 111, 98, 101, 114, 0}; // assumes ASCII
dar este mai ușor pentru ochi să folosești literele șirului.
EDIT 2
Pentru a atribuiți conținutul unui tablou în afara unei declarații, ar trebui să utilizați fie strcpy/strncpy
(pentru șiruri terminate cu 0), fie memcpy
(pentru orice alt tip de matrice):
if (sizeof string > strlen("october")) strcpy(string, "october");
sau
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!
Comentarii
-
strncpy
este rareori răspunsul corect - @KeithThompson: nu este de acord, doar l-am adăugat pentru completare ‘.
- Vă rugăm să rețineți că
char[8] str = "october";
este o practică proastă. A trebuit să mă calculez literalmente pentru a mă asigura că nu a fost ‘ t o revărsare și se întrerupe în timpul întreținerii … de ex. corectarea unei erori de ortografie de laseprate
laseparate
se va întrerupe dacă dimensiunea nu este actualizată. - Sunt de acord cu djechlin, este o practică proastă din motivele expuse. Răspunsul JohnBode ‘ nu comentează deloc despre ” rea practică ” aspect (care este partea principală a întrebării !!), explică doar ce puteți sau nu puteți face pentru a inițializa matricea.
- Minor: Ca ‘ lungime ” valoarea returnată de la
strlen()
nu include caracterul nul, utilizândMAX_MONTH_LENGTH
pentru a menține dimensiunea maximă necesară pentruchar string[]
adesea pare greșit. OMI,MAX_MONTH_SIZE
ar fi mai bine aici.
Răspuns
Singura problemă pe care mi-o amintesc este atribuirea literalului șir 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
De exemplu, luați acest program: p>
#include <stdio.h> int main() { char *var1 = "september"; char *var2 = "september"; var1[0] = "S"; printf("%s\n", var2); }
Acest lucru de pe platforma mea (Linux) se blochează pe măsură ce încearcă să scrie pe pagina marcată ca numai în citire. Pe alte platforme s-ar putea să tipărească „septembrie” etc.
Acestea fiind spuse – inițializarea prin literal face cantitatea specifică de rezervare, astfel încât aceasta să nu funcționeze:
char buf[] = "May"; strncpy(buf, "September", sizeof(buf)); // Result "Sep"
Dar acest lucru va
char buf[32] = "May"; strncpy(buf, "September", sizeof(buf));
Ca ultimă observație – nu aș folosi strcpy
deloc:
char buf[8]; strcpy(buf, "very long string very long string"); // Oops. We overwrite some random memory
În timp ce unii compilatori îl pot schimba în apel sigur strncpy
este mult mai sigur:
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";
Comentarii
- Există încă ‘ riscul de depășire a bufferului pe
strncpy
deoarece nu ‘ nu anulează șirul copiat atunci când lungimeasomething_else
este mai mare decâtsizeof(buf)
. De obicei, setez ultimul caracterbuf[sizeof(buf)-1] = 0
pentru a proteja de asta, sau dacăbuf
este inițializat zero, utilizațisizeof(buf) - 1
ca lungime a copiei. - Utilizați
strlcpy
saustrcpy_s
sau chiarsnprintf
dacă trebuie. - S-a remediat. Din păcate, nu există un mod ușor portabil de a face acest lucru decât dacă aveți un lux de a lucra cu cele mai noi compilatoare (
strlcpy
șisnprintf
nu sunt direct accesibile pe MSVC, cel puțin comenzile șistrcpy_s
nu sunt pe * nix). - @MaciejPiechotka: Ei bine, mulțumesc lui Dumnezeu Unix a respins anexa k sponsorizată de Microsoft.
Răspuns
În primul rând pentru că nu ai dimensiunea char[]
într-o variabilă / construct pe care o puteți utiliza cu ușurință în cadrul programului.
Eșantionul de cod de la link:
char string[] = "october"; strcpy(string, "september");
string
este alocat pe stivă ca 7 sau 8 caractere. Nu-mi pot aminti dacă este terminat în acest mod sau nu – firul la care v-ați conectat a declarat că este .
Copierea „septembrie” peste acel șir este o depășire evidentă a memoriei.
O altă provocare apare dacă treci string
unei alte funcțiideci cealaltă funcție poate scrie în matrice. Trebuie să spuneți celeilalte funcții cât timp este matricea, astfel încât nu creează o depășire. Ați putea trece string
împreună cu rezultatul strlen()
, dar firul explică modul în care acest lucru poate exploda dacă string
nu este terminat cu nul.
Ești mai bine alocarea unui șir cu o dimensiune fixă (de preferință definită ca o constantă) și apoi treceți matricea și dimensiunea fixă la cealaltă funcție. Comentariile lui @John Bode sunt corecte și există modalități de a atenua aceste riscuri. De asemenea, necesită mai mult efort din partea dvs. pentru a le utiliza.
Din experiența mea, valoarea pe care am inițializat-o char[]
to este de obicei prea mic pentru celelalte valori pe care trebuie să le plasez acolo. Utilizarea unei constante definite ajută la evitarea acestei probleme.
sizeof string
vă va oferi dimensiunea bufferului (8 octeți); folosiți rezultatul acelei expresii în loc de strlen
atunci când vă preocupă memoria.
În mod similar, puteți efectua o verificare înainte de apelul către strcpy
pentru a vedea dacă bufferul țintă este suficient de mare pentru șirul sursă: if (sizeof target > strlen(src)) { strcpy (target, src); }
.
Da, dacă trebuie să treceți matricea la o funcție, veți trebuie să-și treacă și dimensiunea fizică: foo (array, sizeof array / sizeof *array);
. – John Bode
Comentarii
-
sizeof string
vă va oferi dimensiunea buffer (8 octeți); utilizați rezultatul acelei expresii în loc destrlen
când ‘ vă preocupă memoria. În mod similar, puteți face o verificare înainte de apelul cătrestrcpy
pentru a vedea dacă bufferul dvs. țintă este suficient de mare pentru șirul sursă:if (sizeof target > strlen(src)) { strcpy (target, src); }
. Da, dacă trebuie să transmiteți matricea către o funcție, ‘ va trebui să treceți și dimensiunea sa fizică:foo (array, sizeof array / sizeof *array);
. - @JohnBode – mulțumesc, iar acestea sunt puncte bune. Am încorporat comentariul dvs. în răspunsul meu.
- Mai exact, majoritatea referințelor la numele matricei
string
duc la o conversie implicită lachar*
, indicând primul element al matricei. Aceasta pierde informațiile despre limitele matricei. Un apel funcțional este doar unul dintre multele contexte în care se întâmplă acest lucru.char *ptr = string;
este altul. Chiar șistring[0]
este un exemplu în acest sens; operatorul[]
funcționează pe pointeri, nu direct pe tablouri. Citire sugerată: secțiunea 6 din FAQ comp.lang.c . - În cele din urmă, un răspuns care se referă de fapt la întrebare!
Răspuns
Un lucru pe care niciun fir nu îl aduce în discuție este:
char whopping_great[8192] = "foo";
vs.
char whopping_great[8192]; memcpy(whopping_great, "foo", sizeof("foo"));
Primul va face ceva de genul:
memcpy(whopping_great, "foo", sizeof("foo")); memset(&whopping_great[sizeof("foo")], 0, sizeof(whopping_great)-sizeof("foo"));
Acesta din urmă face doar memcpy. Standardul C insistă asupra faptului că, dacă se inițializează o parte a unui tablou, totul este. Deci, în acest caz, este mai bine să o faceți singur. Cred că ar fi putut fi ceea ce treuss se ocupa.
Cu siguranță
char whopping_big[8192]; whopping_big[0] = 0;
este mai bun decât oricare dintre ele:
char whopping_big[8192] = {0};
sau
char whopping_big[8192] = "";
ps Pentru puncte bonus, puteți face:
memcpy(whopping_great, "foo", (1/(sizeof("foo") <= sizeof(whopping_great)))*sizeof("foo"));
pentru a arunca o divizare a timpului de compilare la zero eroare dacă sunteți pe punctul de a revărsa matricea.
Răspuns
Cred că ideea de „practică proastă” provine din faptul că această formă:
char string[] = "october is a nice month";
face implicit un strcpy de la codul sursă al mașinii la stivă.
Este mai eficient să gestionați doar un link către acel șir. Ca și cu:
char *string = "october is a nice month";
sau direct:
strcpy(output, "october is a nice month");
(dar, desigur, în majoritatea cod, probabil nu contează)
Comentarii
- Nu ar face ‘ doar o copie dacă încercați să-l modificați? Aș crede că compilatorul ar fi mai inteligent decât atât.
- Ce se întâmplă cu cazuri precum
char time_buf[] = "00:00";
în care ‘ urmează să modificați un buffer? Unchar *
inițializat la un șir literal este setat la adresa primului octet, așa că încercarea de a-l modifica are un comportament nedefinit, deoarece metoda stocării șirului literal ‘ este necunoscută (implementarea definită), în timp ce modificarea octeților unuichar[]
este perfect legală deoarece inițializarea copiește octeții într-un spațiu inscriptibil alocat pe stivă. Pentru a spune că ‘ s ” mai puțin eficient sau ” practică proastă ” fără a elabora nuanțelechar* vs char[]
este înșelător.
Răspuns
Niciodată nu este foarte mult timp, dar ar trebui să evitați caracterul de inițializare [] la șir, pentru că „șir” este const char * și îl atribuiți la char *. Deci, dacă treceți acest caracter [] la metoda care schimbă datele, puteți avea un comportament interesant.
După cum am spus, am amestecat un pic char [] cu char *, nu este bine, deoarece diferă puțin.
Nu este nimic în neregulă cu privire la atribuirea datelor matricei de caractere, dar, din moment ce intenția de a utiliza această matrice este de a o folosi ca „șir” (char *), este ușor să uităm că nu ar trebui să modificați acest lucru tablou.
Comentarii
- Incorect. Inițializarea copiază conținutul șirului literal în tablou. Obiectul matrice nu este ‘ t
const
dacă nu îl definiți astfel.(Și literele șirului din C nu suntconst
, deși orice încercare de a modifica un literal șir are un comportament nedefinit.)char *s = "literal";
are fel de comportament despre care ‘ vorbești; ‘ este mai bine scris caconst char *s = "literal";
- ” Și în general ” asdf ” este o constantă, deci ar trebui să fie declarată ca const. ” – Același raționament ar necesita un
const
peint n = 42;
, deoarece42
este o constantă. - Nu contează ‘ ce contină mașina pe care ‘ vă aflați. Standardul lingvistic garantează că
c
este modificabil. ‘ este o garanție la fel de puternică ca cea pe care1 + 1
o evaluează la2
. Dacă programul la care am legat mai sus face altceva decât să imprimeEFGH
, indică o implementare C neconformă. - @Dainus: compilatorul MSVC are o optimizare numită ‘ pool pool de șiruri ‘ care va pune o singură copie a șiruri identice într-un segment de numai citire dacă poate garanta că utilizările lor sunt doar de citire. Dezactivați optimizarea pentru a vedea comportamentul ‘ normal ‘. FYI ” Editați și continuați ” necesită această opțiune. Mai multe informații aici: msdn.microsoft.com/en-us/library/s0s0asdt.aspx
- Cred că Dainius sugerează că în multe cazuri eroarea este că variabila în sine ar trebui marcată
const char *const
pentru a preveni modificarea octeților sau a indicatorului în sine, dar în multe cazuri programatorii vor lăsa unul sau ambii mutabili permițând un anumit cod de rulare modificați ceea ce pare a fi o constantă tastată (dar nu este constantă).