Je inicializace znaku [] řetězcovým literálem špatná praxe?

Č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

  • strncpy je málokdy správná odpověď
  • @KeithThompson: nesouhlasím, pouze jsem jej pro úplnost přidal ‚.
  • Vezměte prosím na vědomí, že 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 z seprate na separate se zlomí, pokud se velikost neaktualizuje.
  • Souhlasím s djechlin, to je špatná praxe z uvedených důvodů. JohnBode ‚ s odpovědí ‚ vůbec nekomentuje “ špatný postup “ aspekt (který je hlavní částí otázky !!), pouze vysvětluje, co můžete nebo nemůžete udělat pro inicializaci pole.
  • Minor: As ‚ délka “ hodnota vrácená z strlen() nezahrnuje znak null pomocí MAX_MONTH_LENGTH k udržení maximální velikosti potřebné pro char string[] často vypadá špatně. IMO, MAX_MONTH_SIZE by zde bylo lepší.

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élka something_else je větší než sizeof(buf). Obvykle nastavím poslední znak buf[sizeof(buf)-1] = 0, který před tím chrání, nebo pokud je buf nulová inicializace, použijte sizeof(buf) - 1 jako délka kopie.
  • Použijte strlcpy nebo strcpy_s nebo dokonce snprintf 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 a snprintf nejsou přímo přístupné na MSVC alespoň objednávky a strcpy_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ísto strlen, pokud máte ‚ obavy o paměť. Podobně můžete před voláním strcpy 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 na char*, 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 i string[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ěť? A char * 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 nejsou const, 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 jako const 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 na int n = 42;, protože 42 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, kterou 1 + 1 vyhodnotí jako 2. Pokud program, na který jsem odkazoval výše , dělá něco jiného než tisk EFGH, 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).

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *