Kirjautuminen

Haku

Tehtävät

Keskustelu: Ohjelmointikysymykset: C: printf ja char osoitin

Sivun loppuun

Ville [18.09.2011 21:22:42]

#

Nyt on jälleen tyhmää päässä ja isosti!
Alla olevassa esimerkissa on kaksi char tyypin osoitinta.
ekaOsoitin on esitelty ja alustettu, toinenOsoitin ainoastaan esitelty.
Arvo lisätty myöhemmin.

Äkkipäätään tulisi mieleen että molemmat osoittimet käyttäytyisivät samalla lailla noissa printf() lauseissa.
Mutta kun ei. Kuten huomaatte ekassa printfissä EI ole sisältöoperaattoria '*' osoittimen nimen edessä ja jälkimmäisessä on.

Tuo alustus esittelyn yhteydessäkö eroavaisuuden saa aikaan vai mikä?

#include<stdio.h>

int main(){
 char *ekaOsoitin = (char*)'W';//castaus kääntäjän varoituksen takia
 char *toinenOsoitin;

 toinenOsoitin = (char*)malloc(1);//castaus varmuuden vuoksi
 *toinenOsoitin = 'A';

 printf("ekaOsoittimen arvo %c\n", ekaOsoitin);
 printf("toinenOsoittimen arvo %c", *toinenOsoitin);

 return 0;
 }

Metabolix [18.09.2011 21:33:36]

#

Ei se kääntäjä kiusallaan varoita vaan siksi, että muunnoksessasi on paha virhe. Muunnat nyt kirjaimellisesti merkin 'W' osoittimeksi. Osoittimesta ei tule mielekäs (minkä vuoksi ohjelma kaatuu, jos yrität käyttää sitä), mutta osoittimen arvo sinänsä on edelleen 'W', joten voit kyllä tulostaa sen.

Asiaa ehkä havainnollistaa, jos tulostat ne osoittimet ihan osoittimina:

printf("%p\n", ekaOsoitin);    // 0x00000057, siis merkin 'W' ASCII-arvo.
printf("%p\n", toinenOsoitin); // Jotain järkevämpää.

Jos haluat osoittimen merkkijonovakioon, sijoita se ensin johonkin.

char data[] = "W";
char* ekaOsoitin = data;

Ville [18.09.2011 21:59:17]

#

Metabolix kirjoitti:

Ei se kääntäjä kiusallaan varoita vaan siksi, että muunnoksessasi on paha virhe. Muunnat nyt kirjaimellisesti merkin 'W' osoittimeksi. Osoittimesta ei tule mielekäs (minkä vuoksi ohjelma kaatuu, jos yrität käyttää sitä), mutta osoittimen arvo sinänsä on edelleen 'W', joten voit kyllä tulostaa sen.

Tarkoitatko että jos jätän castauksen tekemättä ohjelma kaatuu?
Mitä käytänössä tarkoittaa kirjoittamasi lause "Muunnat nyt kirjaimellisesti merkin 'W' osoittimeksi."?
Tarkoitatako että ekaOsoittimesta ei tulekaan osoitinta vaan merkistä W. No mikä ekOsoitin sitten on?
Osaatko tarjota jotakin "rautalanka mallia" asian selvennykseksi?

Metabolix kirjoitti:

Asiaa ehkä havainnollistaa, jos tulostat ne osoittimet ihan osoittimina:

printf("%p\n", ekaOsoitin);    // 0x00000057, siis merkin 'W' ASCII-arvo.
printf("%p\n", toinenOsoitin); // Jotain järkevämpää.

No pitihän se kokeilla mutta valitettavasti kokeilu ei asiaa juurikaan valaissut :(
Sain kyllä tuon kirjoittamasi ascii arvon + jotain järkevämpää. Mutta silti ei nyt aukea miksi toisessa printf lauseessa ei voi olla * merkkiä ja toisessa pitää olla ...
Eikös molemmat ole kuitenkin osoittimia?

Metabolix kirjoitti:

Jos haluat osoittimen merkkijonovakioon, sijoita se ensin johonkin.

char data[] = "W";
char* ekaOsoitin = data;

No tuo on selvää, jopa minulle ;P
Mutta en nyt tavoittele merkkijonoa vaan yksittäistä merkkiä osoittimen avulla.

jcd3nton [18.09.2011 22:02:16]

#

Yksittaisesta merkkivakiosta ei saa osoitetta. Mutta sama kikka toimii: sijoita se char-tyyppiseen muuttujaan, ja ota taman osoite.

Voit toki myos ottaa merkkijonoliteraalin merkista osoitteen:

char *p = &"nakki"[1];

Kannattaa muuten jattaa tuommoiset "varmuuden vuoksi" -castaukset pois koodista.

Ville [18.09.2011 22:23:17]

#

jcd3nton kirjoitti:

Yksittaisesta merkkivakiosta ei saa osoitetta. Mutta sama kikka toimii: sijoita se char-tyyppiseen muuttujaan, ja ota taman osoite.

Ahaa no nyt alkaa valjeta, siis siinä syy printf lauseiden erillaisuuteen.
No itseasiassa tuota mainitsemaasi kikkaaa olen käyttänytkin ja sen ymmärrän kyllä.
Mutta miksi siitä ei saa osoitetta?

jcd3nton kirjoitti:

Kannattaa muuten jattaa tuommoiset "varmuuden vuoksi" -castaukset pois koodista.

Tarkoitatko tätä riviä?

toinenOsoitin = (char*)malloc(1);//castaus varmuuden vuoksi

Eikös mallocin palauttama void ole hyvä castata vastaamaan osoittimen tyyppiä?

jcd3nton [18.09.2011 22:41:20]

#

Ville kirjoitti:

Mutta miksi siitä ei saa osoitetta?

Noin yleisesti ottaen vakioille ei tarvitse eika kannata varata erikseen muistia, vaan niita voidaan kayttaa nopeasti suoraan sellaisenaan kaannetyssa koodissa. Siispa niilla ei myoskaan ole osoitetta. Tama patee myos merkkivakioihin (joiden tyyppi siis on int). Merkkijonoliteraalit ovat poikkeuksellisia, silla prosessorit harvoin osaavat sellaisenaan niita kasitella. Niille taytyy siis varata pala muistia (jolla on tunnettu osoite), jonne tallennettuja merkkeja kasitellaan yksitellen. Ja tassa on pieni seikka, joka jaa usein aloittelijalta huomaamatta: merkkijonoliteraalit voidaan sijoittaa muistiin, johon ohjelmalla on vain lukuoikeus. Siispa merkkijonoliteraalia ei saa muokata (vaikka se jollain jarjestelmalla onnistuisikin, useimmiten se johtaa ohjelman kaatumiseen).

char *p = "nakki"; /* P osoittaa merkkijonoliteraaliin, jota ei saa muokata. */
char a[] = "nakki"; /* Taulu a varataan ja alustetaan; ohjelma saa vapaasti muokata taulun sisaltoa. */

lainaus:

Tarkoitatko tätä riviä?

toinenOsoitin = (char*)malloc(1);//castaus varmuuden vuoksi

Eikös mallocin palauttama void ole hyvä castata vastaamaan osoittimen tyyppiä?

Muunnos void-osoittimesta tapahtuu automaattisesti. Turhat castaukset sen sijaan ovat merkityksetonta koodia, joka ei koodin lukemista yleensa helpota lainkaan, painvastoin. Pahimmassa tapauksessa ne voivat piilottaa virheita, joista kaantaja muuten huomauttaisi.

Metabolix [18.09.2011 22:44:18]

#

jcd3nton kirjoitti:

Voit toki myos ottaa merkkijonoliteraalin merkista osoitteen:

char *p = &"nakki"[1];

Kuten itsekin myöhemmin sanot, merkkijonoliteraalin sisältöä ei saa muokata. Merkin muokkaaminen voi kaataa ohjelman. Siksi tämä ratkaisu ei ole oikeastaan missään tilanteessa oikea.

Jos on tarkoitus hakea const-osoitin, käytetään sitä const-sanaa sitten. Silloin tuossa voi ehkä ollakin jotain järkeä.

Ville kirjoitti:

Eikös molemmat ole kuitenkin osoittimia?

Ovat, mutta toisessa on järjetön osoite. Osoite tarkoittaa jonkin asian sijaintia muistissa. Kun muutat merkin 'W' sijainniksi, mikä sijainti siitä pitäisi mielestäsi tulla? Koodin sekaan vakiona kirjoitettu merkki ei useinkaan varsinaisesti sijaitse missään, vaan kääntäjä luo arvon tyhjästä siinä vaiheessa, kun merkki sijoitetaan johonkin muuttujaan.

Ville [18.09.2011 22:55:47]

#

jcd3nton kirjoitti:

Noin yleisesti ottaen vakioille ei tarvitse eika kannata varata erikseen muistia, vaan niita voidaan kayttaa nopeasti suoraan sellaisenaan kaannetyssa koodissa. Siispa niilla ei myoskaan ole osoitetta. Tama patee myos merkkivakioihin (joiden tyyppi siis on int). Merkkijonoliteraalit ovat poikkeuksellisia, silla prosessorit harvoin osaavat sellaisenaan niita kasitella. Niille taytyy siis varata pala muistia (jolla on tunnettu osoite), jonne tallennettuja merkkeja kasitellaan yksitellen. Ja tassa on pieni seikka, joka jaa usein aloittelijalta huomaamatta: merkkijonoliteraalit voidaan sijoittaa muistiin, johon ohjelmalla on vain lukuoikeus. Siispa merkkijonoliteraalia ei saa muokata (vaikka se jollain jarjestelmalla onnistuisikin, useimmiten se johtaa ohjelman kaatumiseen).

Metabolix kirjoitti:

Ovat, mutta toisessa on järjetön osoite. Osoite tarkoittaa jonkin asian sijaintia muistissa. Kun muutat merkin 'W' sijainniksi, mikä sijainti siitä pitäisi mielestäsi tulla? Koodin sekaan vakiona kirjoitettu merkki ei useinkaan varsinaisesti sijaitse missään, vaan kääntäjä luo arvon tyhjästä siinä vaiheessa, kun merkki sijoitetaan johonkin muuttujaan.

Kiitos selkeistä vastauksista teille molemmille.
Tuli sellainen olo että jopa minä taisin nuo ymmärtää :)

lainaus:

Muunnos void-osoittimesta tapahtuu automaattisesti. Turhat castaukset sen sijaan ovat merkityksetonta koodia, joka ei koodin lukemista yleensa helpota lainkaan, painvastoin. Pahimmassa tapauksessa ne voivat piilottaa virheita, joista kaantaja muuten huomauttaisi.

Tuo olikin aivan uutta tietoa, että oikein automaagisesti!
Olen aina ollut siinä uskossa että castaus on itse tehtävä. Mielestäni olen asiasta lukenut jostain c ohjelmoinnin kirjastakin sekä useammalta werkkosivuilta.

Grez [18.09.2011 23:23:58]

#

Joissakin kielissä (esim. Java, C#) ei ole void-pointtereita(*) eikä tehdä kuin turvallisia tyyppimuunnoksia ilman että castaus määritellään. Eli niissä on ideana, että jos koodaaja ei kerro että "tiedostan että tämä muunnos saattaa tietyissä tilanteissa kadottaa tietoa / olla muuten vaarallinen, mutta haluan tehdä sen silti", tai muuten kääntäjä kieltäytyy kääntämästä.

En ole ihan varma saiko C#:ssa laitettua tuon pois päältä, mutta mielestäni kyseessä on ihan hyvä ominaisuus.

(*)esim. C#:ssä Managed-puolella

jcd3nton [18.09.2011 23:30:22]

#

Myos C-puolella on olemassa kaantajia ja tyokaluja (esim. lint), jotka osaavat huomauttaa implisiittisista muunnoksista, joissa kadotetaan tietoa. Ja on tosiaan varsin hyva tapa kirjoittaa se casti oikeaan paikkaan tallaisissa tapauksissa (jos todellakin tiedat mita olet tekemassa).

jlaire [19.09.2011 10:34:21]

#

Jos muilta osin tyylikkäässä C-koodissa on eksplisiittisiä void-pointtereiden tyyppimuunnoksia, syy on yleensä se, että koodin halutaan olevan myös validia C++:aa, jossa kyseinen muunnos ei tapahdu automaattisesti. Pääasiassa C++:aa käyttävät tai muuten vaan ignorantit koodarit eivät välttämättä tiedä tätä eroa kielissä, joten ei kannata hämmästyä jos joku väittää että C:ssäkin tyyppimuunnos on pakollinen.

Pekka Karjalainen [19.09.2011 14:01:24]

#

Kernighanin ja Ritchien kakkospainoksensa lukeneetkin saattavat luulla, että mallocin palautusarvon tyyppimuunnos kannattaa tehdä. He nimittäin neuvovat sen tekemään tässä muuten hyvin luotettavassa kirjassa.

Samaisen kirjan erratan lukeneet taas tietävät, etteivät he suosittele näin tekemään. Kirja kirjoitettiin silloin kuin ANSI-standardi oli vielä uusi, ja he eivät olleet miettineet asiaa perusteellisesti. Lisäksi vanha C vaati tyyppimuunnoksen, koska siinä malloc palauttaa char*-tyypin.

Vinkkinä vielä: Malloc-rivin voi kirjoittaa näin, jolloin siinä ei tarvita ollenkaan tyyppien nimien mainintaa. Silloin sitä ei tarvitse muuttaa, jos käytetty tyyppi muuttuu. Alla voi olettaa, että T-tyyppi on jokin perustyyppi, jonka arvoa voi haluta muuttaa perustellusta syystä. Esim. doublen voi joskus vaihtaa floatiksi hyvästäkin syystä.

Oletan tässä, että N:n arvon on järkevästi valittu vakio tai se tarkistetaan sopivaksi, jottei muistia yritetä varata liian paljon, eikä tapahdu kokonaisluvun kertolaskun ns. kiertymistä ympäri (jos tulos ei mahdu size_t-tyypin parametriin).

T *data; // täysin keksitty muuttujan nimi
data = malloc(sizeof *data * N); // pyydetään tilaa N:lle T-tyypin arvolle

Tai ehkä hieman selvemmin näin:

data = malloc(N * sizeof(*data));

Sulutus sopii heille, jotka laittavat return-lauseisiinkin sulut ( :) )

