Inițializarea unui caracter [] cu un șir este practic greșită?

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 la seprate la separate 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ând MAX_MONTH_LENGTH pentru a menține dimensiunea maximă necesară pentru char 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 lungimea something_else este mai mare decât sizeof(buf). De obicei, setez ultimul caracter buf[sizeof(buf)-1] = 0 pentru a proteja de asta, sau dacă buf este inițializat zero, utilizați sizeof(buf) - 1 ca lungime a copiei.
  • Utilizați strlcpy sau strcpy_s sau chiar snprintf 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 și snprintf nu sunt direct accesibile pe MSVC, cel puțin comenzile și strcpy_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 de strlen când ‘ vă preocupă memoria. În mod similar, puteți face o verificare înainte de apelul către strcpy 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ă la char*, 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 și string[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? Un char * 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 unui char[] 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țele char* 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 sunt const, 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 ca const 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 pe int n = 42;, deoarece 42 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 care 1 + 1 o evaluează la 2. Dacă programul la care am legat mai sus face altceva decât să imprime EFGH, 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ă).

Lasă un răspuns

Adresa ta de email nu va fi publicată. Câmpurile obligatorii sunt marcate cu *