Luin ketjua ”strlen vs sizeof” CodeGurussa ja yksi vastauksista toteaa, että ”se on joka tapauksessa [sic] huonoa käytäntöä aloittaa [sic] char
taulukko merkkijonolla. ”
Onko tämä totta, vai onko se vain hänen (vaikkakin” eliittijäsenen ”) mielipide?
Tässä on alkuperäinen kysymys:
#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; }
oikea. koon tulisi olla pituus plus 1 kyllä?
tämä on ulostulon
the size of september is 8 and the length is 9
koon tulisi olla varmasti 10. Se on sama kuin laskettaessa merkkijonon kokoa, ennen kuin strcpy muuttaa sitä, mutta pituus jälkeen.
Onko syntaksissani jotain vikaa vai mitä?
Tässä on vastaus :
On joka tapauksessa huono käytäntö aloittaa char-taulukko merkkijonon kirjaimella. Tee siis jokin seuraavista:
const char string1[] = "october"; char string2[20]; strcpy(string2, "september");
Kommentit
- Huomaa ” const ” ensimmäisellä rivillä. Voisiko kirjailija olettaa, että c ++ ei ole c? C ++: ssa se on ” huono käytäntö ”, koska literaalin tulisi olla const ja mikä tahansa viimeaikainen c ++ -kääntäjä antaa varoituksen (tai virheen) konst-kirjaimen määrittämisestä muulle kuin const-taulukolle.
- @Andr é C ++ määrittelee merkkijonon literaaliksi const-matriiseiksi, koska se on ainoa turvallinen tapa käsitellä heidän kanssaan. Se, että C ei ’ t , on ongelma, joten sinulla on sosiaalinen sääntö, joka panee täytäntöön turvallisen asian
- @Caleth. Tiedän, yritin enemmän väittää, että vastauksen kirjoittaja lähestyi ” huonoa käytäntöä ” c ++ -näkökulmasta.
- @Andr é se ei ole ’ ta huono käytäntö C ++: ssa, koska se ei ole ’ ta harjoittaa , se ’ on suora tyyppivirhe. Sen pitäisi olla tyypin virhe C: ssä, mutta se ei ole ’ t, joten sinulla on oltava tyylioppaan sääntö, joka kertoo sinulle ” Se ’ on kielletty ”
vastaus
On joka tapauksessa huono käytäntö aloittaa char-taulukko merkkijonon kirjaimella.
Kommentin kirjoittaja ei koskaan oikeastaan perustele sitä, ja pidän lausetta hämmentävänä.
Kohdassa C (ja olet merkinnyt tämän nimellä C), että ” se on melkein ainoa tapa alustaa taulukko char
merkkijonolla (alustaminen eroaa tehtävästä). Voit kirjoittaa joko
char string[] = "october";
tai
char string[8] = "october";
tai
char string[MAX_MONTH_LENGTH] = "october";
Ensimmäisessä tapauksessa matriisin koko otetaan alustimen koosta. Merkkijono-literaalit tallennetaan taulukkoina char
päättyvällä 0 tavulla, joten taulukon koko on 8 (”o”, ”c”, ”t”, ”o”, ”b”, ”e”, ”r”, 0). Kahdessa toisessa tapauksessa taulukon koko määritetään osana ilmoitusta (8 ja MAX_MONTH_LENGTH
, riippumatta siitä mitä tapahtuu).
Mitä et voi tehdä, on kirjoittaa esimerkiksi
char string[]; string = "october";
tai
char string[8]; string = "october";
jne. Ensimmäisessä tapauksessa string
-ilmoitus on epätäydellinen , koska matriisikokoa ei ole määritetty eikä koon ottamiseen ole alustinta. Molemmissa tapauksissa =
ei toimi, koska a) matriisilauseke, kuten string
, ei ehkä ole tehtävän kohde ja b) =
-operaattoria ei ole määritelty kopioimaan yhden taulukon sisältö toiseen.
Samalla tunnuksella voit ”t kirjoittaa”
char string[] = foo;
jossa foo
on toinen ryhmän char
joukko. Tämä alustusmuoto toimii vain merkkijono-literaalien kanssa.
MUOKKAA
Haluan muuttaa tätä sanomalla, että voit myös alustaa taulukot, jotka pitävät merkkijonoa taulukotyyppisellä alustusohjelmalla, kuten
char string[] = {"o", "c", "t", "o", "b", "e", "r", 0};
tai
char string[] = {111, 99, 116, 111, 98, 101, 114, 0}; // assumes ASCII
mutta merkkijono-literaaleja on silmissä helpompaa käyttää.
MUOKKAA 2
Jotta Määritä taulukon sisältö ilmoituksen ulkopuolelle, sinun on käytettävä joko strcpy/strncpy
(0-päättyville merkkijonoille) tai memcpy
(minkä tahansa muun tyyppisen taulukon kohdalla):
if (sizeof string > strlen("october")) strcpy(string, "october");
tai
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!
Kommentit
Vastaa
Ainoa ongelma, jonka muistan, on merkkijonon kirjaimellisen osoittaminen 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
Ota esimerkiksi tämä ohjelma:
#include <stdio.h> int main() { char *var1 = "september"; char *var2 = "september"; var1[0] = "S"; printf("%s\n", var2); }
Tämä foorumillani (Linux) kaatuu, kun se yrittää kirjoittaa vain luku -merkitylle sivulle. Muilla alustoilla se saattaa tulostaa ”syyskuu” jne.
Sanottu – alustaminen kirjaimella tekee tietyn varauksen määrän, joten tämä ei toimi:
char buf[] = "May"; strncpy(buf, "September", sizeof(buf)); // Result "Sep"
Mutta tämä
char buf[32] = "May"; strncpy(buf, "September", sizeof(buf));
Viimeisenä huomautuksena – en käytä strcpy
ollenkaan:
char buf[8]; strcpy(buf, "very long string very long string"); // Oops. We overwrite some random memory
Vaikka jotkut kääntäjät voivat muuttaa sen turvalliseksi puheluksi, strncpy
on paljon turvallisempaa:
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";
Kommentit
- Siellä ’ on edelleen puskurin ylityksen riski
strncpy
, koska se ei ’ t nollaa kopioidun merkkijonon, kunsomething_else
on suurempi kuinsizeof(buf)
. Asetan yleensä viimeisen merkinbuf[sizeof(buf)-1] = 0
suojaamaan siitä, tai josbuf
on nolla-alusta, käytäsizeof(buf) - 1
kopion pituudeksi. - Käytä
strlcpy
taistrcpy_s
tai jopasnprintf
, jos joudut. - Korjattu. Valitettavasti tähän ei ole helppoa kannettavaa tapaa, ellei sinulla ole ylellisyyttä työskennellä uusimpien kääntäjien kanssa (
strlcpy
jasnprintf
eivät ole suoraan käytettävissä MSVC: llä ainakin tilaukset jastrcpy_s
eivät ole käytössä * nix). - @MaciejPiechotka: No, kiitos jumalalle, että Unix hylkäsi Microsoftin tukeman liitteen k.
vastaus
Ensinnäkin siksi, että sinulla ei ole kokoa char[]
muuttujassa / rakenteessa, jota voit helposti käyttää ohjelmassa.
Koodinäyte linkistä:
char string[] = "october"; strcpy(string, "september");
string
on varattu pinolle 7 tai 8 merkin pituiseksi. En voi muistaa, onko sen lopettaminen tällä tavalla vai ei – linkittämäsi ketju ilmoitti, että se on .
”Syyskuun” kopioiminen merkkijonon päälle on selvä muistin ylitys.
Toinen haaste syntyy, jos välität string
toiselle funktiolle.joten toinen toiminto voi kirjoittaa taulukkoon. Sinun on kerrottava toiselle funktiolle, kuinka kauan taulukko on niin se ei luo ylitystä. Voit välittää string
-kohdan strlen()
, mutta säie selittää, miten tämä voi räjähtää, jos string
ei ole lopetettu.
Sinulla on parempi varataan merkkijono, jolla on kiinteä koko (mieluiten määritellään vakiona) ja siirretään sitten taulukko ja kiinteä koko toiselle toiminnolle. @John Boden kommentit ovat oikein, ja on olemassa tapoja lieventää näitä riskejä. Niiden käyttö vaatii myös enemmän ponnisteluja.
Kokemukseni mukaan arvo, jonka aloitin char[]
on yleensä liian pieni muille arvoille, jotka minun on lisättävä sinne. Määritetyn vakion käyttäminen auttaa välttämään ongelman.
sizeof string
antaa puskurin koon (8 tavua); käytä lausekkeen tulosta strlen
sijaan, kun olet huolissasi muistista.
Vastaavasti voit tehdä tarkistuksen ennen strcpy
nähdäksesi onko kohdepuskuri riittävän suuri lähdemerkkijonoa varten: if (sizeof target > strlen(src)) { strcpy (target, src); }
.
Kyllä, jos sinun on välitettävä taulukko funktiolle, täytyy siirtää myös sen fyysinen koko: foo (array, sizeof array / sizeof *array);
. – John Bode
Kommentit
-
sizeof string
antaa sinulle puskurin koon (8 tavua); käytä lausekkeen tulostastrlen
sijaan, kun ’ olet huolestunut muistista. Vastaavasti voit tarkistaa ennen puhelua numeroonstrcpy
nähdäksesi, onko kohdepuskuri riittävän suuri lähdemerkkijonolle:if (sizeof target > strlen(src)) { strcpy (target, src); }
. Kyllä, jos sinun on välitettävä taulukko funktiolle, ’ sinun on välitettävä myös sen fyysinen koko:foo (array, sizeof array / sizeof *array);
. - @JohnBode – kiitos, ja nämä ovat hyviä kohtia. Olen sisällyttänyt kommenttisi vastaukseeni.
- Tarkemmin sanottuna suurin osa viitteistä taulukon nimestä
string
johtaa implisiittiseen muuntamiseenchar*
, osoittamalla taulukon ensimmäiseen elementtiin. Tämä menettää taulukon rajatiedot. Funktiokutsu on vain yksi monista tilanteista, joissa näin tapahtuu.char *ptr = string;
on toinen. Jopastring[0]
on esimerkki tästä;[]
-operaattori toimii osoittimilla, ei suoraan matriiseilla. Ehdotettu lukeminen: comp.lang.c UKK: n osio 6. - Lopuksi vastaus, joka todella viittaa kysymykseen!
Vastaa
Yksi asia, jota kumpikaan säie ei tuo esiin, on tämä:
char whopping_great[8192] = "foo";
vs.
char whopping_great[8192]; memcpy(whopping_great, "foo", sizeof("foo"));
Edelliset tekevät jotain seuraavista:
memcpy(whopping_great, "foo", sizeof("foo")); memset(&whopping_great[sizeof("foo")], 0, sizeof(whopping_great)-sizeof("foo"));
Jälkimmäinen tekee vain memcpy: n. C-standardi vaatii, että jos jokin matriisin osa alustetaan, kaikki on. Joten tässä tapauksessa on parempi tehdä se itse. Luulen, että treuss saattoi olla siinä.
Varmasti
char whopping_big[8192]; whopping_big[0] = 0;
on parempi kuin jompikumpi:
char whopping_big[8192] = {0};
tai
char whopping_big[8192] = "";
ps For bonuspisteitä, voit tehdä:
memcpy(whopping_great, "foo", (1/(sizeof("foo") <= sizeof(whopping_great)))*sizeof("foo"));
heittää käännösaika jakamalla nollavirheellä, jos aiot ylittää taulukon.
vastaus
Mielestäni ”huonojen käytäntöjen” idea tulee siitä, että tämä muoto:
char string[] = "october is a nice month";
tekee implisiittisesti strcpy: n lähdekoodikoodista pinoon.
On tehokkaampaa käsitellä vain linkkiä kyseiseen merkkijonoon. Kuten:
char *string = "october is a nice month";
tai suoraan:
strcpy(output, "october is a nice month");
(mutta tietysti useimmissa koodaa sillä ei todennäköisesti ole väliä)
Kommentit
- Ei ’ t vain tekisi kopion jos yrität muokata sitä? Luulen, että kääntäjä olisi sitä älykkäämpi
- Entä tapaukset, kuten
char time_buf[] = "00:00";
, joissa ’ muokataanko puskuria? Merkkijonon kirjaimeksi alustettuchar *
asetetaan ensimmäisen tavun osoitteeksi, joten sen yrittäminen johtaa määrittelemättömään toimintaan, koska merkkijonon literal ’ -tallennusmenetelmää ei tunneta (toteutus määritelty), kun taaschar[]
-tavujen muokkaaminen on täysin laillista, koska alustaminen kopioi tavut pinoon varattuun kirjoitettavaan tilaan. Sanotaan, että se ’ s ” vähemmän tehokas tai ” huono käytäntö ” käsittelemättächar* vs char[]
on harhaanjohtava.
Vastaa
Koskaan ei ole todella pitkä aika, mutta sinun tulisi välttää alustusmerkkiä [] merkkijonoon, koska ”merkkijono” on const char *, ja määrität sen merkille char *. Joten jos välität tämän merkin [] menetelmälle, joka muuttaa tietoja, sinulla voi olla mielenkiintoinen käyttäytyminen.
Kuten kiitosta sanoin, sekoitin vähän char [] char *: een, se ei ole hyvä, koska ne eroavat hieman.
Tietojen osoittamisessa char-taulukolle ei ole mitään vikaa, mutta koska tämän taulukon tarkoituksena on käyttää sitä merkkijonona (char *), on helppo unohtaa, että sinun ei pidä muokata tätä taulukko.
Kommentit
- Virheellinen. Alustus kopioi merkkijonon sisällön taulukkoon. Taulukko-objekti ei ole ’ t
const
ellet määritä sitä tällä tavalla.(Ja merkkijonon literaalit C: ssä eivät oleconst
, vaikka kaikilla merkkijonolitraalin muokkausyrityksillä on määrittelemätön käyttäytyminen.)char *s = "literal";
on sellaisesta käyttäytymisestä, josta ’ puhut; se ’ on parempi kirjoittaa nimelläconst char *s = "literal";
- ” Ja yleensä ” asdf ” on vakio, joten se tulisi ilmoittaa konst. ” – Sama päättely vaatii
const
kohtaanint n = 42;
, koska42
on vakio. - Sillä ’ ei ole väliä mitä konetta ’ käytät. Kielistandardi takaa, että
c
on muokattavissa. Se ’ on täsmälleen yhtä vahva takuu kuin1 + 1
arvioksi2
. Jos yllä linkitetty ohjelma tekee muuta kuin tulostamallaEFGH
, se tarkoittaa vaatimusten vastaista C-toteutusta. - @Dainus: MSVC-kääntäjällä on optimointi nimeltä ’ merkkijonojen yhdistäminen ’, joka laittaa yhden kopion identtiset merkkijonot vain luku -segmentiksi, jos se voi taata, että niiden käyttö on vain luku -tilaa. Katkaise optimointi käytöstä nähdäksesi ’ normaalin ’ käyttäytymisen. FYI ” Muokkaa ja jatka ” vaatii tämän vaihtoehdon olevan päällä. Lisätietoja täältä: msdn.microsoft.com/en-us/library/s0s0asdt.aspx
- Luulen, että Dainius ehdottaa sitä monissa tapauksissa virhe on, että muuttuja itsessään on merkittävä
const char *const
estämään tavujen tai itse osoittimen muokkaaminen, mutta monissa tapauksissa ohjelmoijat jättävät yhden tai molemmat muutettaviksi, jolloin osa ajonaikaisesta koodista muokata kirjoitettua vakiota (mutta ei vakiota).
strncpy
on harvoin oikea vastauschar[8] str = "october";
on huono käytäntö. Minun oli kirjaimellisesti kirjattava itseni varmistaakseni, että se ei ollut ’ ylivuoto ja se rikkoutuu huollon aikana … esim. kirjoitusvirheen korjaaminenseprate
–separate
rikkoo, jos kokoa ei päivitetä.strlen()
, ei sisällä nollamerkkiä, käyttämälläMAX_MONTH_LENGTH
, jottachar string[]
tarvitsisi enimmäiskoon, näyttää usein väärältä. IMO,MAX_MONTH_SIZE
olisi parempi tässä.