Kirjautuminen

Haku

Tehtävät

Keskustelu: Ohjelmointikysymykset: C++: Muistin varaaminen tietueelle

Sivun loppuun

Burton [06.03.2008 16:00:37]

#

Jos minulla on structi:

struct oma {
      int x, y;
      float a, b;
};

ja haluan tehdä siitä taulukon tähän tapaan:

struct oma taulukko[10];

niin tarvitseeko tätä vapauttaa itse tällä tavalla suoritettuna?

Toinen kysymys:
Kuinka varata malloc, alloc jne. -tyylisesti structille muistia? Jos en tiedäkään taulukon alkioiden määrää enkä halua varata määrää N, joka on oletettavasti alkioiden maksimi.

Gaxx [06.03.2008 16:11:33]

#

Burton kirjoitti:

niin tarvitseeko tätä vapauttaa itse tällä tavalla suoritettuna?

Ei tarvitse. Edit: tai en ole ihan varma, mitä tuo struct sana tuossa tekee. Joku asiasta enemmän tietävä voisi valaista.

Burton kirjoitti:

Kuinka varata malloc, alloc jne. -tyylisesti structille muistia? Jos en tiedäkään taulukon alkioiden määrää enkä halua varata määrää N, joka on oletettavasti alkioiden maksimi.

C:ssä ei käsittääkseni ole mitään valmista systeemiä tuohon, mutta c++:ssa standardikirjastosta löytyy vector:

#include <vector>

using namespace std;

...

vector<int> taulukko; // Tyyppi voi olla mikä tahansa
int luku = 4;
taulukko.push_back(5);   // Lisätään suoraan literaali
taulukko.push_back(luku); // Samaan tapaan myös muuttujasta

for(unsigned int i = 0; i < taulukko.size(); i++) { // .size() palauttaa "taulukon" eli vektorin koon
   cout << taulukko[i] << endl; // Voidaan lukea ja kirjoittaa samaan tapaan, kuin taulukkoa
}

Lisätietoja

Päärynämies [06.03.2008 16:29:28]

#

Tuota taulukkoa ei tarvitse vapauttaa erikseen. Muistia tulee itse vapauttaa vain siinä tapauksessa, että sitä itse varaat esim. malloc -funtiota käyttäen (ei täysin yleispätevä sääntö). Kun määrittelet tuolla tavalla tuon taulukon, niin sille kääntäjä varaa tilaa pinosta.

Kray [06.03.2008 16:39:04]

#

Gaxx: struct tulee sanasta structure, tässä tapauksessa se on suurin piirtein "kokoelma". Siihen voi kasata useita eri muuttujia.

Gaxx [06.03.2008 16:42:58]

#

kray kirjoitti:

Gaxx: struct tulee sanasta structure, tässä tapauksessa se on suurin piirtein "kokoelma". Siihen voi kasata useita eri muuttujia.

Tämä nyt oli selvää, mutta tuo "struct oma taulukko[10];" mietitytti. Päärynämies jo tähän vastasikin.

Metabolix [06.03.2008 17:00:26]

#

Burton kirjoitti:

Kuinka varata malloc, alloc jne. -tyylisesti structille muistia? Jos en tiedäkään taulukon alkioiden määrää enkä halua varata määrää N, joka on oletettavasti alkioiden maksimi.

Muistia varataan tietueille aivan samalla tavalla kuin tavallisillekin muuttujatyypeille. Varattaessa täytyy tietenkin tietää, paljonko muistia varaa, eihän sitä muuten voi varata.

Kray [06.03.2008 17:05:07]

#

Callocilla saa taulukoita varattua ilman että täytyy tietää varattavan muistin määrä, alkioiden määrä riittää.

Hakoulinen [06.03.2008 17:07:30]

#

kray kirjoitti:

Callocilla saa taulukoita varattua ilman että täytyy tietää varattavan muistin määrä, alkioiden määrä riittää.

No eikö yksi alkio ole aina tietyn kokoinen?

Metabolix [06.03.2008 17:10:24]

#

kray kirjoitti:

Callocilla saa taulukoita varattua ilman että täytyy tietää varattavan muistin määrä, alkioiden määrä riittää.

Satutko muistamaan, että callocin toinen parametri on yhden alkion koko? Ei ole vaikea laskea, että muistia varataan yhteensä (koko * määrä) tavua, eli toisin sanoen ei voi väittää, etteikö muistin määrää tarvitsisi tietää. Seuraavassa täysin validi calloc-implementaatio:

void *calloc(size_t kpl, size_t koko)
{
  void *ptr = malloc(kpl * koko);
  if (!ptr) return NULL;
  memset(ptr, 0, kpl * koko);
  return ptr;
}

Kysyjähän halusi muutenkin varata taulukolle tilaa tietämättä edes alkioiden määrää, joten vastaus meni muutenkin aiheen ohi.

Burton [06.03.2008 18:40:45]

#

Kiitos nopeista vastauksista, mutta mitenköhän onnistuisi yhden alkion lisääminen kerrallaan taulukkoon? Jos käytössä on structi, joka esim. sisältää henkilön iän, sukupuolen (1 = mies, 0 = nainen tms.) ja vaikkapa kengännumeron, niin kuinka voisin lisätä silmukassa yhden ihmisen mukaan kerralla?

Päärynämies [06.03.2008 18:49:19]

#

Jos siis haluat, että taulukkosi koko muuttuisi dynaamisesti ( = sitä mukaan kun sinne pukkaa uutta alkiota), niin siihen ei valmista ratkaisua löydy C:n standardikirjastoista. Käytännössä, jos välttämättä haluat taulukkoa käyttää, niin joudut varaamaan aina uuden taulukon ja kopioimaan vanhan sisällön sinne. Suurilla datamäärillä hidasta hommaan.

Kannnattaa tutustua linkitettyihin listoihin. Eivät kovin hankalia ole, mutta niillä saisit ainakin tuon toteutettua.

Kray [06.03.2008 18:55:24]

#

Tai vektoreihin.

Metabolix [06.03.2008 19:01:34]

#

Varatun muistin kokoa voi muuttaa realloc-funktiolla. Samalla muistin osoite saattaa muuttua, joten uusi osoitin täytyy ottaa talteen. Tiedot kuitenkin kopioidaan automaattisesti uuteen sijaintiin. Tässä pieni esimerkki:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

struct ihminen {
	char nimi[12];
	int kenka;
};

struct ihminen pyyda_ihminen(void)
{
	struct ihminen jannu;
	printf("Anna nimi (11 merkkiä): ");
	/* Luetaan nimi (sana) ja hypätään rivin loppuun. */
	scanf("%11s%*[^\n]", jannu.nimi);
	printf("Anna kengän koko: ");
	scanf("%d", &jannu.kenka);
	return jannu;
}

int main(void)
{
	int i, n = 0;
	struct ihminen *vaki = NULL;
	while (1) {
		printf("Haluatko antaa lisää ihmisiä? Annoit jo %d. (Vastaa 1 tai 0.)\n", n);
		/* Jos lukeminen ei onnistunut (esim. syötettiin aakkosia) tai vastaus != 1 */
		if (scanf("%d", &i) != 1 || i != 1) {
			printf("Ei sitten jatketa.\n");
			break;
		}
		/* Muuten lisätään väkeä ja laajennetaan muistia.
		sizeof(*vaki) = sizeof(struct ihminen), tietueen koko */
		n++;
		vaki = realloc(vaki, n * sizeof(*vaki));
		vaki[n - 1] = pyyda_ihminen();
	}
	printf("Tässä ihmisesi:\n");
	for (i = 0; i < n; ++i) {
		printf("%s käyttää %d-kokoisia kenkiä.\n", vaki[i].nimi, vaki[i].kenka);
	}
	/* Vapautetaan lopuksi. */
	free(vaki);
	vaki = NULL;
	return 0;
}

Jatkuva varaileminen on suhteellisen hidasta, joten kannattaa joko käyttää jo ehdotettuja linkitettyjä listoja tai tehdä jonkinlainen arvio siitä, paljonko muistia tarvitaan, ja varata kerralla vähän enemmänkin. Olisi mahdollista esimerkiksi varata aina kerralla tilaa kymmenelle uudelle ihmiselle, jolloin varaus tarvitsisi tehdä vain joka kymmenennen kohdalla.

Päärynämies [07.03.2008 12:17:05]

#

Ja vieläpä voisi huomattaa, että esimerkiksi seuraavanlainen tietue ei kovin paljoa sitä muistia vie.

struct tietue {
    int x, y, z;
    char str[12];
};

Tuosta kun laskee, että int vie yleensä 32-bittisillä järjestelmillä sen neljä tavua ja tuo taulukko 12, niin lopulliseksi tietueen kooksi tulee 24 tavua, joka ei paljoa ole. Tuollaisia pieniä taulukoita voi mielestäni varata surutta kerralla enemmänkin, jos ei jotain erityistä tarvette ole yksitellen varata tai kitsastella muistin kanssa. Nykyisissä kotikoneissakin kun muistia alkaa olla jo kovin runsaasti.

Ja jos en ihan väärin muista, niin ainakin Linux taitaa aina varata ohjelman käyttöön kerralla sen 4 kilotavua (sivutuksesta johtuen). Siitä *alloc sitten aina saa tarvitsemansa määrän ja kun se 4 kt loppuu, niin sitten käyttöjärjestelmä varaa lisää muistia taas ohjelmalle 4 kt kerrallaan. Näin muistan joskus opiskelleeni ja järkevältä tuo tuntuisi.

Jtm [07.03.2008 13:29:53]

#

Päärynämies kirjoitti:

Ja vieläpä voisi huomattaa, että esimerkiksi seuraavanlainen tietue ei kovin paljoa sitä muistia vie.

struct tietue {
    int x, y, z;
    char str[12];
};

Tuosta kun laskee, että int vie yleensä 32-bittisillä järjestelmillä sen neljä tavua ja tuo taulukko 12, niin lopulliseksi tietueen kooksi tulee 24 tavua, joka ei paljoa ole. Tuollaisia pieniä taulukoita voi mielestäni varata surutta kerralla enemmänkin, jos ei jotain erityistä tarvette ole yksitellen varata tai kitsastella muistin kanssa. Nykyisissä kotikoneissakin kun muistia alkaa olla jo kovin runsaasti.

Tietueen todellista kokoa ei voida aina laskea suoraan päättelemällä, koska myös itse struct tietue vie oman osansa muistista. Muistinvaraukseen tietysti vaikuttaa käyttöjärjestelmäkin. Siitä syystä pitää varmin tapa on käyttää sizeof() todellisen koon määrittämiseksi.

Päärynämies [07.03.2008 13:54:57]

#

Jtm kirjoitti:

Tietueen todellista kokoa ei voida aina laskea suoraan päättelemällä, koska myös itse struct tietue vie oman osansa muistista. Muistinvaraukseen tietysti vaikuttaa käyttöjärjestelmäkin. Siitä syystä pitää varmin tapa on käyttää sizeof() todellisen koon määrittämiseksi.

Tuo tosiaan ei täydellinen ja kaikkia tilanteita kattava laskutapa ole. Tuolla kuitenkin pystyy hieman arvioida, jos ei mihinkään yksittäisen tavun tarkkuuteen tarvitse päästä. Tosin ainakin gcc:llä pienillä lisätiedoilla kääntäjän toiminnasta, voi structien kokoja laskea yksinkertaisella tietueen jäsenten kokojen yhteenlaskulla, ainakin osassa tapauksissa. Tuosta käyttämästäni esimerkissä saan sizeof:ta käyttäen tietueen kooksi 24 tavua. Käytössä gcc ja kyseessä 32-bittinen järjestelmä.

Toki, jos softaa kirjoitetaan, niin sizeof() on oikea valinta koon selvittämiseen.

Mitä tarkoitat, että kokoa ei voi laskea suoraan päättelemällä, koska struct tietue vie oman osansa muistista?

Metabolix [07.03.2008 14:35:07]

#

Jtm kirjoitti:

Tietueen todellista kokoa ei voida aina laskea suoraan päättelemällä, koska myös itse struct tietue vie oman osansa muistista.

Tuo on kyllä väärin sanottu.

Ylimääräistä muistia saattaa kulua sen takia, että kääntäjä pyrkii nopeusoptimointisyistä asettelemaan muuttujat sopiviin kohtiin. Esimerkiksi 4:n tavun int-muuttuja pyritään usein asettamaan neljällä jaolliseen muistiosoitteeseen. Tämän takia yhden short- ja yhden int-muuttujan sisältävän tietueen kooksi voi tulla yllättäen 8 tavua eikä 6, kuten muuttujien kokojen perusteella voisi olettaa. Nuo ylimääräiset tavut ovat täysin käyttämättömiä, eivät suinkaan mitenkään itse structin käytössä, kuten Jtm asian ilmaisi.

Burton [07.03.2008 16:21:59]

#

Kiitos vastauksista jälleen kerran. Mieleeni kuitenkin pulpahti kysymys koskien jonomaista rakennetta. Miten onnistuu varatun muistin tyhjennys vain yhden alkion kohdalta? Ihminen nimeltä "Rauno" on vaki[5]-kohdassa. Rauno kuolee ja nyt jää tyhjä paikka kohtaan vaki[5], ellei Raunon anneta kummitella siellä. Miten on?

Päärynämies [07.03.2008 16:28:58]

#

Tosiaan asia on kuten Metabolix ilmaisi. Itse se struct ei mitään tilaa tarvitse. Suoritettava koodi ei millään tapaa tiedä, että ollaanko nyt tekemisissä tietueen vai tavallisen muuttujat kanssa. Se vain käsittelee muistiosoitteita ja niissä olevia arvoja.

Tuosta kääntäjän muuttujien sijoittelusta voisi vielä pienen esimerkkikoodin laittaa.

/* test.c */
#include <stdio.h>

struct tietue{
	char str[5];
	int x;
};

struct tietue_packed{
	char str[5];
	int x;
}__attribute__((packed));

int main(int argc, char **argv)
{
	printf("sizeof(struct tietue): %i\n", sizeof(struct tietue));
	printf("sizeof(struct tietue_packed): %i\n", sizeof(struct tietue_packed));
	return 0;
}

Kääntyy komentamalla: gcc test.c -o test
Komento ./test sitten tuottaa itsellä seuraavanlaisen tulosteen:

sizeof(struct tietue): 12
sizeof(struct tietue_packed): 9

Gcc:n __attribute__((packed)) aiheuttaa sen, että kääntäjä latoo tietueen jäsenet peräkkäin muistiin. Ilman sitä int -muuttuja sijoitetaan seuraavaan neljällä jaolliseen osoitteeseen, jolloin kokonaiskooksi tuleekin 12 tavua, eikä yhteenlaskettu 9. Gcc siis pyrkii optimoimaan tosiaan sen int-muuttujan sinne neljällä jaolliseen osoitteeseen, ellei toisin käsketä. Itse tietueen alku kanssa sijoittuu neljällä jaolliseen osoitteeseen.

Nuo sijoittumiset tietenkin vaihtelevat suorittimen tyypin mukaan. Itsellä 32-bittinen x86-suoritin.

Metabolix [07.03.2008 16:34:30]

#

Jos henkilöiden järjestyksellä ei ole merkitystä, helpoin ratkaisu on siirtää viimeinen henkilö vapautuneeseen paikkaan ja lyhentää taulua yhdellä:

A, B, C, D, E, F, G, _, _, _ ; lkm = 7, koko = 10
B kuolee
A, _, C, D, E, F, G, _, _, _ ; lkm = 7, koko = 10
paikataan aukko
A, G, C, D, E, F, _, _, _, _ ; lkm = 6, koko = 10

Toinen vaihtoehto on siirtää loppuja henkilöitä taaksepäin:

for (i = kuollut; i < koko - 1; ++i) {
  vaki[i] = vaki[i+1];
}

Näiden molempien heikkous on siinä, että taulun henkilöiden numerot muuttuvat, joten jos jossain on säilötty henkilön numeroa, tieto ei enää pidä paikkaansa. Kolmas vaihtoehto onkin lisätä henkilörakenteeseen tieto, onko kyseinen kohta käytössä, ja henkilöä lisättäessä etsiä aina taulusta ensimmäinen käyttämätön kohta.

A, B, C, D, E
B kuolee
A, _, C, D, E
lisätään henkilö F
A, F, C, D, E
lisätään henkilö G, huomataan, ettei taulu riitä, ja laajennetaan taulua
A, F, C, D, E, G

Jtm [07.03.2008 17:21:40]

#

Metabolix kirjoitti:

Jtm kirjoitti:

Tietueen todellista kokoa ei voida aina laskea suoraan päättelemällä, koska myös itse struct tietue vie oman osansa muistista.

Tuo on kyllä väärin sanottu.

Ylimääräistä muistia saattaa kulua sen takia, että kääntäjä pyrkii nopeusoptimointisyistä asettelemaan muuttujat sopiviin kohtiin. Esimerkiksi 4:n tavun int-muuttuja pyritään usein asettamaan neljällä jaolliseen muistiosoitteeseen. Tämän takia yhden short- ja yhden int-muuttujan sisältävän tietueen kooksi voi tulla yllättäen 8 tavua eikä 6, kuten muuttujien kokojen perusteella voisi olettaa. Nuo ylimääräiset tavut ovat täysin käyttämättömiä, eivät suinkaan mitenkään itse structin käytössä, kuten Jtm asian ilmaisi.

Pahoitteluni virheellisestä ilmauksestani. Pointtini oli, että tietueen (siis tietue kaikkine sisältöineen) viemä muistimäärä ei välttämättä ole sama kuin sisältöjen koot summattuna. Tuo mitä sanoit on se, jota hain.

Edit: Tulkitsin ns. hukkuneet bitit muistinkäytöksi, mistä syystä käytin ilmausta "tietueen käytössä".


Sivun alkuun

Vastaus

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

Tietoa sivustosta