Kirjautuminen

Haku

Tehtävät

Keskustelu: Ohjelmointikysymykset: [C++] Binääritiedoston lukeminen lopusta alkuun

Hara Kiri [25.06.2009 15:03:37]

#

Binääritiedostossa on XY tietueita. Tietueiden tietojen lukeminen alusta loppuun toimii, mutta lopusta alkuun luettaessa seekg() ei tee mitään vaan ohjelma jää kiertämään while rakenteen sisään samassa kohdassa tiedostoa. Googlella ei löytynyt apua. Kääntäjänä g++ Ubuntussa ja g++ (mingw) Windows XP:ssä. (Ohjelma on esimerkki 14-11 Päivi Hietasen C++ ja olio-ohjelmointi kirjassa.)

struct XY
{
   int x;
   int y;
};

//-------------------------------------//

//muuttujat allaolevaan esimerkkiin
fstream tdsto;
tdsto.seekg(0);
streampos alku = tdsto.tellg();
//tiedostossa on n-määrä tietueita:
XY piste = {0,0};


// tdsto.tellg() ei koskaa saavuta kohtaa "alku"
// ja tdsto.read(..) lukee aina saman kohdan

   cout<<"selataan taaksepäin"<<endl;
   tdsto.clear();
   tdsto.seekg(-(1 * sizeof piste), ios_base::end);
   while(!tdsto.tellg() == alku)
   {
      tdsto.read((char *) &piste, sizeof piste);
      cout<<piste.x<<", "<<piste.y<<endl;
      tdsto.seekg(-(2 * sizeof piste), ios_base::cur);
   }
   tdsto.read((char *) &piste, sizeof piste);
   cout<<piste.x<<", "<<piste.y<<endl;
   cout<<sizeof piste<<endl;
   break;

Mitähän vikaa koodissa on?

Metabolix [25.06.2009 16:01:08]

#

Yksi vika on ainakin nimi "tdsto", koska se on nimenä epäselvä ja olisi luultavasti yhtä nopeaa kirjoittaa "tiedosto". >_>

Yksi toiminnallinen vika on, että yrität ilmeisesti hakea sijainnin tiedostossa, ennen kuin tiedosto on auki. Muuttuja "alku" pitäisi alustaa vasta, kun tiedosto on avattu.

#include <iostream>
#include <fstream>

int main(int argc, char **argv) {
	std::ifstream f;

	f.seekg(0);
	std::cout << f.tellg() << std::endl;
	// -1, virhe

	f.open(argv[0]);
	f.seekg(0);
	std::cout << f.tellg() << std::endl;
	// 0, todellinen alkukohta
}

Lisäksi ehto (!tdsto.tellg() == alku) on selvästi väärin, koska negaatio lasketaan ennen yhtäsuuruusvertailua, jolloin vasen puoli on aina 0 tai 1. Et myöskään voi muuttaa sizeof-operaattorin paluuarvoa negatiiviseksi, koska se on etumerkitön luku; sizeof-laskujen pitäisi olla ilman sulkuja, jolloin laskun ensimmäinen luku (-2) olisi itsessään etumerkillinen.

Järkevämpi tapa olisi laskea ensin tietueiden määrä ja lukea sitten for-silmukassa oikea määrä tietueita välittämättä tiedoston kohdasta eli tellg()-funktiosta. Nykyinen koodisi nimittäin jää ikuiseen silmukkaan, jos tiedoston koko jaettuna tietueen koolla ei mene tasan.

eq [26.06.2009 04:26:17]

#

Hara Kiri kirjoitti:

while(!tdsto.tellg() == alku)

Mitähän vikaa koodissa on?

Ottamatta kantaa itse toteutukseen, tämä ehtolauseke pisti silmääni – ehto, joka vertailee streampos-tyyppistä objektia ja streampos-objektin negaatiota (looginen ei, totuusarvo). Semanttisesti epäilyttävältä kuulostavasta vertailusta on vaikea sanoa, onko sen toiminnalle esim. C++-standardin taetta – kääntäjästähän tuo riippuu, eli siitä, miten streampos on toteutettu – enkä ole myöskään järin vakuuttunut siitä, että streampos-objektin vertaaminen totuusarvoon (boolean) kuulostaisi juurikaan järkevämmältä, oikein missään tapauksessa. Ylläolevahan ei vastaa esim. ehtoa (tdsto.tellg() != alku) tai ehtoa (!(tdsto.tellg() == alku)).

Jos hiustenhalkominen ei miellytä, voi toki suoraan ajatella, että streampos on kokonaislukutyyppi, jolle on määritelty looginen ei -operaatio ja vertailtaessa [totuusarvoon] totuusarvo muunnetaan implisiittisesti kokonaislukutyypiksi. On myös mahdollisesti luonnollista olettaa, että virran sisäistä sijaintia ilmoittava kokonaislukumuuttuja "alkaa" nollasta, joten ehto käy toteen kunnes myös tdsto.tellg() on nollassa, eli alussa. En tähän hätään osaa sanoa, vahvistaako esim. C++-standardi näitä oletuksia.

Mutta on tuo ehdon rakenne silti hassu, pakko sanoa – jos nyt en missannut mitään oleellista. Viestin ainoa pointti, jos sellaista siis on, löytynee ensimmäisen kappaleen lopusta.

Grez [26.06.2009 10:22:57]

#

Itse kiinnitin huomiota samaan kuin eq, mutta toisaalta kysyjä sanoo, että koodi lukee koko ajan samaa kohtaa. Nähdäkseni tuosta ehdosta riippumatta koodin pitäisi aloittaa lopusta, lukea kunnes pääsee alkuun ja vasta sitten jäädä ikuiseen looppiin tai hajota muuten.

Hara Kiri [30.06.2009 16:54:23]

#

Testasin kääntää tuota kirjan esimerkkiä Visual-C++:lla (2008 Express Edition). Ei onnistunut ja kääntäjä luultavasti valitti miinuksista seekg():n edessä. Ehkä tuo koodipätkö toimii vain borlandin kääntäjällä, jolla kirjan esimerkit on luultavasti käännetty. Mutta kiitoksia avusta, täytyy varmaan Metabolixin viestin pohjalta lähteä kehittämään omaa systeemiä tiedoston lukuun.

Grez [30.06.2009 17:20:30]

#

Ei se valittanut miinuksista seekg():ssä vaan siitä, että yrität tehdä miinuksen (1 * sizeof piste):stä, vaikka sizeof on unsigned.

Eli jos laitat

tdsto.seekg(-(int)(1 * sizeof piste), ios_base::end);

niin ei ainakaan valita mitään ja toimiikin luultavasti paremmin.

Vastaus

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

Tietoa sivustosta