EDIT: Editoin pois tyhmän muuttujan nimen. Esimerkeissäkin pitää miettiä järkevät nimet mokomille!

EDIT2: Oletin tätä kirjoittaessani, että N on valittu järkevästi tai tarkistetaan ennen malloc-kutsua, jotta se ei aiheuta ongelmia. Valitettavasti en kirjoittanut tätä selvästi näkyviin, joten syntyi paljon turhaa ja heikkolaatuista keskustelua. En välittänyt enää vastata siihen mitenkään. Lisäsin kuitenkin oletuksen N:n arvosta selvästi tekstiini.

jcd3nton [19.09.2011 14:46:50]

#

Pekka Karjalainen kirjoitti:

data = malloc(sizeof *data * N); // pyydetään tilaa N:lle T-tyypin arvolle

Tai ehkä hieman selvemmin näin:

data = malloc(N * sizeof(*data));

Kummassakin tavassa on vaarana overflow. Kannattaa mielummin kayttaa callocia.

jalski [19.09.2011 15:53:36]

#

jcd3nton kirjoitti:

Kummassakin tavassa on vaarana overflow. Kannattaa mielummin kayttaa callocia.

Ei kai tuolla nyt oikeasti paljon väliä ole, käyttääkö malloc:ia vai calloc:ia?

Eihän noissa taida muuta eroa olla kuin, että toinen noista palauttaa osoittiminen nollilla alustetun muistilohkon alkuun ja tekee yhden kertolaskun käyttäjän puolesta.

Pekka Karjalainen [19.09.2011 16:09:03]

#

Calloc osaa joskus tarkistaa, ettei kertolaskun tulos kierry ympäri, koska se ei mahdu size_t-tyyppiin. Se ei kuitenkaan tee sitä jokaisessa C-toteutuksessa, joten kokoargumentti kannattaa tarkastaa ihan itse mahdollisimman luotettavassa koodissa. Tuloksena voi muuten olla, että toiminto varaa muistialueen, joka on haluttua pienempi. (Se on yleensä pulmallista.)

Ks. Suositus Cert.orgista

Torgo [19.09.2011 16:19:01]

#

Pekka Karjalainen kirjoitti:

Calloc osaa joskus tarkistaa, ettei kertolaskun tulos kierry ympäri, koska se ei mahdu size_t-tyyppiin. Se ei kuitenkaan tee sitä jokaisessa C-toteutuksessa, joten kokoargumentti kannattaa tarkastaa ihan itse mahdollisimman luotettavassa koodissa.

Vaikka tiedettäisiinkin että koodia käytetään vain ympäristössä, missä tarkistus on käytössä, vie noin suuren muistin varaaminen callocilla älyttömästi aikaa verrattuna mallociin. Jos ympäripyörähdyksen mahdollisuus on oikeasti olemassa, se kannattaa joka tapauksessa tarkistaa itse. Callocista on vain haittaa, ellei alustaminen nollaksi todella ole tarpeen.

jcd3nton [19.09.2011 16:21:41]

#

Pekka Karjalainen kirjoitti:

Calloc osaa joskus tarkistaa, ettei kertolaskun tulos kierry ympäri, koska se ei mahdu size_t-tyyppiin. Se ei kuitenkaan tee sitä jokaisessa C-toteutuksessa, joten kokoargumentti kannattaa tarkastaa ihan itse mahdollisimman luotettavassa koodissa.

Nain kannattaa tosiaan menetella, jos koodia on tarkoitus pyorittaa vanhoilla korjauskelvottomilla implementaatioilla. Muussa tapauksessa ei kuitenkaan kannata liikaa huolehtia yksittaisten bugisten toteutusten oikuista -- korjataan ne viat siella missa ovat. Nopealla vilkaisulla ainakin glibc, darwin, opensolaris, uclibc, musl, ja open/net/freebsd tekevat overflow-tarkistuksen kuten kuuluu.

User137 [19.09.2011 16:56:50]

#

Riippuu mitä tyyppiä sizeof() paluuarvo on? En harrasta C:tä, mutta oletan että kertolaskun tulos on samaa int tyyppiä mitä suurempi kertolaskun osapuoli. Jos sizeof() on 4 tavuinen int, niin koko joka ylittää 4 294 967 295 alkaa kierroksen nollasta.
(Mutta harvemmin tarviit varata yli 4 gigaa muistia...)

jalski [19.09.2011 17:12:12]

#

User137 kirjoitti:

En harrasta C:tä, mutta oletan että kertolaskun tulos on samaa int tyyppiä mitä suurempi kertolaskun osapuoli. Jos sizeof() on 4 tavuinen int, niin koko joka ylittää 4 294 967 295 alkaa kierroksen nollasta.
(Mutta harvemmin tarviit varata yli 4 gigaa muistia...)

Minä taas en harrasta moderneja käyttöjärjestelmiä, mutta kuinka moni käyttöjärjestelmä antaa varata prosessin käyttöön edes tuota 4 gigatavua? ;-)

jcd3nton [19.09.2011 17:20:34]

#

jalski kirjoitti:

User137 kirjoitti:

En harrasta C:tä, mutta oletan että kertolaskun tulos on samaa int tyyppiä mitä suurempi kertolaskun osapuoli. Jos sizeof() on 4 tavuinen int, niin koko joka ylittää 4 294 967 295 alkaa kierroksen nollasta.
(Mutta harvemmin tarviit varata yli 4 gigaa muistia...)

Minä taas en harrasta moderneja käyttöjärjestelmiä, mutta kuinka moni käyttöjärjestelmä antaa varata prosessin käyttöön edes tuota 4 gigatavua? ;-)

Ei tarvitse antaakaan. Ongelma on siina, etta kun paha-aikeinen kayttaja syottaa liian suuren numeron, niin kertolaskun tulos pyorahtaa ympari, jolloin saadaan passattua mallocille kelpaava sopivan pieni luku. Sen varaaminen todennakoisesti siis onnistuu, mutta koska ohjelma ei hoksaa overflowia, se kuvittelee saaneensa suunnattoman palan muistia, ja antaa huoletta kirjoittaa ohi puskurin.

Callocille annetaan luvut sellaisenaan, ja se voi ennen kertolaskun suorittamista (tai miksei sen jalkeenkin) varmistaa, ettei pyorahtamista paase tapahtumaan. Jos tulos on liian suuri, mitaan muistia ei varata, ja calloc palauttaa NULLin.

Metabolix [19.09.2011 19:17:04]

#

User137 kirjoitti:

Riippuu mitä tyyppiä sizeof() paluuarvo on?

sizeof-operaattori palauttaa size_t-tyyppisen luvun, joka kuvaa juurikin järjestelmän tukemaa kokoa. Samaista tyyppiä edustaa malloc-funktion parametri.

jalski kirjoitti:

Minä taas en harrasta moderneja käyttöjärjestelmiä, mutta kuinka moni käyttöjärjestelmä antaa varata prosessin käyttöön edes tuota 4 gigatavua? ;-)

Esimerkiksi minulla yksinkertainen C++-testi saa Linuxissa varattua noin 36000 gigatavua virtuaalimuistia.


Sivun alkuun

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta