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 vanseprate
naarseparate
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, metMAX_MONTH_LENGTH
om de maximale grootte vast te houden die nodig is voorchar 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 vansomething_else
is groter dansizeof(buf)
. Ik stel meestal het laatste tekenbuf[sizeof(buf)-1] = 0
in om daartegen te beschermen, of alsbuf
nul-geïnitialiseerd is, gebruik dansizeof(buf) - 1
als de kopie lengte. - Gebruik
strlcpy
ofstrcpy_s
of zelfssnprintf
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
ensnprintf
zijn niet direct toegankelijk op MSVC staan tenminste bestellingen enstrcpy_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 vanstrlen
wanneer je ‘ bezorgd bent over het geheugen. Evenzo kunt u vóór de aanroep vanstrcpy
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 naarchar*
, 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. Zelfsstring[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? Eenchar *
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 eenchar[]
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 vanchar* 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 nietconst
, 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 alsconst char *s = "literal";
- ” En in het algemeen is ” asdf ” een constante, dus het moet worden gedeclareerd als const. ” – Dezelfde redenering vraagt om een
const
opint n = 42;
, omdat42
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 die1 + 1
evalueert naar2
. Als het programma waarnaar ik hierboven heb gelinkt iets anders doet danEFGH
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).