Is het initialiseren van een char [] met een letterlijke slechte praktijk?

Ik las een thread met de titel “strlen vs sizeof” op CodeGuru , en een van de antwoorden stelt dat “het” sowieso [sic] een slechte gewoonte is om [sic] een char array te initialiseren met een letterlijke tekenreeks. “

Is dit waar, of is dat alleen zijn (zij het een” elitelid “) mening?


Hier is de oorspronkelijke vraag:

#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; } 

klopt. de grootte moet de lengte zijn plus 1 ja?

dit is de output

the size of september is 8 and the length is 9

size zou zeker 10 moeten zijn. het is alsof het de grootte van de string berekent voordat het veranderd wordt door strcpy maar de lengte daarna.

Is er iets mis met mijn syntaxis of wat?


Hier is het antwoord :

Het is sowieso een slechte gewoonte om een tekenreeks te initialiseren met een letterlijke tekenreeks. Voer dus altijd een van de volgende handelingen uit:

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

Reacties

  • Let op de ” const ” op de eerste regel. Zou het kunnen dat de auteur c ++ heeft aangenomen in plaats van c? In c ++ is het ” slechte praktijk “, omdat een letterlijke waarde const zou moeten zijn en elke recente c ++ compiler zal een waarschuwing (of fout) geven over het toewijzen van een letterlijke const aan een niet-const-array.
  • @Andr é C ++ definieert letterlijke tekenreeksen als const arrays, omdat dat de enige veilige manier is om te handelen met hen. Dat C niet ‘ t is, is het probleem, dus je hebt een sociale regel die het veilige ding afdwingt
  • @Caleth. Ik weet het, ik probeerde meer te beweren dat de auteur van het antwoord de ” slechte praktijk ” benaderde vanuit een c ++ perspectief.
  • @Andr é het is geen ‘ een slechte praktijk in C ++, omdat het isn ‘ ta oefenen , het ‘ is een regelrechte typefout. Het zou een typefout in C moeten zijn, maar het is niet ‘ t, dus je hebt een stijlgidsregel nodig die je vertelt ” Het ‘ is verboden ”

Antwoord

Het is sowieso een slechte gewoonte om een tekenreeks te initialiseren met een letterlijke tekenreeks.

De auteur van die opmerking rechtvaardigt het nooit echt, en ik vind de uitspraak raadselachtig.

In C (en je “hebt dit als C getagd), dat” Het is vrijwel de enige manier om initialiseren een array van char met een stringwaarde (initialisatie is anders dan toekennen). Je kunt ofwel

schrijven

char string[] = "october"; 

of

char string[8] = "october"; 

of

char string[MAX_MONTH_LENGTH] = "october"; 

In het eerste geval wordt de grootte van de array ontleend aan de grootte van de initializer. Letterlijke tekenreeksen worden opgeslagen als arrays van char met een afsluitende 0 byte, dus de grootte van de array is 8 (“o”, “c”, “t”, “o”, “b”, “e”, “r”, 0). In de tweede twee gevallen wordt de grootte van de array gespecificeerd als onderdeel van de declaratie (8 en MAX_MONTH_LENGTH, wat dat ook mag zijn).

Wat je niet kunt doen, is iets schrijven als

char string[]; string = "october"; 

of

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

enz. In het eerste geval is de declaratie van string incompleet omdat er geen arraygrootte is opgegeven en er geen initialisatieprogramma is om de grootte van te nemen. In beide gevallen zal de = “niet werken omdat a) een array-expressie zoals string mogelijk niet het doelwit van een toewijzing is en b) de = operator is sowieso niet gedefinieerd om de inhoud van de ene array naar de andere te kopiëren.

Met datzelfde token kun je “niet schrijven

char string[] = foo; 

waarbij foo een andere array is van char. Deze vorm van initialisatie werkt alleen met letterlijke tekenreeksen.

EDIT

Ik zou dit moeten wijzigen om te zeggen dat je ook kunt initialiseren arrays om een string vast te houden met een array-style initializer, zoals

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

of

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

maar het is gemakkelijker voor de ogen om letterlijke tekenreeksen te gebruiken.

EDIT 2

Om wijs de inhoud van een array buiten een declaratie toe, je zou ofwel strcpy/strncpy (voor 0-beëindigde strings) of (voor elk ander type array):

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

of

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! 

Reacties

  • strncpy is zelden het juiste antwoord
  • @KeithThompson: niet oneens, alleen voor de volledigheid toegevoegd ‘ sake.
  • Houd er rekening mee dat char[8] str = "october"; is een slechte gewoonte. Ik moest mezelf letterlijk tellen om er zeker van te zijn dat het geen ‘ t een overloop was en het breekt tijdens onderhoud … bijv. het corrigeren van een spelfout van seprate naar separate zal breken als de grootte niet wordt bijgewerkt.
  • Ik ben het met djechlin eens, het is om de genoemde redenen een slechte gewoonte. JohnBode ‘ s antwoord geeft geen ‘ commentaar op de ” slechte praktijk ” aspect (dat is het belangrijkste deel van de vraag !!), het legt alleen uit wat je wel of niet kunt doen om de array te initialiseren.
  • Minor: As ‘ lengte ” waarde geretourneerd van strlen() bevat geen null-teken, met MAX_MONTH_LENGTH om de maximale grootte vast te houden die nodig is voor char string[] ziet er vaak er verkeerd uit . IMO, MAX_MONTH_SIZE zou hier beter zijn.

Antwoord

Het enige probleem dat ik me herinner is het letterlijk toewijzen van een string aan 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 

Neem bijvoorbeeld dit programma:

#include <stdio.h> int main() { char *var1 = "september"; char *var2 = "september"; var1[0] = "S"; printf("%s\n", var2); } 

Dit crasht op mijn platform (Linux) als het probeert te schrijven naar een pagina die is gemarkeerd als alleen-lezen. Op andere platforms kan het “September” enz. Afdrukken.

Dat gezegd hebbende – letterlijke initialisatie maakt het specifieke aantal reserveringen “, zodat dit” niet werkt:

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

Maar dit zal

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

Als laatste opmerking – ik zou strcpy helemaal:

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

Hoewel sommige compilers het kunnen veranderen in een veilige aanroep is strncpy veel veiliger:

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"; 

Reacties

  • Er is ‘ s nog steeds een risico voor bufferoverschrijding op die strncpy omdat het niet ‘ t null de gekopieerde tekenreeks beëindigt wanneer de lengte van something_else is groter dan sizeof(buf). Ik stel meestal het laatste teken buf[sizeof(buf)-1] = 0 in om daartegen te beschermen, of als buf nul-geïnitialiseerd is, gebruik dan sizeof(buf) - 1 als de kopie lengte.
  • Gebruik strlcpy of strcpy_s of zelfs snprintf als het moet.
  • Opgelost. Helaas is er geen gemakkelijke draagbare manier om dit te doen, tenzij je de luxe hebt om met de nieuwste compilers te werken (strlcpy en snprintf zijn niet direct toegankelijk op MSVC staan tenminste bestellingen en strcpy_s niet op * nix).
  • @MaciejPiechotka: Nou, godzijdank heeft Unix de door Microsoft gesponsorde bijlage k afgewezen.

Antwoord

Vooral omdat je “niet de grootte van de char[] in een variabele / construct die je gemakkelijk in het programma kunt gebruiken.

Het codevoorbeeld van de link:

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

string wordt op de stapel toegewezen als 7 of 8 tekens. Ik kan me niet herinneren of het op deze manier met null is beëindigd of niet – de thread waarnaar je hebt gelinkt, heeft aangegeven dat het zo is .

Het kopiëren van “september” over die string is een overduidelijke geheugenoverschrijding.

Een andere uitdaging ontstaat als je string doorgeeft aan een andere functie.zodat de andere functie in de array kan schrijven. U moet de andere functie vertellen hoe lang de array is, zodat het geen “overschrijding” veroorzaakt. U kunt string doorgeven samen met het resultaat van strlen() maar de thread legt uit hoe dit kan worden opgeblazen als string niet wordt beëindigd.

Je bent beter af het toewijzen van een string met een vaste grootte (bij voorkeur gedefinieerd als een constante) en vervolgens de array en de vaste grootte doorgeven aan de andere functie. De opmerking (en) van @John Bode is / zijn correct, en er zijn manieren om deze risicos te beperken. Ze vereisen ook meer inspanning van uw kant om ze te gebruiken.

In mijn ervaring is de waarde die ik heb geïnitialiseerd de char[] to is meestal te klein voor de andere waarden die ik daar moet plaatsen. Het gebruik van een gedefinieerde constante helpt om dat probleem te voorkomen.


sizeof string geeft u de grootte van de buffer (8 bytes); gebruik het resultaat van die uitdrukking in plaats van strlen als je je zorgen maakt over het geheugen.
Evenzo kun je een controle uitvoeren voordat je strcpy om te zien of je doelbuffer groot genoeg is voor de source string: if (sizeof target > strlen(src)) { strcpy (target, src); }.
Ja, als je de array aan een functie moet doorgeven, zul je moeten ook de fysieke grootte doorgeven: foo (array, sizeof array / sizeof *array);. – John Bode

Reacties

  • sizeof string geeft je de grootte van de buffer (8 bytes); gebruik het resultaat van die uitdrukking in plaats van strlen wanneer je ‘ bezorgd bent over het geheugen. Evenzo kunt u vóór de aanroep van strcpy controleren of uw doelbuffer groot genoeg is voor de brontekenreeks: if (sizeof target > strlen(src)) { strcpy (target, src); }. Ja, als je de array aan een functie moet doorgeven, moet je ‘ ook de fysieke grootte doorgeven: foo (array, sizeof array / sizeof *array);.
  • @JohnBode – bedankt, en dat zijn goede punten. Ik heb uw opmerking in mijn antwoord verwerkt.
  • Meer precies, de meeste verwijzingen naar de arraynaam string resulteren in een impliciete conversie naar char*, wijzend naar het eerste element van de array. Hierdoor gaat de informatie over de arraygrenzen verloren. Een functieaanroep is slechts een van de vele contexten waarin dit gebeurt. char *ptr = string; is een andere. Zelfs string[0] is hier een voorbeeld van; de [] -operator werkt op pointers, niet rechtstreeks op arrays. Voorgestelde lezing: sectie 6 van de comp.lang.c FAQ .
  • Eindelijk een antwoord dat daadwerkelijk naar de vraag verwijst!

Antwoord

Een ding dat geen van beide discussies naar voren brengt, is dit:

char whopping_great[8192] = "foo"; 

vs.

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

De eerste doet zoiets als:

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

De laatste doet alleen de memcpy. De C-standaard houdt vol dat als een deel van een array wordt geïnitialiseerd, dit alles is. Dus in dit geval is het beter om het zelf te doen. Ik denk dat Treuss dat misschien bedoelde.

Zeker

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

is beter dan:

char whopping_big[8192] = {0}; 

of

char whopping_big[8192] = ""; 

ps Voor bonuspunten kunt u het volgende doen:

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

om een compilatietijd te delen door nul fout als u op het punt staat de array te overlopen.

Antwoord

Ik denk dat het idee van “slechte praktijken” voortkomt uit het feit dat dit formulier:

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

maakt impliciet een strcpy van de bronmachinecode naar de stapel.

Het is efficiënter om alleen een link naar die string te verwerken. Zoals met:

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

of direct:

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

(maar natuurlijk in de meeste code maakt het waarschijnlijk niet uit)

Reacties

  • Zou het niet ‘ maken om alleen een kopie te maken als je het probeert te wijzigen? Ik denk dat de compiler slimmer is dan dat
  • Hoe zit het met gevallen als char time_buf[] = "00:00"; waarin je ‘ gaat een buffer wijzigen? Een char * geïnitialiseerd met een letterlijke tekenreeks wordt ingesteld op het adres van de eerste byte, dus als u probeert deze te wijzigen, resulteert dit in ongedefinieerd gedrag omdat de methode van de letterlijke tekenreeks ‘ s opslag is onbekend (implementatie gedefinieerd), terwijl het wijzigen van de bytes van een char[] volkomen legaal is omdat de initialisatie kopieert de bytes naar een schrijfbare ruimte die is toegewezen op de stapel. Om te zeggen dat het ‘ s ” minder efficiënt of ” slechte praktijken ” zonder de nuances van char* vs char[] is misleidend.

Answer

Nooit is echt lang, maar je moet initialisatie char [] vermijden to string, omdat “string” const char * is, en je wijst het toe aan char *. Dus als je dit char [] doorgeeft aan een methode die gegevens verandert, kun je interessant gedrag vertonen.

Zoals gezegd heb ik een beetje char [] gemengd met char *, dat is niet goed, want ze verschillen een beetje.

Er is niets mis met het toewijzen van gegevens aan de char-array, maar aangezien het de bedoeling is deze array te gebruiken als “string” (char *), is het gemakkelijk om te vergeten dat je dit niet moet wijzigen array.

Commentaar

  • Onjuist. De initialisatie kopieert de inhoud van de tekenreeks letterlijk naar de array. Het array-object isn ‘ t const tenzij je het op die manier definieert.(En letterlijke tekenreeksen in C zijn niet const, hoewel elke poging om een letterlijke tekenreeks te wijzigen een ongedefinieerd gedrag heeft.) char *s = "literal"; heeft de soort gedrag waar je ‘ over praat; het ‘ is beter geschreven als const char *s = "literal";
  • ” En in het algemeen is ” asdf ” een constante, dus het moet worden gedeclareerd als const. ” – Dezelfde redenering vraagt om een const op int n = 42;, omdat 42 is een constante.
  • Het doet ‘ niet uit op welke machine je ‘ bent. De taalstandaard garandeert dat c aanpasbaar is. Het ‘ is precies een even sterke garantie als degene die 1 + 1 evalueert naar 2. Als het programma waarnaar ik hierboven heb gelinkt iets anders doet dan EFGH afdrukken, duidt dit op een niet-conforme C-implementatie.
  • @Dainus: de MSVC-compiler heeft een optimalisatie genaamd ‘ string pooling ‘ die een enkele kopie van identieke strings in een alleen-lezen segment als het kan garanderen dat het gebruik ervan alleen-lezen is. Schakel de optimalisatie uit om ‘ normaal ‘ gedrag te zien. Ter info: de ” Bewerken en doorgaan ” vereist dat deze optie is ingeschakeld. Meer info hier: msdn.microsoft.com/en-us/library/s0s0asdt.aspx
  • Ik denk dat Dainius dat in veel gevallen is de fout dat de variabele zelf gemarkeerd moet worden als const char *const om wijziging van de bytes of de pointer zelf te voorkomen, maar in veel gevallen laten programmeurs een of beide veranderlijk achter, waardoor sommige runtime-code wijzig wat een getypte constante lijkt te zijn (maar is niet constant).

Geef een reactie

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *