C: blokk olvasása fájlból

Fórumok

Van olyan könyvtári modul C-hez, ami megbízható fájlkezelést tartalmaz, azaz - az fread-del ellentétben -, ha azt mondom, hogy 5 bájtot olvasson be a fájlból, és abban van is 5 bájt, akkor azt megbízhatóan be is olvassa?

Hozzászólások

Ha van 5 byte a file-ban, akkor az fread() azt miert nem olvassa be nalad? Mi az a "megbizhato", vagyis mit ertesz azalatt? 

Az fread általában beolvassa, de néha nem. Nem tudom, miért.

Az fread visszaad egy értéket, ami ha 1, akkor sikeresen beolvasta, de néha nem 1. Ekkor újrapozícionálok, és újraolvasom, amíg 1 nem lesz. Így most működik rendben. Enélkül az újraolvasási ciklus nélkül azonban néha nem. Nem tudom, mi alapján ront, és azt sem tudom, milyen hibát észlel olyankor. A fájl, amit olvasok, adatfájl, és csak ez a program olvassa, azaz még az sem lehet, hogy valaki más blokkolja. Tehát passz.

De olyan műveletet keresek, ahol nem nekem kell kezelnem azt, hogy sikeresen olvas-e. ha lehetséges az olvasás.

Letezo, fix meretu, mas altal epp' nem irt file-oknal nem kellene hogy gond legyen. Ott valami mas van a hatterben (a file egyszercsak elkezd nem letezni, valtozik a merete, mas is irja, stb).

Ugyanakkor ha debuggolnad a problemat, akkor ne `fread(buffer,N,1,fp)` modon, hanem `fread(buffer,1,N,fp)` modon olvasd be, es ugy nezd meg a visszateresi erteket. Ugy megkapod hogy hany byte-ot olvastal be, es nem azt hogy hany blokkot, es ez lehet hogy kozelebb visz a rejtely megoldasahoz. Illetve az is fontos az fread()-nal hogy: 

fread() does not distinguish between end-of-file and error, and callers must use feof(3) and ferror(3) to determine which occurred.

Szoval ha barmi gyanus, akkor ne brute-force modon fseek()-elj, hanem nezd meg hogy mi az oka. Egyeb hasznos fuggveny lehet meg az ftell() is. 

Tovabbi lehetosegek: `strace`-szel futtasd a programodat. Az nem mondja meg hogy magasszinten (stdio) mi a gond, de alacsonyszinen (unistd: open(), read(), stb) gyorsan valaszt kapsz arra hogyha valami rendszerszintu(bb) dolog miatt van fennakadas, vagy... vagy barmi egyeb. Szelsoseges esetben meg persze ilyesmik is lehetnek hogy disk i/o error vagy barmi megdurvabb. De ez a fent emlitett `strace`-szel, vagy siman `dmesg`-gel is kideritheto.

A programom egy bináris fájlkonverziós program. A forrásfájlok adottak, léteznek és senki nem változtat rajtuk. A konvertálás közben csak én olvasom. A konvertálást egymás után lefuttatva többször is ugyanarra a fájlra, pár helyes eredmény után néha elakadt. Mint kiderült, azért, mert hibásan olvasta ki az adatot a forrásfájlból. Ez volt az első fread a fájlból. Előtte csak fgetc-vel olvastam bájtokat. Itt leállítva a konverziót lehetett tesztelni, hogy 4-szer 5-ször lefut helyesen, az fread kiolvasta a megfelelő értéket a forrásfájlból, majd a következő futtatásra nem 1-gyel tért vissza, és rögtön nem volt megfelelő a beolvasott érték.

Most felbuzdulva azon, hogy ennek nem kellene így lennie, kivettem a ciklusból az fread-et, és visszatettem egymagában, de akárhányszor is futtatom, most mindig 1-gyel tér vissza az fread, tehát az ok közben elmúlt ... ami persze így még szomorúbb, és már esélyem sincs megtalálni ...

 A konvertálást egymás után lefuttatva többször is ugyanarra a fájlra, pár helyes eredmény után néha elakadt.

No igen, akkor itt valami egyeb csunyasag lesz, tenyleg, ami nem az fread() jellegu hivasokhoz kapcsolodik. Ld. pl amit lentebb irtam az int16_t vs. int temaban. Vagy barmi ehhez hasonlo. Altalaban azok csinalnak ilyet. NevemTeve kollega altal fentebb ajanlott `valgrind` erre is jo: megmutatja hogyha inicializalatlan memoriateruletet olvasol. Most mar a forditok is egyre jobbak ebben, de tombokben, stringekben azok se tudjak mindig jol detektalni ezt a hibat (foleg ha egy konyvtari fv-t hivsz be, forditasi idoben ismeretlen inputtal). Es ezutobbi nyalanksagra is pont jo ez az `int (==int32_t) xyz; fread(&xyz,1,2,fp);` pelda.

Akkor lenne igazad, ha a ciklus nem oldotta volna meg. Ne feledd el, hogy miután betettem egy ciklusba az fread-et, ami addig ismételte, amíg 1 nem lesz, akkor minden helyreállt, és onnantól stabilan működött. Tesztelhetően hibázott néha, ha kivettem a ciklusból, és stabilan ment, ha visszatettem a ciklusba. Ez nagyon meglepett. (Sajnos akkor még nem ismertem a fent említett error lekérdező műveleteket.) Emiatt gondoltam arra, hogy ez az fread-nek egy megszokott működése, és mindig ciklus kell köré, hogy megbízható legyen.

Egyébként az int változóba beolvasás előtt 0-át írok, tehát az nem lehet a hiba oka. Továbbá ha az int-be hibásan is került volna be az adat, attól az fread még 1-et adott volna eredményül.

Amúgy a sok meglátás ellenére továbbra is tartom, hogy itt az fread-ben volt probléma. Az lehet, hogy az fread egy valós fájlrendszer szintű ideiglenes hibába akadt, ami az ismétlésre megszűnt, de ezt már sajnos nem fogom tudni kideríteni, ha csak újra elő nem fordul. Ilyen irányú ötlet pedig egy sem hangzott még el, hogy valójában mi is okozhat ilyen gondot. Magam valamilyen helytelen cache működésre tudok egyedül gondolni.

A konkrét olvasási műveletben, ahol előjött a hiba, egy int változóba olvasok be egy 2 bájtos integert a fájlból.

Bájtonként nem hatékony az olvasás, de legalább az fgetc megbízható. A forrásfájl pedig pici, és a formátuma bájt szervezésű, így bájtonként olvasva egyeszerű feldolgozni a rekordjait.

Kezeld alacsony szinten és első lépésben tedd nonblocking módba a handle-t.

Nonblocking mód: a read akkor is visszatér az olvasásból, ha nincs 5 byte sem a file-ban. (A visszatérési érték tartalmazza, hogy hány byte-ot olvasott. Ha -1, akkor hiba történt.)

Alacsony szint: a fopen, fread és társai magas szinten kezelik a file-t, saját bufferrel, így ha nincs elég adat a file-ban. akkor a fread megáll és vár, amíg elegendő adat nem lesz.

Hat, megmondom oszinten, nonblocking modot nem ajanlanek senkinek, az szerintem csak nagyon extrem kornyezetben hasznalando. Raadasul ugye az stdio nativan nem is tamogatja, ki kell menned unistd-fuggvenyekhez ha hasznalod (fileno(), fcntl(), stb-n keresztul) - es nem veletlenul. Es szerintem itten a kolleganak nem is ezzel van a gondja, mert az fread() az visszater neki (tehat nem blokkol), csak nem annyival mint amennyit vart. 

Szerkesztve: 2022. 05. 01., v – 16:52

Perror - mert ez így találgatás.

Kód?

Futási környezet?

> Sol omnibus lucet.

Perror-t sem használtam, most meg már nem tudom, de a következő esetben, ha megcsípem, használni fogom.

Nem vagyok C-ben jártas, igazából egy meglévő C kódot alakítok át a konkrét bemeneti adatokhoz, emiatt csinálom ezt C-ben.

A futási környezet Debina 11, gcc. A kód elég egyszerű. A fájlt fopen() nyitja "rb" attribútummal. Bájtokat olvasok fgetc()-vel, majd jön az

int address = 0;

fread( &address, 2, 1, src );

És itt elhasalt néha (vagyis nem olvasta be a két bájtot az address-be), bár az ftell() és a feof() szerint is messze a fájlvége előtt volt még.

Sajnos annyira nem voltam rutinos, hogy magam kiderítsem a hiba okát. Az error-ok vizsgálatát nem ismertem.

Ah! Akkor itt van rogton egy gyanus dolog. Ha az int-ed ezen az architekturan az nem 2 byteos (ami elegge valoszinu, merthogy a 8 ill. 16 bites /most mar embedded/ rendszerek kivetelevel mindenhol 4 byte-os), akkor az int-ed aslo ket byte-jat fogja csak felulirni ez az `fread( &address, 2, 1, src );` utasitas. Sot, az eredmeny az MSB-LSB fuggo is, bar valoszinusithetoen LSB-s architekturan dolgozol.

A helyes megoldas ez lenne:

#include <stdint.h>
int16_t address;
fread( &address, 2, 1, src );

Ettol fuggetlenul jo lenne latni a teljes kodot, hatha van benne mas hasonlo turpissag is. 

Felteszem MSB/LSB alatt az endianness-re gondolsz, és little-endian-on dolgozik (PC). Mert nem ugyanazt jelenti.

Ha a performance nem számít, akkor én használnám inkább azt a két fgetc-t, uint8_t-be beolvasva a két chart, és beshiftelni őket az int-ben megfelelő helyre.

Az platformfüggetlen megoldás.

Valahogy így:

int x;
uint8_t b0,b1;

while(!feof(f)) {
  b0=fgetc(f);
  b1=fgetc(f);
  x=(b1<<8)|b0;
}
int32_t x;

x = fgetc(f);
*((uint8_t *) &x + 1) = fgetc(f);
*((int16_t *) &x + 1) = (x & 0x8000) ? -1 : 0;

Rögtön oda írjuk, ahova való. :) Ez akkor fog működni, ha x int32_t. Persze sokkal egyszerűbb, ha x eleve int16_t, és akkor nem kell az utolsó sor. Sőt, akkor nem is szabad.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

O.K. NE keverd a fgetc()-t és az fread()-et! Lehet, hogy az fget()-c dob egy file végét, ha a binárisban filevége karakter van (asszem ASCII26, ^Z)!

Ha jól sejtem az olvasással egyben fel is szeleteled a fájl bizonyos logika szerint. Olvass be egy nagyobb puffert és abban kotorássz. A feldolgozás is gyorsabb lesz (egyébként is beolvasódik egy BUFFSIZE méretű blokk!). Az fread egy nagyon stabil utasítás, eddig SOHA nem tapasztaltam olyasmit, amit leírtál.

 

Én így csinálnám:

--------------------

 

#define MAX_BUFFER 256

unsigned char my_buffer[MAX_BUFFER];

FILE *fp;

unsigned int num_of_byte;

fp=fopen(file_name,"rb");

while(!feof(fp))

{

  num_of_byte= fread(my_buffer,1,MAX_BUFFER,fp);

  feldolgozás(num_of_byte, egyéb_paraméterek....,);

}

fflush(fp);fclos(fp);

// kérem az fflush megköpködésének mellőzését!

> Sol omnibus lucet.

NE keverd a fgetc()-t és az fread()-et! Lehet, hogy az fget()-c dob egy file végét, ha a binárisban filevége karakter van (asszem ASCII26, ^Z)!

Na, halistennek azert ilyet nem csinal az fgetc() :) Teljesen jo az, maximum relative unhatekony. Persze joval hatekonyabb meg egy fgetc() is mint egy byte-onkenti read(), de az mas kerdes...

Mondjuk ez a text vs. binary mode bezavarhat, az valo igaz. De azert nem kisertem az ordogot, es text mode-ot soha nem hasznaltam semmire. Merthogy igy minek. Marmint lehet hogy nem baj hogyha azt irjuk ki tenylegesen is a file-ba amit akarunk es/vagy azt olvassuk be ami tenylegesen ott van ;)

Igen, ilyen nincs altalanos lib-kent, mert ez mar nagyon alkalmazas-fuggo.

Egyreszt azert mert konkret fizikailag letezo, file-szeru objektumoknal (azaz filerendszerbeli fileoknal, meg unix-rendszerek alatt block device-eknel) nem jelenik meg a problema egyatalan. Azaz ezeknel ha nem annyit tudsz kiolvasni amennyit szeretnel csak kevesebbet akkor es csak akkor van vege a file-nak. Abban az esetben mikor az altalad megnyitot file vegere ertel, nyitva maradt, de valaki menet kozben, utolag ir hozza meg egy kicsit es ujra folytatni akarod az olvasast azt persze lehet, amint megkapod azt az infot hogy "jaj, megiscsak nott a file-om menetkozben". De ez mar tulmutat a klasszik libc/stdio keretein belul, lasd: `man 7 inotify`, `man 1 inotifywait`, stb. A hetkoznapi eletben ez a `tail -f ...` parancs implementacional jon szembe: azt sem lehetne mar klasszik stdio-val megoldani.

Abban az esetben viszont amikor varnal adatot, de nem jon epp annyi, de azert megis bevarnad mig megerkezik (azaz pipe-ok, socket-ek, chardev-ek olvasasa) az mar szintugy nem annyira klasszik stdio-hataskor. De valojaban kezelheto lehet stdio-val is, ha kelloen specialis az alkalmazas. Beagyazott rendszereknel, amikor fopencookie()-val csinalunk stdio-absztrakciot pl egy UART fole, akkor ezek hasznosak lehetnek. De ezeken felul pl pipe/socket IPC-knel egy sajat hread() fv-t hasznalok erre a celra egyszerubb esetekben, ami kb igy fest:

ssize_t hread(int fd,void *vbuff,size_t len)
{
 unsigned char  *buff;
 ssize_t        r0,r;

 r0=0;
 buff=(unsigned char *)vbuff;
 while ( 0<len )
  {     r=read(fd,buff,len);
        if ( r<0 )              return(r);
        else if ( r==0 )        return(r0);
        r0+=r;
        buff+=r;
        len-=r;
  };

 return(r0);
}

Ez pont azt csinalja amire gondolunk: azaz csak akkor ter vissza ha tenylegesen megjott a kert adatmennyiseg, vagy valami fennakadas volt menetkozben (pl vege a stream-nek, lezartak a pipe-t, socket-et, valamelyik hibat dobott). Egyszerubb esetekben - pl UNIX domain socket-eknel - ez teljesen jo es biztonsagosnak mondhato, de kicsit instabilabb kornyezetben (chardev-ek vilagaban, vagy magaban a nagybetus interwebek vilagaban) magaban hordozza azt a kockazatot hogy blokkol ha valami miatt megsem jon adat. Szoval ilyesmire gondol a kolto amikor aszondja hogy "nagyon alkalmazas-fuggo".

Szerkesztve: 2022. 05. 01., v – 17:54

Ez így elég sovány. fread elég megbízható, ha senki más nem machinálja a filet és érted is amit csinálsz. Ez alatt azt értem, hogy miképp nyitod meg a filet? Milyen, mekkora tömbbe olvasod? Hányszol olvasod a filet? Olvasod a file státuszát és méretét megnyitás előtt? Olvasod a jogosultságaidat a file megnyitása előtt? syncelsz a file megynitása előtt? Lezárod file második olvasás előtt? Nullázod a filepointert második olvasás előtt? Visszatérési értékek? Visszatérési hiba értékek?
 

"Maradt még 2 kB-om. Teszek bele egy TCP-IP stacket és egy bootlogót. "

Azon gondolkoztam el a kérdéseid olvasva, hogy igaz-e, amit feltételeztem, hogy a programból való kilépés automatán le is zárja a fájlokat C-ben?

Bár a forrásfájlokat "rb" attribútummal nyitom, így elvileg nem okozhatna zavart, ha nem záródnak le. Már csak azért sem, mert a ciklusommal kibővítve végig tudtam olvasni a fájlt. De az is igaz, hogy a jelenség előtt sokszor futtattam le párszáz fájlra a programot, és ezekből néhány a konverzió közben hibát észlelt, így az exit() paranccsal fejezte be a futását, tehát ilyenkor külön nem zártam le egyik fájlt se. Okozhat ez ilyen jellegű problémát?

Amúgy csak fgetc() és fread() olvassa a fájlt, ez utóbbi is maximum 32 bájtot egyszerre.

A konverzió úgy történik, hogy megnyitok egy fájlt írásra, egyet olvasásra, az olvasotton végigmegyek, és közben írom a konvertált adatokat. Aztán a program befejezi és kilép. Fájlok státuszát, méretét, jogosultságait nem figyelem, sync-et nem használok, bár nem is gondolnám, hogy kell, mivel a forrásadataim nem módosulnak.

Mivel nem tudom mlyen operációs rendszert használsz, azt kell mondanom, hogy fogalmam sincsen. Normálisabb operációs rendszereken és filerendszereknél a kilépés után a kernel felszabadítja az erőforásokat és lezárja a nyiotott fileokat. Linuxnál, sem az r, sem ar rb-vel megnyitott file nem marad nyitva az alkalmazás kilépése után.

"Maradt még 2 kB-om. Teszek bele egy TCP-IP stacket és egy bootlogót. "

Ezzel alapból teljesen egyetértek, ugyanis egy programozónak illik számontartania az erőforrásait. Ha hozzá kell később írni a kódhoz, na ilyenekből lesz a bloat, ha ez nincs meg.

Ugyanakkor pl. a busyboxnál ez egy optimalizációs lépés, hogy még az se foglaljon erőforrást :) - persze az egy elég speciális eset.

Szerkesztve: 2022. 05. 02., h – 08:03

A C az egyetlen programozasi nyelv, ahol tenyleg erdemi segitseget tud nyujtani egy rakas user a hupon. Talan kivetelesen meg hatekonyabbat, mint a stackoverflow. :) Tetszik ez a mellekhatasa ennek a boomer kozonsegnek. :)

De C-t nem style alapon valaszt az ember, hanem mert meg mindig az a legmegbizhatobb ASM wrapper, amihez (valosagtol nem elrugaszkodott) karbantarto human eroforrast is talalsz. :)

JavaScriptben egyszer nem allit le valaki egy egyebkent senior vagy lead frontendest es maris brutalis koltseg es human eroforras karbantartani, amit osszehanyt.

TL;DR valami hiba volt a programban, de valahogyan megjavult.

Ha még szabad egy kicsit belekotyognom, akkor annyit tennék hozzá, hogy a hibakeresés első lépése MINDIG a hiba kipreparálása! Ez alatt azt értem, hogy a hibát teljes bizonyossággal elő kell tudni idézni szándékosan. Ez akkor nagyon nehéz, ha időzítések egymáshozképesti elcsúszása okozza azt, de más esetekben sikerülnie kell. Nem tudom kell-e indokolnom, hogy miért.

> Sol omnibus lucet.

Az egész ott bukott el, hogy nem hibaként tekintettem a jelenségre. Azt gondoltam, az én korlátos C ismereteim jelentik a gondot, és egyszerűen az fread nem arra való, amire én használni akarom, van helyette más.

Most már kipreparálnám a hibát, mert engem is érdekel, de nem tudom újra reprodukálni. Pedig próbáltam ugyanúgy nagymennyiségű konverziót lefuttatni, mint akkor, de mégsem jön elő.

Úgy tűnik, ez veszett fejsze nyele ...

Ha tudod, hogy mi okozza, akkor reprodukálhatóvá tudod tenni, bár akkor ki is tudod javítani. Volt már velem olyan, hogy signed char típusú változót jobbra shifteltem, és az volt a kilépési feltételem, hogy ez nulla lesz. Néha belefagyott a ciklusba. Akkor még nem tudtam, hogy signed esetében aritmetikai, unsigned esetén pedig logikai shiftelést használ. Már tudom. :) Viszont signed esetében is tudott működni akkor, ha a szám 0x7f, vagy annál kisebb, de nagyobb vagy egyenlő 0 volt. Szóval a 7-es bitje 0.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Azt ugye tudod, hogy nagyon rossz, amit csináltál? Van egy int változód, csak az Isten tudja, hány byte hosszú. Ebbe beleteszel egy 16 bites integer-t, de csak úgy, byte-onként. Így lesz aztán a -1-ből +65535, mert a felső byte-okat nem töltötted fel az előjelbittel!

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE