1 #include <studio.h> 2 3 int main(void) { 4 int lkm, ok_lkm; 5 char rivi[128]; 6 7 do { 8 printf("Anna kokonaisluku: "); 9 gets(rivi); 10 ok_lkm = sscanf(rivi, "%D", &lkm); 11 if (ok_lkm != 1) { 12 printf("\n\nVirheellinen 13 syöte.\n\n"); 14 } 15 } while (ok_lkm != 1); 16 17 printf("Annoit luvun %d\n", lkm); 18 }
Ensiksikin kannattaa käyttää kooditageja. Seuraavaksi kannattaa lukea C-opas. Siellä varmaankin kerrotaan, mitä tuo ohjelma tekee, mutta voin yrittää:
#include <studio.h> // Sisällytetään standardikirjaston IO-ominaisuudet int main(void) { // Pääohjelma int lkm, ok_lkm; // Alustetaan kokonaislukutyyppiset muuttujat lkm ja ok_lkm char rivi[128]; // Alustetaan 128 merkkiä pitkä taulukko rivi do { // Toiston alustus printf("Anna kokonaisluku: "); // Tulostetaan konsoliin teksti Anna kokonaisluku: gets(rivi); // Luetaan kirjoitetut merkit rivi-taulukkoon ok_lkm = sscanf(rivi, "%D", &lkm); // Muutetaan luettu taulukko rivi luvuksi ja tallennetaan se muuttujaan ok_lkm if (ok_lkm != 1) { // Jos ok_lkm on erisuuri kuin 1 printf("\n\nVirheellinen // Hassusti jaettu rivi, joka ei ole syntaksin mukainen syöte.\n\n"); // Loppu hassusti jaetusta rivistä } // Poistutaan jossista } while (ok_lkm != 1); // Toistetaan looppia niin kauan kuin ok_lkm on erisuuri kuin 1 ja lopetetaan toistorakenne printf("Annoit luvun %d\n", lkm); // Tulostetaan formatoitu teksti esim. Annoit luvun 0(rivintaihto) } // Poistutaan pääohjelmasta
Helpompaa olisi varmaan kääntä ja kokeilla mitä ohjelma tekee.
Hennkka kirjoitti:
#include <studio.h> // Sisällytetään standardikirjaston IO-ominaisuudet
Tämä pitäisi olla stdio.h. Standardikirjasto ei sisällä studio-osia.
Hennkka kirjoitti:
int lkm, ok_lkm; // Alustetaan kokonaislukutyyppiset muuttujat lkm ja ok_lkm char rivi[128]; // Alustetaan 128 merkkiä pitkä taulukko rivi
Ei alusteta. Ainoastaan esitellään.
Hennkka kirjoitti:
ok_lkm = sscanf(rivi, "%D", &lkm); // Muutetaan luettu taulukko rivi luvuksi ja tallennetaan se muuttujaan ok_lkm
Ei, vaan muuttujaan lkm. Muuttujaan ok_lkm sijoitetaan syötteestä onnistuneesti luettujen lukujen määrä. Mikä siis tässä tapauksessa pitäisi olla 1, koska syötteestä haetaan yhtä lukua.
Hennkka kirjoitti:
} while (ok_lkm != 1); // Toistetaan looppia niin kauan kuin ok_lkm on erisuuri kuin 1 ja lopetetaan toistorakenne
Suomeksi sanottuna kysytään lukua uudestaan, kunnes käyttäjä antaa kelvollisen syötteen.
No koodaankin C++:lla, joten C:n standardikirjasto ei ole hirveän tuttu :)
Torgo kirjoitti:
Ei alusteta. Ainoastaan esitellään.
Mitä luultavimmin C:n kääntäjä sijoittaa noihin kohtiin nollia, mutta olet oikeassa, että alustaminen on väärä termi, se kun vain lienee helpoimmin ymmärrettävissä ja tuli ensiksi mieleen, ja muuttujat oikeastaan esitellään ja määritellään tuossa kohdassa.
En jaksanut ihan hirveästi lukea tuon sscanf:n spekseistä, kuinka se oikeasti toimii, joten se on väärin minun osaltani. Ei kyllä alkuperäisessä koodissakaan hirveästi kehumista ollut :/
Hennkka kirjoitti:
Mitä luultavimmin C:n kääntäjä sijoittaa noihin kohtiin nollia
Tuskin.
Hennkka kirjoitti:
Mitä luultavimmin C:n kääntäjä sijoittaa noihin kohtiin nollia, mutta olet oikeassa
Ei näytä sijoittavan. Tosin ekalla kerralla ilmeisesti prosessi saa nollattua muistia, mutta selvästikään alustamattomiin muuttujiin ei kääntäjä sijoita mitään:
#include <stdio.h> void testi() { int i; printf("%d\n", i); i = 5; printf("%d\n", i); } void testi2() { int o; printf("%d\n", o); o = 15; printf("%d\n", o); } int main(void) { testi(); testi2(); return 0; }
$ gcc hennkka.c -o hennkka
$ ./hennkka
0
5
5
15
jcd3nton kirjoitti:
Hennkka kirjoitti:
Mitä luultavimmin C:n kääntäjä sijoittaa noihin kohtiin nollia
Tuskin.
Luultavasti Debug käännöksessä alustetaan nolliksi, mutta varsinaisessa release-versiossa ei.
tkok kirjoitti:
Luultavasti Debug käännöksessä alustetaan nolliksi, mutta varsinaisessa release-versiossa ei.
Tuohan olisi käänteishyödyllistä. Ennemmin melkein kannattaisi debug-käännöksessä tarkoituksella tunkea roskaa niihin.
Voisin kuvitella että ainakin osassa käyttiksistä pinolle varattu muisti nollataan ohjelmaa käynnistettäessä (ettei esim. suljetun prosessin tiedot vuoda toiselle ohjelmalle), jossa tapauksessa näyttää siltä kuin arvot olisi nollattu. Sitten kun ohjelma käyttää uudelleen pinoa, niin niissä onkin roskaa. Tämän huomaa erinomaisesti tuosta aiemmin laittamasta esimerkistäni.
Grez kirjoitti:
tkok kirjoitti:
Luultavasti Debug käännöksessä alustetaan nolliksi, mutta varsinaisessa release-versiossa ei.
Tuohan olisi käänteishyödyllistä. Ennemmin melkein kannattaisi debug-käännöksessä tarkoituksella tunkea roskaa niihin.
Hyödyllisyys riippu mitä testataan ja missä vaiheessa mennään ohjelmiston kehityskaaressa. Mutta kaikki on kiinni kääntäjästä ja sen asetuksista.
Mitä hyötyä voisi olla siitä, että kääntäjä olisi debugissa anteeksiantavampi virheille kuin releasissa? Debugissahan on idea löytää mahdollisimman suuri osa virheistä.
Toki se on joo eri asia, jos oletuksena kääntäjä toimii järkevästi mutta käyttäjä käy säätämässä sen hölmösti.
tkok kirjoitti:
Luultavasti Debug käännöksessä alustetaan nolliksi, mutta varsinaisessa release-versiossa ei.
Ei tällä ole mitään tekemistä asian kanssa. Jos nuo alustetaan nolliksi, niin sen tekee jokin muu kuin kääntäjä. Käyttöjärjestelmältä on järkevää tarjota prosessille valmiiksi alustettua muistia. Muuten olisi varsin helppoa tehdä ohjelma millä luetaan muiden prosessien muistiin jättämiä jälkiä.
Kääntäjä ei alustusta oma-aloitteisesti tee, koska silloin se toimisi vastoin standardia. Jotkut kääntäjät voi kyllä komentaa erikseen niin tekemään. Esim. gcc -finit-local-zero
Globaalit ja staattiset muuttujat sen sijaan alustetaan nollaksi, jos alkuarvoa ei ole eksplisiittisesti kerrottu.
Edit.
Nyt kun testasin, niin nähtävästi tuo lippu ei olekaan gcc:lle kelvollinen. Ajoin myös Grezin esimerkkiohjelman Ubuntussa seuraavin tuloksin:
-1081663304
5
5
15
Näyttäisi tuon perusteella siltä, että edes käyttöjärjestelmä ei oletukseni vastaisesti alusta tuota muistia (ainakaan nollaksi).
Torgo kirjoitti:
Kääntäjä ei alustusta oma-aloitteisesti tee, koska silloin se toimisi vastoin standardia.
Mika standardi kieltaa kaantajaa alustamasta?
Torgo kirjoitti:
Näyttäisi tuon perusteella siltä, että edes käyttöjärjestelmä ei oletukseni vastaisesti alusta tuota muistia (ainakaan nollaksi).
Aika jännä. Itse testasin palvelinubuntulla, niin se nollasi.
jcd3nton kirjoitti:
Mika standardi kieltaa kaantajaa alustamasta?
C99 sanoo seuraavaa: "If an object that has automatic storage duration is not initialized explicitly, its value is indeterminate." Ei ehkä suoranaisesti kiellä mutta ei ainakaan takaa alustusta. Automaattiseen alustukseen luottaminen on joka tapauksessa virhe.
Torgo kirjoitti:
Näyttäisi tuon perusteella siltä, että edes käyttöjärjestelmä ei oletukseni vastaisesti alusta tuota muistia (ainakaan nollaksi).
Olisiko ohjelmassa tapahtunut automaattisesti jokin pinoa käyttävä operaatio (esim. DLL-tiedoston alustus) jo ennen main-funktiota?
jcd3nton kirjoitti:
Mika standardi kieltaa kaantajaa alustamasta?
C99. Suora käännös standardista:
Jos objektia, jolla on automaattinen elinaika, ei eksplisiittisesti alusteta, sen arvoa ei määritellä. Jos objektia, jolla on staattinen elinaika, ei eksplisiittisesti alusteta, sen arvo määritellään seuraavasti:
- blah blah ...
Eihän tuo nyt suoraan kiellä alustamasta, mutta antaa aika selvästi ymmärtää ettei kääntäjän tulisi niin tehdä.
Edit. Metabolix ehtikin ensin. :)
Jos ideaa yksinkertaistaa niin ensimmäinen on "nopeampi"
int a; a = 1;
int a = 0; // Ts. jos kääntäjä automaattisesti tekisi näin a = 1;
2 sijoitusta > 1 sijoitus.
No näin yksinkertaisen tapauksen kääntäjä voisi periaatteessa optimoida, ohittamalla ensimmäisen sijoituksen. Mutta entäpä jos tapaus ei ole näin yksinkertainen, vaan jotain:
int a = 0; // <- edelleen kääntäjän automaatio "= 0" AlustusFunktio(@a);
Tai entä jos AlustusFunktio() on dll-kutsu? Kääntäjä ei yksinkertaisesti voisi tietää, ja jättäisi "= 0" sisään.
Tietty yhden muuttujan sijoitus ei tavallisesti näy koodin tehokkuudessa, mutta jos joudut käyttämään sitä funktiota miljoona kertaa sekunnissa niin ero alkaa näkyä.
User137 kirjoitti:
Jos ideaa yksinkertaistaa niin ensimmäinen on "nopeampi"
int a; a = 1;
Käytännössähän tuo siis tarkoittaa jotakin tälläistä:
push ebp mov ebp, esp sub esp, 0x4 ; Reserve space for local variable a a = dword ptr [ebp-0x4] mov a, 0x1
Torgo kirjoitti:
Eihän tuo nyt suoraan kiellä alustamasta, mutta antaa aika selvästi ymmärtää ettei kääntäjän tulisi niin tehdä.
Minusta ei kannata lukea kovin paljoa rivien välistä, ja on yksinkertaisesti väärin sanoa että alustus on vastoin standardia. Standardi sanoo että alkuarvo on määrittelemätön, joten se voi olla mitä vaan — vaikka 0.
jlaire kirjoitti:
on yksinkertaisesti väärin sanoa että alustus on vastoin standardia. Standardi sanoo että alkuarvo on määrittelemätön, joten se voi olla mitä vaan — vaikka 0.
Alustus nimenomaisesti määrittelee muuttujan alkuarvon. Kun kerta kerta standardi sanoo, että alkuarvo on määrittelemätön, niin en näe siinä virhettä väittäessäni kääntäjän toimivan vastoin standardia, jos se omin päin alustaa alustamattomia muuttujia tiettyyn ennalta määrättyyn arvoon - vaikka 0. Vaikka tuossa nyt ei erikseen sanotakaan, että kääntäjä ei saa määrittää alkuarvoa, niin ei sitä mielestäni tarvitakaan, koska muuttujan arvo ei oikein voi olla yhtaikaa sekä määritetty että määrittelemätön. Ylipäätään jos kääntäjä tekee omin päin standardiin kuulumattomia asioita ilman ohjelmoijan tahtoa, toimii se mielestäni väärin.
Tietysti jos termin "määrittelemätön alkuarvo" tulkitsee siten, että kääntäjä saa täysin vapaat kädet lisäillä ohjelmoijan tietämättä täysin mielivaltaista alustuskoodia, niin sitten väittämäni on virheellinen. Tarkalleen ottaen tuo standardin englanninkielinen termi "value is indeterminate" tarkoittaa, että arvo ei ole vielä tiedossa. Jos kääntäjä kuitenkin alustaa muuttujan - vaikka nollaksi - alkuarvo on tällöin väkisinkin tiedossa ja suoraan standardin vastainen.
No, ei siitä sen enempää. Sillä nyt ei ole sen suurempaa merkitystä olenko tuossa oikessa vai väärässä. Kunhan esitin oman tulkintani. :)
Se millä on enemmänkin väliä, on miten kääntäjät oikeasti toimivat ja yksikään testaamani c-kääntäjä ei ole omin päin alustanut lokaaleja muuttujia. GCC:stä en löytänyt edes optiota, millä sen saisi generoimaan alustuskoodia. (tuo -finit-local-zero ei siis toiminut c-koodille) Eli jos haluaa nollalla alustettua lokaalia muistia, on sen alustaminen ohjelmoijan, ei kääntäjän, vastuulla.
Metabolix kirjoitti:
Olisiko ohjelmassa tapahtunut automaattisesti jokin pinoa käyttävä operaatio (esim. DLL-tiedoston alustus) jo ennen main-funktiota?
Ei ainakaan dll-tiedoston alustus, kun Ubuntu on kyseessä. Assemblyssäkään ei näy mitään ylimääräisiä pino-operaatioita. Täytynee perehtyä paremmin, mitä Ubuntussa tapahtuu kun se kutsuu tuota. Mahdollisesti siellä jotenkin tuota pinoa käpistellään.
Torgo kirjoitti:
Tietysti jos termin "määrittelemätön alkuarvo" tulkitsee siten
Sen voisi tulkita myös siten, että standardi ei ota kantaa alkuarvoon ja se on siksi ohjelmoijan kannalta "ei ennalta tiedossa", koska ohjelmoija ei (välttämättä) tiedä millä se edes käännetään. Eli näin tulkittuna kääntäjä olisi standardin mukainen, alusti se muuttujan itse tai ei.
Tiukka tulkintasi, että alustamattoman muuttujan arvo ei saa olla ennalta tiedossa, olisi myös ongelmallinen ja hölmö. Sen mukaan sellaisissa käyttöjärjestelmissä, jotka aina nollaa ohjelmalle varattavan muistin, pitäisi kääntäjän generoida muuttujiin satunnaisluku että ne olisi standardin mukaisia ;D
Torgo kirjoitti:
Tarkalleen ottaen tuo standardin englanninkielinen termi "value is indeterminate" tarkoittaa, että arvo ei ole vielä tiedossa. Jos kääntäjä kuitenkin alustaa muuttujan - vaikka nollaksi - alkuarvo on tällöin väkisinkin tiedossa ja suoraan standardin vastainen.
Tuo termi on itse asiassa määritelty standardin alussa.
http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf
lainaus:
3.17.2.
indeterminate value
either an unspecified value or a trap representation3.17.3
unspecified value
valid value of the relevant type where this International Standard imposes no
requirements on which value is chosen in any instance
Eli arvo on määrittelemätön C-koodarin kannalta, mutta kääntäjä saa valita sen täysin vapaasti ilman mitään rajoitteita. Sen ei tarvitse olla satunnainen.
jlaire kirjoitti:
Tuo termi on itse asiassa määritelty standardin alussa.
Hyvä huomio. Olen missannut/unohtanut, että standardilla oli tuolle oma määritelmänsä. Eli perun puheeni - kääntäjä saa siis halutessaan alustaa muuttujan mihin arvoon tahansa. GCC ja monet muut eivät kuitenkaan näytä tarjoavan edes optiona automaattialustusta.
Grez kirjoitti:
Tiukka tulkintasi, että alustamattoman muuttujan arvo ei saa olla ennalta tiedossa, olisi myös ongelmallinen ja hölmö. Sen mukaan sellaisissa käyttöjärjestelmissä, jotka aina nollaa ohjelmalle varattavan muistin, pitäisi kääntäjän generoida muuttujiin satunnaisluku että ne olisi standardin mukaisia ;D
Ei nyt ihan noin pitkälle tarvitse sentään tulkita. :D Ei kääntäjä kuitenkaan voi tehdä niin pitkälle meneviä johtopäätöksiä, että stack tarjottaisiin sille siististi nollalla alustettuna. Ja vaikka joissain ympäristöissä voisikin, niin ei mikään takaa, että se on nolla vielä silläkin hetkellä, kun kyseinen koodirivi suoritetaan.
Konsultoin tuossa yhtä meidän Linux-kernel experteistä. Hänen mukaansa Linuxin kernel ei sisällä mitään koodia joka nollaisi ohjelmalle tarjottavan stackin. Siellä on kyllä muita keinoja hankaloittaa muistiin jääneiden jälkien lukemista, mutta ainoa varma keino estää jälkien jääminen on pyyhkiä ne itse kyseisessä ohjelmassa yli. Dynaamisen muistin varaus puolestaan käyttää eri mekanismia ja mallocilla varattu muisti näyttikin alustuvan aina nollaksi.
En tiedä olisiko Ubuntun Server jakelua sen verran kustomoitu, että se nollaa pinonkin, mutta ainakaan Ubuntun normijakelussa ja Debianissa ei näyttänyt nollautuvan. Sen sijaan mallocilla glibc:n hallinnoimasta heapista varattu muisti nollaantui. Mutta kuten tulosteesta näkyy, niin sitäkään ei nollata varauksen yhteydessä.
#include <stdlib.h> #include <stdio.h> void testi() { int i; printf("%d\n", i); i = 5; printf("%d\n", i); } void testi2() { int o; printf("%d\n", o); o = 15; printf("%d\n", o); } int main(void) { int * a, i; testi(); testi2(); a = malloc(sizeof(int)*5); printf("a = %d\n", (int)a); for(i=0;i<5;++i) { printf("%d\n", a[i]); a[i] = i+1; } free(a); a = malloc(sizeof(int)*5); printf("a = %d\n", (int)a); for(i=0;i<5;++i) printf("%d\n", a[i]); free(a); return 0; }
Tuloste:
134513408 5 5 15 a = 151658504 0 0 0 0 0 a = 151658504 0 2 3 4 5
Tarinan opetus? Huolehdi aina itse muuttujan alustamisesta ennen käyttöä.
Torgo kirjoitti:
En tiedä olisiko Ubuntun Server jakelua sen verran kustomoitu, että se nollaa pinonkin, mutta ainakaan Ubuntun normijakelussa ja Debianissa ei näyttänyt nollautuvan. Sen sijaan mallocilla glibc:n hallinnoimasta heapista varattu muisti nollaantui. Mutta kuten tulosteesta näkyy, niin sitäkään ei nollata varauksen yhteydessä.
Tässä kironnut kun joku oli stripannut glibc:n kirjastoja ja Valgrind ei arvostanut. Samalla tuli huomattua parikin eri pätsiä kyseiseen kirjastoon, kun ihmettelin kyseisen kirjaston kehitystä.
Tosiaankin löytyy erilaisia nollauspätsejä ja käännösvipuja tuonne. Osa nollaa aina kaiken jaettavan muistin. Lisäksi bongailin keskustelua kernelin modifioimiseen siten, että kaikki saatava muisti on alustettua. Ihan kiva, jos olet vainoharhainen. Suorituskyky voi kyllä ottaa siipeensä, jos muistinkäsittely on liian hidasta.
Toinen kummallinen paikka, missä on tullut leikittyä, on Digi Internationalin NetOS. Siellä luotaessa omalle sovellukselle pinoja ja muistipooleja kannattaa ensin ajaa malloc() ja memset()illä nollata. Muussa tapauksessa noita luotaessa lähtee homma lapasesta. Eikä tuosta ole mitään dokumentaatiota. Toivottavasti uudemmissa on korjattu tuo ominaisuus. Muistia muuten saa sitten joko blokkeina noista pooleista tai sitten tavuina.
Mutta nyt ei olla enää sivuraiteilla vaan ratapihan ulkopuolella.
vuokkosetae kirjoitti:
Toinen kummallinen paikka, missä on tullut leikittyä, on Digi Internationalin NetOS. Siellä luotaessa omalle sovellukselle pinoja ja muistipooleja kannattaa ensin ajaa malloc() ja memset()illä nollata. Muussa tapauksessa noita luotaessa lähtee homma lapasesta.
Kääntäjän hommahan tuota pinoa on käpistellä ja eihän malloc() nollattua muistia normaalisti varaakaan, vaan tuohon löytyy calloc() funktio.
Aah. Selitin itseni hieman huonosti.
Eli tosiaan tuossa NetOS:ssä pitää varata itse muisti tuolle prosessin pinolle jonka käynnistää. Prosessit näyttävät funktioilta ohjelmoijalle.
Nyt kun sanoo, että prosessin_luonti() niin sinne tulee mm. pinon koko ja alkuosoite parametreiksi. Jos se ei olekaan nollattu niin tapahtuu kummallisuuksia. Samoin käy noille muistipooleille.
Edes toimittajan demokoodi ei pysynyt pystyssä, jos pinoa käytettiin ankaramammin ilman tuota nollausta. Lisäsin siihen demoon jotain
foo(int * bar){ if(*bar < 10){ *bar += 1; foo(bar); }else{ return; } }
tuon tapaista. Pinon nollattua toimi aina. Ilman nollausta ei kaikilla kerroilla. Ja kukaan ei valistanut tästä asiasta dokumentaatiossa. Se, miksi näin kävi jäi minulle harvinaisen epäselväksi.
Jos siihen pinoon laitetaan jotain, niin ei sitä silloin kurkita mitä siellä alla on. Pois ottaessa vain mietitään, voiko sieltä ottaa. Jollei niin heitetään voltti. Ja pinon rajat tulee tietää. Nämä muutama aisiaa tekee pinon ja minun mielestäni ja noissa ei ole höykäsen pöläystä sillä väliä mitä siellä pinossa lukee ennen sen käyttöön ottoa. Mutta tuon projektin koodinimi onkin navetan takana oleva haiseva kukkula.
Muutenkin sillä on jotain kummallista muistin käsittelyssä.
const char * tervehdi = "Hello "; const char kohteliaasti[] = "again "; printf("%s%s%s", tervehdi, kohteliaasti, "World!");
Ja tuon Worldin tulostaminen on tautisen hidasta. Kaksi ensimmäistä sanaa menisivät nopeasti, mutta suoraan lainausmerkeistä ottaminen on noin kolme kertaa hitaanpaa. Testit tein siis erillaisilla tulostuksilla ja 100 000 otannalla. Mielestäni kaikki saavat osoitteen ensimmäiseen merkkiin tuossa kutsussa ja lopun edestä en ymmärrä. Ja sillä ei ole vaikutusta, onko lokaali tai globaali muuttuja. Gcc-arm jokin antiikkinen ei oikein siis osaa.
Mutta näillä mennään, kun parempaakaan ei ole. Joten oman perstuntuman mukaan sanoisin, että välttäkää jos tulee vastaan. Uudemmissa versioissa asiat voivat olla paremmin. Minulla on 7.1 käytössä.
Aihe on jo aika vanha, joten et voi enää vastata siihen.