Četl jsem vlákno s názvem „strlen vs sizeof“ na CodeGuru a jedna z odpovědí uvádí, že „je vždy [sic] špatný postup inicializovat [sic] a char
pole s řetězcovým literálem. „
Je to pravda, nebo je to jen jeho (byť„ elitní člen „) názor?
Zde je původní otázka:
#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; }
správně. velikost by měla být délka plus 1 ano?
to je výstup
the size of september is 8 and the length is 9
velikost by měla být určitě 10. je to jako jeho výpočet velikosti řetězce, než bude změněn strcpy, ale délka poté.
Je něco v nepořádku s mojí syntaxí nebo co?
Zde je odpověď :
Inicializovat pole char s řetězcovým literálem je každopádně špatný postup. Vždy tedy proveďte jednu z následujících akcí:
const char string1[] = "october"; char string2[20]; strcpy(string2, "september");
Komentáře
- Všimněte si “ const “ v prvním řádku. Je možné, že autor předpokládal c ++ místo c? V jazyce C ++ je to “ špatný postup „, protože literál by měl být const a jakýkoli nedávný kompilátor c ++ vydá varování (nebo chybu) o přiřazení konstantní literál k nekonstantnímu poli.
- @Andr é C ++ definuje řetězcové literály jako maticová pole, protože to je jediný bezpečný způsob řešení s nimi. To, že C nemá ‚ t problém, takže máte sociální pravidlo, které vynucuje bezpečnou věc
- @Caleth. Vím, že jsem se více snažil tvrdit, že autor odpovědi přistupoval k “ špatnému postupu “ z pohledu c ++.
- @Andr é to není ‚ v C ++ špatnou praxí, protože není ‚ ta praxe , ‚ je to chyba typu přímo nahoru. Mělo by to být chyba typu v C, ale není to ‚ t, takže musíte mít pravidlo průvodce stylem, které vám řekne “ Je ‚ zakázáno “
odpovědět
Inicializovat pole char pomocí řetězcového literálu je i tak špatný postup.
Autor tohoto komentáře to ve skutečnosti nikdy neospravedlňuje a tvrzení mi připadá záhadné.
V jazyce C (a toto jste označili jako C), že “ je to skoro jediný způsob, jak inicializovat pole char
s hodnotou řetězce (inicializace se liší od přiřazení). Můžete napsat buď
char string[] = "october";
nebo
char string[8] = "october";
nebo
char string[MAX_MONTH_LENGTH] = "october";
V prvním případě je velikost pole převzata z velikosti inicializátoru. Řetězcové literály jsou uloženy jako pole char
s ukončovacím 0 bajtem, takže velikost pole je 8 („o“, „c“, „t“, „o“, „b“, „e“, „r“, 0). Ve druhých dvou případech je velikost pole zadána jako součást deklarace (8 a MAX_MONTH_LENGTH
, ať už se to stane cokoli).
To, co nemůžete udělat, je napsat něco jako
char string[]; string = "october";
nebo
char string[8]; string = "october";
atd. V prvním případě je deklarace string
neúplná , protože nebyla zadána žádná velikost pole a neexistuje žádný inicializátor, který by velikost převzal. V obou případech =
nebude fungovat, protože a) výraz pole jako string
nemusí být cílem úkolu ab) operátor =
není definován tak, aby i tak zkopíroval obsah jednoho pole do druhého.
Ze stejného tokenu nemůžete psát
char string[] = foo;
kde foo
je další pole char
. Tato forma inicializace bude fungovat pouze s řetězcovými literály.
EDIT
Měl bych to upravit tak, aby bylo možné také inicializovat pole pro uložení řetězce s inicializátorem ve stylu pole, například
char string[] = {"o", "c", "t", "o", "b", "e", "r", 0};
nebo
char string[] = {111, 99, 116, 111, 98, 101, 114, 0}; // assumes ASCII
ale pro oči je jednodušší používat řetězcové literály.
EDIT 2
Chcete-li přiřadit obsah pole mimo deklaraci, budete muset použít buď strcpy/strncpy
(pro řetězce zakončené 0) nebo memcpy
(pro jakýkoli jiný typ pole):
if (sizeof string > strlen("october")) strcpy(string, "october");
nebo
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!
Komentáře
Odpověď
Jediný problém, který si pamatuji, je přiřazení řetězcového literálu k 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
Vezměte si například tento program:
#include <stdio.h> int main() { char *var1 = "september"; char *var2 = "september"; var1[0] = "S"; printf("%s\n", var2); }
To na mé platformě (Linux) selhává, když se pokouší zapsat na stránku označenou jen pro čtení. Na jiných platformách může tisknout „září“ atd.
To znamená – inicializace pomocí literálu způsobí konkrétní množství rezervace, takže to nebude fungovat:
char buf[] = "May"; strncpy(buf, "September", sizeof(buf)); // Result "Sep"
Ale toto bude
char buf[32] = "May"; strncpy(buf, "September", sizeof(buf));
Jako poslední poznámku – nepoužiji strcpy
vůbec:
char buf[8]; strcpy(buf, "very long string very long string"); // Oops. We overwrite some random memory
Zatímco někteří překladači to mohou změnit na bezpečné volání strncpy
je mnohem bezpečnější:
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";
Komentáře
- ‚ stále existuje riziko přetečení vyrovnávací paměti na tom
strncpy
protože to ‚ n null neukončí zkopírovaný řetězec, když délkasomething_else
je větší nežsizeof(buf)
. Obvykle nastavím poslední znakbuf[sizeof(buf)-1] = 0
, který před tím chrání, nebo pokud jebuf
nulová inicializace, použijtesizeof(buf) - 1
jako délka kopie. - Použijte
strlcpy
nebostrcpy_s
nebo dokoncesnprintf
pokud musíte. - Opraveno. Bohužel neexistuje žádný snadný přenosný způsob, jak toho dosáhnout, pokud nemáte luxus pracovat s nejnovějšími překladači (
strlcpy
asnprintf
nejsou přímo přístupné na MSVC alespoň objednávky astrcpy_s
nejsou na * nix). - @MaciejPiechotka: No, díky bohu, Unix odmítl přílohu k sponzorovanou společností Microsoft.
Odpověď
Především proto, že nebudete mít velikost char[]
v proměnné / konstrukci, kterou můžete snadno použít v rámci programu.
Ukázka kódu z odkazu:
char string[] = "october"; strcpy(string, "september");
string
je v zásobníku přidělen jako 7 nebo 8 znaků dlouhý. Nemohu si vzpomenout, jestli je takto zakončen nulou – nebo ne – vlákno, na které jste odkazovali, uvedlo, že je .
Kopírování „září“ přes tento řetězec je zjevným překročením paměti.
Další výzva nastane, pokud předáte string
jiné funkcitakže druhá funkce může zapisovat do pole. Druhé funkci musíte sdělit, jak dlouho je pole tak nevytváří přetečení. Můžete předat string
spolu s výsledkem strlen()
, ale vlákno vysvětluje, jak to může vybuchnout, pokud string
není zakončen nulou.
Je vám lépe přidělení řetězce s pevnou velikostí (nejlépe definovanou jako konstanta) a poté předáním pole a pevné velikosti druhé funkci. Komentáře @John Bode jsou správné a existují způsoby, jak tato rizika zmírnit. Vyžadují také větší úsilí z vaší strany, aby je mohly používat.
Podle mých zkušeností hodnota, kterou jsem inicializoval char[]
to je obvykle příliš malý pro ostatní hodnoty, které tam musím umístit. Použitím definované konstanty se tomuto problému vyhneme.
sizeof string
vám poskytne velikost vyrovnávací paměti (8 bajtů); použijte výsledek tohoto výrazu namísto strlen
, pokud máte obavy o paměť.
Podobně můžete provést kontrolu před voláním strcpy
abyste zjistili, zda je vaše cílová vyrovnávací paměť dostatečně velká pro zdrojový řetězec: if (sizeof target > strlen(src)) { strcpy (target, src); }
.
Ano, pokud musíte předat pole funkci, budete „ll“ je třeba předat i jeho fyzickou velikost: foo (array, sizeof array / sizeof *array);
. – John Bode
Komentáře
-
sizeof string
vám dá velikost bufferu (8 bajtů); použijte výsledek tohoto výrazu namístostrlen
, pokud máte ‚ obavy o paměť. Podobně můžete před volánímstrcpy
zkontrolovat, zda je váš cílový buffer dostatečně velký pro zdrojový řetězec:if (sizeof target > strlen(src)) { strcpy (target, src); }
. Ano, pokud musíte předat pole funkci, musíte ‚ předat také jeho fyzickou velikost:foo (array, sizeof array / sizeof *array);
. - @JohnBode – díky, a to jsou dobré body. Váš komentář jsem začlenil do své odpovědi.
- Přesněji řečeno, většina odkazů na název pole
string
má za následek implicitní převod nachar*
, ukazující na první prvek pole. Tím se ztratí informace o mezích pole. Volání funkce je jen jedním z mnoha kontextů, ve kterých k tomu dochází.char *ptr = string;
je další. Příkladem toho je istring[0]
; operátor[]
pracuje na ukazatelích, nikoli přímo na polích. Doporučené čtení: Oddíl 6 comp.lang.c FAQ . - Nakonec odpověď, která ve skutečnosti odkazuje na otázku!
Odpověď
Jedna věc, kterou ani jedno vlákno nevyvolá, je tato:
char whopping_great[8192] = "foo";
vs.
char whopping_great[8192]; memcpy(whopping_great, "foo", sizeof("foo"));
První bude dělat něco jako:
memcpy(whopping_great, "foo", sizeof("foo")); memset(&whopping_great[sizeof("foo")], 0, sizeof(whopping_great)-sizeof("foo"));
Ten druhý pouze dělá memcpy. Standard C trvá na tom, že pokud je inicializována jakákoli část pole, je to všechno. V tomto případě je tedy lepší to udělat sami. Myslím, že to mohlo být to, k čemu treuss dospěl.
Určitě
char whopping_big[8192]; whopping_big[0] = 0;
je lepší než buď:
char whopping_big[8192] = {0};
nebo
char whopping_big[8192] = "";
ps pro bonusové body, můžete:
memcpy(whopping_great, "foo", (1/(sizeof("foo") <= sizeof(whopping_great)))*sizeof("foo"));
hodit kompilaci časového dělení nulovou chybou, pokud se chystáte pole přetéct.
Odpověď
Myslím, že myšlenka „špatného postupu“ vychází ze skutečnosti, že tato forma:
char string[] = "october is a nice month";
implicitně vytvoří strcpy ze zdrojového strojového kódu do zásobníku.
Je efektivnější zpracovávat pouze odkaz na tento řetězec. Stejně jako:
char *string = "october is a nice month";
nebo přímo:
strcpy(output, "october is a nice month");
(ale samozřejmě ve většině kód to pravděpodobně nezáleží)
Komentáře
- Nechtěl ‚ pouze vytvořit kopii pokud se ho pokusíte upravit? Myslel bych, že kompilátor bude chytřejší než ten
- A co případy jako
char time_buf[] = "00:00";
kde jste ‚ chystáte se upravit vyrovnávací paměť? Achar *
inicializovaný na řetězcový literál je nastaven na adresu prvního bajtu, takže pokus o úpravu má za následek nedefinované chování, protože metoda úložiště řetězcového literálu ‚ je neznámá (implementace definována), zatímco úprava bajtůchar[]
je naprosto legální, protože inicializace zkopíruje bajty do zapisovatelného prostoru přiděleného na zásobníku. Říká se, že ‚ s “ méně efektivní nebo “ špatný postup “ bez rozpracování nuancíchar* vs char[]
je zavádějící.
Odpověď
Nikdy není opravdu dlouhá doba, ale měli byste se vyhnout inicializaci char [] na řetězec, protože „řetězec“ je const char * a vy jej přiřazujete char *. Takže pokud předáte tento char [] metodě, která mění data, můžete mít zajímavé chování.
Jak jsem řekl pochvalu, smíchal jsem trochu char [] s char *, to není dobré, protože se trochu liší.
Na přiřazení dat k char poli není nic špatného, ale protože záměrem použití tohoto pole je použít jej jako „string“ (char *), je snadné zapomenout, že byste to neměli upravovat pole.
Komentáře
- Nesprávné. Inicializace zkopíruje obsah řetězcového literálu do pole. Objekt pole není ‚ t
const
pokud to tak nedefinujete.(A řetězcové literály v jazyce C nejsouconst
, i když jakýkoli pokus o úpravu řetězcového literálu má nedefinované chování.)char *s = "literal";
nemá druh chování, o kterém ‚ mluvíte; je ‚ lépe napsáno jakoconst char *s = "literal";
- “ A obecně “ asdf “ je konstanta, takže by měla být deklarována jako const. “ – Stejné uvažování by vyžadovalo
const
naint n = 42;
, protože42
je konstanta. - Nezáleží na ‚ jakém stroji ‚ znovu zapnete. Jazyková norma zaručuje, že
c
je upravitelný. Je ‚ stejně silná záruka jako ta, kterou1 + 1
vyhodnotí jako2
. Pokud program, na který jsem odkazoval výše , dělá něco jiného než tiskEFGH
, znamená to nevyhovující C implementaci. - @Dainus: kompilátor MSVC má optimalizaci nazvanou ‚ sdružování řetězců ‚, která vloží jednu kopii identické řetězce do segmentu jen pro čtení, pokud může zaručit, že jejich použití je jen pro čtení. Chcete-li vidět ‚ normální ‚ chování, vypněte optimalizaci. Pro vaši informaci “ Upravit a pokračovat “ vyžaduje, aby byla tato možnost zapnutá. Více informací zde: msdn.microsoft.com/en-us/library/s0s0asdt.aspx
- Myslím, že Dainius to v mnoha případů je chyba, že samotná proměnná by měla být označena
const char *const
, aby se zabránilo úpravám bajtů nebo samotného ukazatele, ale v mnoha případech programátoři ponechají jeden nebo oba proměnlivé, což umožní nějaký běhový kód upravit to, co se jeví jako typizovaná konstanta (ale není to konstanta).
strncpy
je málokdy správná odpověďchar[8] str = "october";
je špatný postup. Musel jsem se doslova započítat, abych se ujistil, že to není ‚ t přetečení a při údržbě to praskne … např. oprava pravopisné chyby zseprate
naseparate
se zlomí, pokud se velikost neaktualizuje.strlen()
nezahrnuje znak null pomocíMAX_MONTH_LENGTH
k udržení maximální velikosti potřebné prochar string[]
často vypadá špatně. IMO,MAX_MONTH_SIZE
by zde bylo lepší.