Heipä hei,
C++ aloittelijana yritän päästä mahdollisimman hyvin jyvälle aina siitä, miten kukin uusi asia käytännössä toimii. Tänä aamuna ihmetystä herättää Yhdistelmien (union) jäsenten arvojen muuttumiskäytös arvoja muutettaessa.
Eli, kun yhdistelmään(union) syötetään uusi arvo, se muuttaa edellisen syötetyn arvon kokoa, koska data tallentuu päällekkäin.
Kysymys kuuluu, miksi edellisen arvon koko ei esimerkiksi tässä oppaan esimerkissä pienene, vaan suurenee sen väistäessä uutta syötettyä arvoa? Kun x.koko 'väistää' uutta x.liukua, sen koko muuttuu 1 => 1.00015. Tai jos x.kokon arvo on vaikkapa 2, se kasvaa => 2.00029. Oman logiikan mukaan arvon tulisi pienentyä. Sillä jos kerran yhdistelmässä ei ole tilaa säilyttää molempia arvoja, niin luulisi tuon väistyvän arvon tosiaan pienenevän, eikä suurenevan ja vievän enemmän tilaa. Miksi näin?
#include <iostream> union kokoliuku { short koko; float liuku; }; int main(){ kokoliuku x; std::cout << "sizeof(x): " << sizeof(x) << std::endl; std::cout << "sizeof(x.koko): " << sizeof(x.koko) << std::endl; std::cout << "sizeof(x.liuku): " << sizeof(x.liuku) << std::endl; x.liuku = 1; std::cout << "sij. x.liuku = " << x.liuku << ";" << std::endl; x.koko = 1234; std::cout << "sij. x.koko = " << x.koko << ";" << std::endl; std::cout << "nyt x.koko == " << x.koko << "!" << std::endl; std::cout << "nyt x.liuku == " << x.liuku << "!" << std::endl; x.liuku = 3.14159; std::cout << "sij. x.liuku = " << x.liuku << ";" << std::endl; std::cout << "nyt x.koko == " << x.koko << "!" << std::endl; }
Tiedän, ei ole järkevää käyttää tuota muuttuvaa arvoa mihinkään, vaan mielummin sijoittaa muuttujat vaikka listaan, tietueeseen tai taulukkoon, mutta...
jos joku osaa kertoa, niin mielelläni kuulisin logiikan tässä asiassa.
Olisikohan tästä apua, kun en jaksa/osaa selittää selkeästi... ?
https://www.youtube.com/watch?v=L8OYx1I8qNg
Olet ilmeisesti ymmärtänyt unionin (logiikan) pahan kerran pieleen.
Se on rakenne, jossa saman muistipaikan sisältöä voi käyttää usealla eri tietytyypillä.
Eri suuruiset luvut ei myöskään vie eri kokoista tilaa, vaan tilantarve on täysin riippuvainen käyttämästäsi muuttujatyypistä. Lyhyt liukuluku on 32bittiä ja lyhyt kokonaisluku 16 bittiä.
Sinulla on siis esimerkissäsi kaksi lukua, jotka ovat samassa muistipaikassa. Jos sijoitat jotain tuohon x.liuku muuttujaan, niin se ylikirjoittaa koko muistipaikan sisällön. Jos taas kirjoitat jotain tuohon x.koko -muuttujaan, niin se ylikirjoittaa vain 2 ensimmäistä tavua ja jälkimmäiset 2 tavua säilyvät muuttumattomina. (Ja koska käyttämäsi prosessori on vähiten merkitsevä tavu ensin, niin alempana olevassa esimerkissä tavut ovat käänteisessä järjestyksessä)
Se, miksi erilaisten lukujen sijoittaminen tuottaa saamiasi tuloksia selviää jos ymmärrät tuon peranin laittaman kuvauksen liukuluvuista.
Tässä vielä vähän näkymää siihen mitä siellä tapahtuu:
sij. x.liuku = 1; 32-bittiä muistissa ovat nyt: 00111111100000000000000000000000 [ kok.luku 0 ] [ liukuluku 1 ] Liulukuna tämä tulkitaan 0 etumerkki (plus) 01111111 eksponentti (2^0) 00000000000000000000000 arvo (1) sij. x.koko = 1234; 32-bittiä muistissa ovat nyt: 00111111100000000000010011010010 [kok.luku 1234 ] [ liukuluku 1.00015 ] sij. x.liuku = 3.14159; 32-bittiä muistissa ovat nyt: 01000000010010010000111111010000 [kok.luku 4048 ] [ liukuluku 3.14159 ]
Jos haluat testailla lisää liukulukujen ihmeellistä maailmaan niin tuossa on hauska väline siihen:
Täytyy kyllä antaa propsit grez:lle kädestä pitäen esitykselle.
Itse kun en jaksanut sitä tehdä.
No nyt! Taisin jopa saada käsityksen. Kiitos Grez hyvästä vastauksesta, ja loistavan hienosta ja selkeästä esityksestä! Kiitos myös linkeistä teille molemmille - täytyypä tutkailla vielä paremmin näitä liuku-/binääri-/kymmenlukujen toimintoja jatkossa.
+ kiitos muutenkin hyvästä sivustosta ylläpitäjälle & jäsenille. Ei varmaan olisi englanniksi edes jaksanut lähteä opettelemaan C++ tai muutakaan kieltä kovin pitkälle. Nythän tämä on suorastaan miellyttävää.
Kirjoitan Grezin ansiokkaan vastauksen vielä toisella tavalla kymmenjärjestelmän kautta.
xd00mz kirjoitti:
Luulisi tuon väistyvän arvon tosiaan pienenevän, eikä suurenevan ja vievän enemmän tilaa.
Tilankäyttö ei muutu, vaan liukuluvut 2 ja 2.00029 vievät aivan saman verran muistia: 2 on itse asiassa 2.00000... jollain määrätyllä tarkkuudella. Todellinen tapahtuma tässä on siis se, että luvun 2.00000 loppuosan päälle tallentuu uutta tietoa ja ”sattumalta” tämä johtaa viimeisten desimaalien vaihtumiseen.
Kun unioniin tallennetaan uusi arvo, vanhoilta arvoilta ei mitenkään ”oteta tilaa”, vaan niiden päälle liimataan se uusi (eri tyyppinen) arvo. Jos vanhasta arvosta jääkin jokin osa peittämättä, voi näyttää siltä, että vanhat arvot vain osittain muuttuvat. Oikeastaan väärän tyyppistä arvoa ei pitäisi edes yrittää käyttää.
Grez esitti jo bitteinä, mitä tapahtuu. Jos tietokone käyttäisi kymmenjärjestelmää, tulos saattaisi näyttää hieman tältä:
sijoitetaan float-lukuun 2. short: 000 float: 2.00000 sijoitetaan short-lukuun 29. short: 029 float: 2.00029 sijoitetaan float-lukuun 1.23456. short: 456 float: 1.23456
Niin se on tietysti hyvä muistaa, että vaikkapa 1 ja 9 vievät saman tilan, vaikka kymmenlukujärjestelmässä 9 on suurempi. Tämä ajatus hipaisi kevyesti kuin vieno tuulen henkäys harmaita aivosolujani kun niitä yritin epätoivoisesti hieroa tämän asian ympärillä, mutta en jostain syystä osannut tarttua siihen. Suokaa anteeksi aloittelijan virheellinen päättelykyky ja vielä kerran kiitän hyvistä vastauksista. Viimeistään tämä Metabolixin vastaus kirkasti täysin oman häpeällisen tyhmyyteni näin yksinkertaisen asian äärellä, mutta nyt olen sen ansiosta kuitenkin viisaampi, joten nöyrästi vielä kerran kiitän avustanne ja että soitte älyllisen kirkkautenne loistavan valoa myös tällaiseen maan matoseen, joka ei ansaitse itseään kutsuttavan koodariksi vielä ainakaan tuhanteen vuoteen.
Kiitos.
Niin, siis union varaa tilan suurimmalle jäsenelleen.
Olettaen seuraava unioni, sekä sizeof(int) == 4:
union foo { struct { int a; int b; } bar; int a; char b; };
Kuinka suuri onkaan foo?
Lisänä vielä se, että kääntäjästä ja alustasta riippuen kääntäjä voi pistää fylliä tasausta varten, mutta ongelmaa voi pohtia ottamatta niitä huomioon.
Aihe on jo aika vanha, joten et voi enää vastata siihen.