ndeksnyj uzel, pasport), chto i ishodnyj fajl. |to prosto al'ternativnoe imya fajla, uchityvaemoe v pole di_nlink v I-node. ____________________ |- BSD - semejstvo UNIX-ov iz University of California, Berkley. Berkley Software Distribution. A. Bogatyrev, 1992-95 - 145 - Si v UNIX symbolic link - sozdaetsya vyzovom symlink. |to otdel'nyj samostoyatel'nyj fajl, s sobstvennym I-node. Pravda, kody dostupa k etomu fajlu ne igrayut nikakoj roli; znachimy tol'ko kody dostupa ukazuemogo fajla. 4.7. Napishite programmu, kotoraya nahodit v fajle simvol @ i vydaet fajl s etogo mesta dvazhdy. Ukazanie: dlya zapominaniya pozicii v fajle ispol'zujte vyzov lseek() - pozicionirovanie ukazatelya chteniya/zapisi: long offset, lseek(); ... /* Uznat' tekushchuyu poziciyu chteniya/zapisi: * sdvig na 0 ot tekushchej pozicii. lseek vernet novuyu * poziciyu ukazatelya (v bajtah ot nachala fajla). */ offset = lseek(fd, 0L, 1); /* ftell(fp) */ A dlya vozvrata v etu tochku: lseek(fd, offset, 0); /* fseek(fp, offset, 0) */ Po povodu lseek nado pomnit' takie veshchi: - lseek(fd, offset, whence) ustanavlivaet ukazatel' chteniya/zapisi na rasstoyanie offset bajt pri whence: 0 ot nachala fajla RWptr = offset; 1 ot tekushchej pozicii RWptr += offset; 2 ot konca fajla RWptr = dlina_fajla + offset; |ti znacheniya whence mozhno oboznachat' imenami: #include <stdio.h> 0 eto SEEK_SET 1 eto SEEK_CUR 2 eto SEEK_END - Ustanovka ukazatelya chteniya/zapisi - eto virtual'naya operaciya, t.e. real'nogo podvoda magnitnyh golovok i voobshche obrashcheniya k disku ona ne vyzyvaet. Real'noe dvizhenie golovok k nuzhnomu mestu diska proizojdet tol'ko pri operaciyah chteniya/zapisi read()/write(). Poetomu lseek() - deshevaya operaciya. - lseek() vozvrashchaet novuyu poziciyu ukazatelya chteniya/zapisi RWptr otnositel'no nachala fajla (long smeshchenie v bajtah). Pomnite, chto esli vy ispol'zuete eto zna- chenie, to vy dolzhny predvaritel'no opisat' lseek kak funkciyu, vozvrashchayushchuyu dlin- noe celoe: long lseek(); - Argument offset dolzhen imet' tip long (ne oshibites'!). - Esli postavit' ukazatel' za konec fajla (eto dopustimo!), to operaciya zapisi write() snachala zapolnit bajtom '\0' vse prostranstvo ot konca fajla do pozicii ukazatelya; operaciya read() pri popytke chteniya iz-za konca fajla vernet "prochi- tano 0 bajt". Popytka postavit' ukazatel' pered nachalom fajla vyzovet oshibku. - Vyzov lseek() neprimenim k pipe i FIFO-fajlam, poetomu popytka sdvinut'sya na 0 bajt vydast oshibku: /* eto standartnaya funkciya */ int isapipe(int fd){ extern errno; return (lseek(fd, 0L, SEEK_CUR) < 0 && errno == ESPIPE); } vydaet "istinu", esli fd - deskriptor "truby"(pipe). A. Bogatyrev, 1992-95 - 146 - Si v UNIX 4.8. Kakov budet effekt sleduyushchej programmy? int fd = creat("aFile", 0644); /* creat sozdaet fajl otkrytyj na zapis', s dostupom rw-r--r-- */ write(fd, "begin", 5 ); lseek(fd, 1024L * 1000, 0); write(fd, "end", 3 ); close(fd); Napomnim, chto pri zapisi v fajl, ego dlina avtomaticheski uvelichivaetsya, kogda my zapisyvaem informaciyu za prezhnim koncom fajla. |to vyzyvaet otvedenie mesta na diske dlya hraneniya novyh dannyh (porciyami, nazyvaemymi blokami - razmerom ot 1/2 do 8 Kb v raznyh versiyah). Takim obrazom, razmer fajla ogranichen tol'ko nalichiem svobodnyh blokov na diske. V nashem primere poluchitsya fajl dlinoj 1024003 bajta. Budet li on zanimat' na diske 1001 blok (po 1 Kb)? V sisteme UNIX - net! Vot koe-chto pro mehaniku vydeleniya blokov: - Bloki raspolagayutsya na diske ne obyazatel'no podryad - u kazhdogo fajla est' speci- al'nym obrazom organizovannaya tablica adresov ego blokov. - Poslednij blok fajla mozhet byt' zanyat ne celikom (esli dlina fajla ne kratna razmeru bloka), tem ne menee chislo blokov u fajla vsegda celoe (krome semejstva BSD, gde blok mozhet delit'sya na fragmenty, prinadlezhashchie raznym fajlam). Opera- cionnaya sistema v kazhdyj moment vremeni znaet dlinu fajla s tochnost'yu do odnogo bajta i ne pozvolyaet nam "zaglyadyvat'" v ostatok bloka, poka pri svoem "roste" fajl ne zajmet eti bajty. - Blok na diske fizicheski vydelyaetsya lish' posle operacii zapisi v etot blok. V nashem primere: pri sozdanii fajla ego razmer 0, i emu vydeleno 0 blokov. Pri pervoj zapisi fajlu budet vydelen odin blok (logicheskij blok nomer 0 dlya fajla) i v ego nachalo zapishetsya "begin". Dlina fajla stanet ravna 5 (ostatok bloka - 1019 bajt - ne ispol'zuetsya i fajlu logicheski ne prinadlezhit!). Zatem lseek postavit ukazatel' zapisi daleko za konec fajla i write zapishet v 1000-yj blok slovo "end". 1000-yj blok budet vydelen na diske. V etot moment u fajla "vozniknut" i vse promezhutochnye bloki 1..999. Odnako oni budut tol'ko "chislit'sya za fajlom", no na diske otvedeny ne budut (v tablice blokov fajla eto oboznachaetsya adresom 0)! Pri chtenii iz nih budut chitat'sya bajty '\0'. |to tak nazyvaemaya "dyrka" v fajle. Fajl imeet razmer 1024003 bajta, no na diske zanimaet vsego 2 bloka (na samom dele chut' bol'she, t.k. chast' tablicy blokov fajla tozhe nahoditsya v special'nyh blokah fajla). Blok iz "dyrki" stanet real'nym, esli v nego chto-nibud' zapisat'. Bud'te gotovy k tomu, chto "razmer fajla" (kotoryj, kstati, mozhno uznat' sistem- nym vyzovom stat) - eto v UNIX ne to zhe samoe, chto "mesto, zanimaemoe fajlom na diske". 4.9. Najdite oshibki: FILE *fp; ... fp = open( "fajl", "r" ); /* otkryt' */ close(fp); /* zakryt' */ Otvet: ispol'zuetsya sistemnyj vyzov open() vmesto funkcii fopen(); a takzhe close vmesto fclose, a ih formaty (i rezul'tat) razlichayutsya! Sleduet chetko razlichat' dve sushchestvuyushchie v Si modeli obmena s fajlami: cherez sistemnye vyzovy: open, creat, close, read, write, lseek; i cherez biblioteku buferizovannogo obmena stdio: fopen, fclose, fread, fwrite, fseek, getchar, putchar, printf, i.t.d. V pervoj iz nih obra- shchenie k fajlu proishodit po celomu fd - deskriptoru fajla, a vo vtorom - po ukazatelyu FILE *fp - ukazatelyu na fajl. |to parallel'nye mehanizmy (po svoim vozmozhnostyam), hotya vtoroj yavlyaetsya prosto nadstrojkoj nad pervym. Tem ne menee, luchshe ih ne smeshi- vat'. A. Bogatyrev, 1992-95 - 147 - Si v UNIX 4.10. Dostup k disku (chtenie/zapis') gorazdo (na neskol'ko poryadkov) medlennee, chem dostup k dannym v operativnoj pamyati. Krome togo, esli my chitaem ili zapisyvaem fajl pri pomoshchi sistemnyh vyzovov malen'kimi porciyami (po 1-10 simvolov) char c; while( read(0, &c, 1)) ... ; /* 0 - standartnyj vvod */ to my proigryvaem eshche v odnom: kazhdyj sistemnyj vyzov - eto obrashchenie k yadru operaci- onnoj sistemy. Pri kazhdom takom obrashchenii proishodit dovol'no bol'shaya dopolnitel'naya rabota (smotri glavu "Vzaimodejstvie s UNIX"). Pri etom nakladnye rashody na takoe posimvol'noe chtenie fajla mogut znachitel'no prevysit' poleznuyu rabotu. Eshche odnoj problemoj yavlyaetsya to, chto sistemnye vyzovy rabotayut s fajlom kak s nestrukturirovannym massivom bajt; togda kak cheloveku chasto udobnee predstavlyat', chto fajl podelen na stroki, soderzhashchie chitabel'nyj tekst, sostoyashchij lish' iz obychnyh pechatnyh simvolov (tekstovyj fajl). Dlya resheniya etih dvuh problem byla postroena special'naya biblioteka funkcij, nazvannaya stdio - "standartnaya biblioteka vvoda/vyvoda" (standard input/output library). Ona yavlyaetsya chast'yu biblioteki /lib/libc.a i predstavlyaet soboj nadstrojku nad sistemnymi vyzovami (t.k. v konce koncov vse ee funkcii vremya ot vremeni obrashcha- yutsya k sisteme, no gorazdo rezhe, chem esli ispol'zovat' sisvyzovy neposredstvenno). Nebezyzvestnaya direktiva #include <stdio.h> vklyuchaet v nashu programmu fajl s ob®yavle- niem formatov dannyh i konstant, ispol'zuemyh etoj bibliotekoj. Biblioteku stdio mozhno nazvat' bibliotekoj buferizovannogo obmena, a takzhe bib- liotekoj raboty s tekstovymi fajlami (t.e. imeyushchimi razdelenie na stroki), poskol'ku dlya optimizacii obmenov s diskom (dlya umen'sheniya chisla obrashchenij k nemu i tem samym sokrashcheniya chisla sistemnyh vyzovov) eta biblioteka vvodit buferizaciyu, a takzhe pre- dostavlyaet neskol'ko funkcij dlya raboty so strochno-organizovannymi fajlami. Svyaz' s fajlom v etoj modeli obmena osushchestvlyaetsya uzhe ne pri pomoshchi celogo chisla - deskriptora fajla (file descriptor), a pri pomoshchi adresa "svyaznoj" struktury FILE. Ukazatel' na takuyu strukturu uslovno nazyvayut ukazatelem na fajl (file pointer)|-. Struktura FILE soderzhit v sebe: - deskriptor fd fajla dlya obrashcheniya k sistemnym vyzovam; - ukazatel' na bufer, razmeshchennyj v pamyati programmy; - ukazatel' na tekushchee mesto v bufere, otkuda nado vydat' ili kuda zapisat' oche- rednoj simvol; etot ukazatel' prodvigaetsya pri kazhdom vyzove getc ili putc; - schetchik ostavshihsya v bufere simvolov (pri chtenii) ili svobodnogo mesta (pri zapisi); - rezhimy otkrytiya fajla (chtenie/zapis'/chtenie+zapis') i tekushchee sostoyanie fajla. Odno iz sostoyanij - pri chtenii fajla byl dostignut ego konec|=; - sposob buferizacii; Predusmotreno neskol'ko standartnyh struktur FILE, ukazateli na kotorye nazyvayutsya stdin, stdout i stderr i svyazany s deskriptorami 0, 1, 2 sootvetstvenno (standartnyj vvod, standartnyj vyvod, standartnyj vyvod oshibok). Napomnim, chto eti kanaly otkryty neyavno (avtomaticheski) i, esli ne perenapravleny, svyazany s vvodom s klaviatury i vyvodom na terminal. Bufer v operativnoj pamyati nashej programmy sozdaetsya (funkciej malloc) pri otk- rytii fajla pri pomoshchi funkcii fopen(). Posle otkrytiya fajla vse operacii obmena s fajlom proishodyat ne po 1 bajtu, a bol'shimi porciyami razmerom s bufer - obychno po 512 bajt (konstanta BUFSIZ). Pri chtenii simvola int c; FILE *fp = ... ; c = getc(fp); ____________________ |- |to ne ta "svyazuyushchaya" struktura file v yadre, pro kotoruyu shla rech' vyshe, a ESHCHE odna - v pamyati samoj programmy. |= Proverit' eto sostoyanie pozvolyaet makros feof(fp); on istinen, esli konec byl dostignut, lozhen - esli eshche net. A. Bogatyrev, 1992-95 - 148 - Si v UNIX v bufer schityvaetsya read-om iz fajla porciya informacii, i getc vydaet ee pervyj bajt. Pri posleduyushchih vyzovah getc vydayutsya sleduyushchie bajty iz bufera, a obrashchenij k disku uzhe ne proishodit! Lish' kogda bufer budet ischerpan - proizojdet ocherednoe chtenie s diska. Takim obrazom, informaciya chitaetsya iz fajla s operezheniem, zaranee napolnyaya bufer; a po trebovaniyu vydaetsya uzhe iz bufera. Esli my chitaem 1024 bajta iz fajla pri pomoshchi getc(), to my 1024 raza vyzyvaem etu funkciyu, no vsego 2 raza sistemnyj vyzov read - dlya chteniya dvuh porcij informacii iz fajla, kazhdaya - po 512 bajt. Pri zapisi char c; FILE *fp = ... ; putc(c, fp); vyvodimye simvoly nakaplivayutsya v bufere. Tol'ko kogda v nem okazhetsya bol'shaya porciya informacii, ona za odno obrashchenie write zapisyvaetsya na disk. Bufer zapisi "vytalki- vaetsya" v fajl v takih sluchayah: - bufer zapolnen (soderzhit BUFSIZ simvolov). - pri zakrytii fajla (fclose ili exit |-|-). - pri vyzove funkcii fflush (sm. nizhe). - v special'nom rezhime - posle pomeshcheniya v bufer simvola '\n' (sm. nizhe). - v nekotoryh versiyah - pered lyuboj operaciej chteniya iz kanala stdin (naprimer, pri vyzove gets), pri uslovii, chto stdout buferizovan postrochno (rezhim _IOLBF, smotri nizhe), chto po-umolchaniyu tak i est'. Privedem uproshchennuyu shemu, poyasnyayushchuyu vzaimootnosheniya osnovnyh funkcij i makrosov iz stdio (kto kogo vyzyvaet). Dalee s oznachaet stroku, c - simvol, fp - ukazatel' na strukturu FILE |=|=. Funkcii, rabotayushchie so strokami, v cikle vyzyvayut posimvol'nye operacii. Obratite vnimanie, chto v konce koncov vse funkcii obrashchayutsya k sistemnym vyzovam read i write, osushchestvlyayushchim vvod/vyvod nizkogo urovnya. Sistemnye vyzovy dalee oboznacheny zhirno, makrosy - kursivom. Otkryt' fajl, sozdat' bufer: #include <stdio.h> FILE *fp = fopen(char *name, char *rwmode); | vyzyvaet V int fd = open (char *name, int irwmode); Esli otkryvaem na zapis' i fajl ne sushchestvuet (fd < 0), to sozdat' fajl vyzovom: fd = creat(char *name, int accessmode); fd budet otkryt dlya zapisi v fajl. Po umolchaniyu fopen() ispol'zuet dlya creat kody dostupa accessmode ravnye 0666 (rw- rw-rw-). ____________________ |-|- Pri vypolnenii vyzova zaversheniya programmy exit(); vse otkrytye fajly avtomati- cheski zakryvayutsya. |=|= Oboznacheniya fd dlya deskriptorov i fp dlya ukazatelej na fajl prizhilis' i ih sle- duet priderzhivat'sya. Esli peremennaya dolzhna imet' bolee mnemonichnoe imya - sleduet pisat' tak: fp_output, fd_input (a ne prosto fin, fout). A. Bogatyrev, 1992-95 - 149 - Si v UNIX Sootvetstvie argumentov fopen i open: rwmode irwmode ------------------------- "r" O_RDONLY "w" O_WRONLY|O_CREAT |O_TRUNC "r+" O_RDWR "w+" O_RDWR |O_CREAT |O_TRUNC "a" O_WRONLY|O_CREAT |O_APPEND "a+" O_RDWR |O_CREAT |O_APPEND Dlya r, r+ fajl uzhe dolzhen sushchestvovat', v ostal'nyh sluchayah fajl sozdaetsya, esli ego ne bylo. Esli fopen() ne smog otkryt' (ili sozdat') fajl, on vozvrashchaet znachenie NULL: if((fp = fopen(name, rwmode)) == NULL){ ...neudacha... } Itak, shema: printf(fmt,...)--->--,----fprintf(fp,fmt,...)->--* fp=stdout | fputs(s,fp)--------->--| puts(s)----------->-------putchar(c)-----,---->--| fp=stdout | fwrite(array,size,count,fp)->--| | YAdro OS putc(c,fp) ------------------* | |fajlovaya---<--write(fd,s,len)------------<----BUFER |sistema---->---read(fd,s,len)-* _flsbuf(c,fp) | | ! | |sistemnye bufera ! | | | ! V ungetc(c,fp) |drajver ustr-va ! | | |(disk, terminal) ! | _filbuf(fp) | | | ! *--------->-----BUFER<-* |ustrojstvo ! | ------------------* c=getc(fp) | rdcount=fread(array,size,count,fp)--<--| gets(s)-------<---------c=getchar()------,----<--| fp=stdout | | fgets(sbuf,buflen,fp)-<--| scanf(fmt,.../*uk-li*/)--<-,--fscanf(fp,fmt,...)-* fp=stdin Zakryt' fajl, osvobodit' pamyat' vydelennuyu pod bufer: fclose(fp) ---> close(fd); I chut' v storone - funkciya pozicionirovaniya: fseek(fp,long_off,whence) ---> lseek(fd,long_off,whence); Funkcii _flsbuf i _filbuf - vnutrennie dlya stdio, oni kak raz sbrasyvayut bufer v fajl libo chitayut novyj bufer iz fajla. Po ukazatelyu fp mozhno uznat' deskriptor fajla: int fd = fileno(fp); |to makroopredelenie prosto vydaet pole iz struktury FILE. Obratno, esli my otkryli A. Bogatyrev, 1992-95 - 150 - Si v UNIX fajl open-om, my mozhem vvesti buferizaciyu etogo kanala: int fd = open(name, O_RDONLY); /* ili creat() */ ... FILE *fp = fdopen(fd, "r"); (zdes' nado vnov' ukazat' KAK my otkryvaem fajl, chto dolzhno sootvetstvovat' rezhimu otkrytiya open-om). Teper' mozhno rabotat' s fajlom cherez fp, a ne fd. V prilozhenii imeetsya tekst, soderzhashchij uproshchennuyu realizaciyu glavnyh funkcij iz biblioteki stdio. 4.11. Funkciya ungetc(c,fp) "vozvrashchaet" prochitannyj bajt v fajl. Na samom dele bajt vozvrashchaetsya v bufer, poetomu eta operaciya neprimenima k nebuferizovannym kanalam. Vozvrat sootvetstvuet sdvigu ukazatelya chteniya iz bufera (kotoryj uvelichivaetsya pri getc()) na 1 poziciyu nazad. Vernut' mozhno tol'ko odin simvol podryad (t.e. pered sle- duyushchim ungetc-om dolzhen byt' hot' odin getc), poskol'ku v protivnom sluchae mozhno sdvinut' ukazatel' za nachalo bufera i, zapisyvaya tuda simvol c, razrushit' pamyat' programmy. while((c = getchar()) != '+' ); /* Prochli '+' */ ungetc(c ,stdin); /* A mozhno zamenit' etot simvol na drugoj! */ c = getchar(); /* snova prochtet '+' */ 4.12. Ochen' chasto delayut oshibku v funkcii fputc, putaya poryadok ee argumentov. Tak nichego ne stoit napisat': FILE *fp = ......; fputc( fp, '\n' ); Zapomnite navsegda! int fputc( int c, FILE *fp ); ukazatel' fajla idet vtorym! Sushchestvuet takzhe makroopredelenie putc( c, fp ); Ono vedet sebya kak i funkciya fputc, no ne mozhet byt' peredano v kachestve argumenta v funkciyu: #include <stdio.h> putNtimes( fp, c, n, f ) FILE *fp; int c; int n; int (*f)(); { while( n > 0 ){ (*f)( c, fp ); n--; }} vozmozhen vyzov putNtimes( fp, 'a', 3, fputc ); no nedopustimo putNtimes( fp, 'a', 3, putc ); Tem ne menee vsegda, gde vozmozhno, sleduet pol'zovat'sya makrosom - on rabotaet byst- ree. Analogichno, est' funkciya fgetc(fp) i makros getc(fp). Otmetim eshche, chto putchar i getchar eto tozhe vsego lish' makrosy #define putchar(c) putc((c), stdout) #define getchar() getc(stdin) A. Bogatyrev, 1992-95 - 151 - Si v UNIX 4.13. Izvestnaya vam funkciya printf takzhe yavlyaetsya chast'yu biblioteki stdio. Ona vho- dit v semejstvo funkcij: FILE *fp; char bf[256]; fprintf(fp, fmt, ... ); printf( fmt, ... ); sprintf(bf, fmt, ... ); Pervaya iz funkcij formatiruet svoi argumenty v sootvetstvii s formatom, zadannym strokoj fmt (ona soderzhit formaty v vide %-ov) i zapisyvaet stroku-rezul'tat posim- vol'no (vyzyvaya putc) v fajl fp. Vtoraya - eto vsego-navsego fprintf s kanalom fp ravnym stdout. Tretyaya vydaet sformatirovannuyu stroku ne v fajl, a zapisyvaet ee v massiv bf. V konce stroki sprintf dobavlyaet nulevoj bajt '\0' - priznak konca. Dlya chteniya dannyh po formatu ispol'zuyutsya funkcii semejstva fscanf(fp, fmt, /* adresa arg-tov */...); scanf( fmt, ... ); sscanf(bf, fmt, ... ); Funkcii fprintf i fscanf yavlyayutsya naibolee moshchnym sredstvom raboty s tekstovymi faj- lami (soderzhashchimi izobrazhenie dannyh v vide pechatnyh simvolov). 4.14. Tekstovye fajly (imeyushchie strochnuyu organizaciyu) hranyatsya na diske kak linejnye massivy bajt. Dlya razdeleniya strok v nih ispol'zuetsya simvol '\n'. Tak, naprimer, tekst str1 strk2 knc hranitsya kak massiv s t r 1 \n s t r k 2 \n k n c dlina=14 bajt ! ukazatel' chteniya/zapisi (read/write pointer RWptr) (rasstoyanie v bajtah ot nachala fajla) Pri vyvode na ekran displeya simvol \n preobrazuetsya drajverom terminalov v posledova- tel'nost' \r\n, kotoraya vozvrashchaet kursor v nachalo stroki ('\r') i opuskaet kursor na stroku vniz ('\n'), to est' kursor perehodit v nachalo sleduyushchej stroki. V MS DOS stroki v fajle na diske razdelyayutsya dvumya simvolami \r\n i pri vyvode na ekran nikakih preobrazovanij ne delaetsya|-. Zato bibliotechnye funkcii yazyka Si preobrazuyut etu posledovatel'nost' pri chtenii iz fajla v \n, a pri zapisi v fajl prevrashchayut \n v \r\n, poskol'ku v Si schitaetsya, chto stroki razdelyayutsya tol'ko \n. Dlya raboty s fajlom bez takih preobrazovanij, ego nado otkryvat' kak "binarnyj": FILE *fp = fopen( imya, "rb" ); /* b - binary */ int fd = open ( imya, O_RDONLY | O_BINARY ); ____________________ |- Upravlyayushchie simvoly imeyut sleduyushchie znacheniya: '\n' - '\012' (10) line feed '\r' - '\015' (13) carriage return '\t' - '\011' (9) tab '\b' - '\010' (8) backspace '\f' - '\014' (12) form feed '\a' - '\007' (7) audio bell (alert) '\0' - 0. null byte A. Bogatyrev, 1992-95 - 152 - Si v UNIX Vse netekstovye fajly v MS DOS nado otkryvat' imenno tak, inache mogut proizojti raz- nye nepriyatnosti. Naprimer, esli my programmoj kopiruem netekstovyj fajl v tekstovom rezhime, to odinochnyj simvol \n budet schitan v programmu kak \n, no zapisan v novyj fajl kak para \r\n. Poetomu novyj fajl budet otlichat'sya ot originala (chto dlya fajlov s dannymi i programm sovershenno nedopustimo!). Zadanie: napishite programmu podscheta strok i simvolov v fajle. Ukazanie: nado podschitat' chislo simvolov '\n' v fajle i uchest', chto poslednyaya stroka fajla mozhet ne imet' etogo simvola na konce. Poetomu esli poslednij simvol fajla (tot, kotoryj vy prochitaete samym poslednim) ne est' '\n', to dobav'te k schetchiku strok 1. 4.15. Napishite programmu podscheta kolichestva vhozhdenij kazhdogo iz simvolov alfavita v fajl i pechatayushchuyu rezul'tat v vide tablicy v 4 kolonki. (Ukazanie: zavedite massiv iz 256 schetchikov. Dlya bol'shih fajlov schetchiki dolzhny byt' tipa long). 4.16. Pochemu vvodimyj pri pomoshchi funkcij getchar() i getc(fp) simvol dolzhen opisy- vat'sya tipom int a ne char? Otvet: funkciya getchar() soobshchaet o konce fajla tem, chto vozvrashchaet znachenie EOF (end of file), ravnoe celomu chislu (-1). |to NE simvol kodirovki ASCII, poskol'ku getchar() mozhet prochest' iz fajla lyuboj simvol kodirovki (kodirovka soderzhit simvoly s kodami 0...255), a special'nyj priznak ne dolzhen sovpadat' ni s odnim iz hranimyh v fajle simvolov. Poetomu dlya ego hraneniya trebuetsya bol'she odnogo bajta (nuzhen hotya by eshche 1 bit). Proverka na konec fajla v programme obychno vyglyadit tak: ... while((ch = getchar()) != EOF ){ putchar(ch); ... } - Pust' ch imeet tip unsigned char. Togda ch vsegda lezhit v intervale 0...255 i NIKOGDA ne budet ravno (-1). Dazhe esli getchar() vernet takoe znachenie, ono budet privedeno k tipu unsigned char obrubaniem i stanet ravnym 255. Pri srav- nenii s celym (-1) ono rasshiritsya v int dobavleniem nulej sleva i stanet ravno 255. Takim obrazom, nasha programma nikogda ne zavershitsya, t.k. vmesto priznaka konca fajla ona budet chitat' simvol s kodom 255 (255 != -1). - Pust' ch imeet tip signed char. Togda pered sravneniem s celym chislom EOF bajt ch budet priveden k tipu signed int pri pomoshchi rasshireniya znakovogo bita (7- ogo). Esli getchar vernet znachenie (-1), to ono budet snachala v prisvaivanii znacheniya bajtu ch obrubleno do tipa char: 255; no v sravnenii s EOF znachenie 255 budet privedeno k tipu int i poluchitsya (-1). Takim obrazom, istinnyj konec fajla budet obnaruzhen. No teper', esli iz fajla budet prochitan nastoyashchij simvol s kodom 255, on budet priveden v sravnenii k celomu znacheniyu (-1) i budet takzhe vosprinyat kak konec fajla. Takim obrazom, esli v nashem fajle okazhetsya simvol s kodom 255, to programma vosprimet ego kak fal'shivyj konec fajla i ostavit ves' ostatok fajla neobrabotannym (a v netekstovyh fajlah takie simvoly - ne red- kost'). - Pust' ch imeet tip int ili unsigned int (bol'she 8 bit). Togda vse korrektno. Otmetim, chto v UNIX priznak konca fajla v samom fajle fizicheski NE HRANITSYA. Sistema v lyuboj moment vremeni znaet dlinu fajla s tochnost'yu do odnogo bajta; priznak EOF vyrabatyvaetsya standartnymi funkciyami togda, kogda obnaruzhivaetsya, chto ukazatel' chte- niya dostig konca fajla (to est' poziciya chteniya stala ravnoj dline fajla - poslednij bajt uzhe prochitan). V MS DOS zhe v tekstovyh fajlah priznak konca (EOF) hranitsya yavno i oboznachaetsya simvolom CTRL/Z. Poetomu, esli programmnym putem zapisat' kuda-nibud' v seredinu fajla simvol CTRL/Z, to nekotorye programmy perestanut "videt'" ostatok fajla posle etogo simvola! Nakonec otmetim, chto raznye funkcii pri dostizhenii konca fajla vydayut raznye znacheniya: scanf, fscanf, fgetc, getc, getchar vydayut EOF, read - vydaet 0, a gets, fgets - NULL. A. Bogatyrev, 1992-95 - 153 - Si v UNIX 4.17. Napishite programmu, kotoraya zaprashivaet vashe imya i privetstvuet vas. Dlya vvoda imeni ispol'zujte standartnye bibliotechnye funkcii gets(s); fgets(s,slen,fp); V chem raznica? Otvet: funkciya gets() chitaet stroku (zavershayushchuyusya '\n') iz kanala fp==stdin. Ona ne kontroliruet dlinu bufera, v kotoruyu schityvaetsya stroka, poetomu esli stroka okazhetsya slishkom dlinnoj - vasha programma povredit svoyu pamyat' (i avarijno zaver- shitsya). Edinstvennyj vozmozhnyj sovet - delajte bufer dostatochno bol'shim (ochen' tuman- noe ponyatie!), chtoby vmestit' maksimal'no vozmozhnuyu (dlinnuyu) stroku. Funkciya fgets() kontroliruet dlinu stroki: esli stroka na vhode okazhetsya dlin- nee, chem slen simvolov, to ostatok stroki ne budet prochitan v bufer s, a budet ostav- len "na potom". Sleduyushchij vyzov fgets prochitaet etot sohranennyj ostatok. Krome togo fgets, v otlichie ot gets, ne obrubaet simvol '\n' na konce stroki, chto dostavlyaet nam dopolnitel'nye hlopoty po ego unichtozheniyu, poskol'ku v Si "normal'nye" stroki zaver- shayutsya prosto '\0', a ne "\n\0". char buffer[512]; FILE *fp = ... ; int len; ... while(fgets(buffer, sizeof buffer, fp)){ if((len = strlen(buffer)) && buffer[len-1] == '\n') /* @ */ buffer[--len] = '\0'; printf("%s\n", buffer); } Zdes' len - dlina stroki. Esli by my vybrosili operator, pomechennyj '@', to printf pechatal by tekst cherez stroku, poskol'ku vydaval by kod '\n' dvazhdy - iz stroki buffer i iz formata "%s\n". Esli v fajle bol'she net strok (fajl dochitan do konca), to funkcii gets i fgets vozvrashchayut znachenie NULL. Obratite vnimanie, chto NULL, a ne EOF. Poka fajl ne dochi- tan, eti funkcii vozvrashchayut svoj pervyj argument - adres bufera, v kotoryj byla zapi- sana ocherednaya stroka fajla. Fragment dlya obrubaniya simvola perevoda stroki mozhet vyglyadet' eshche tak: #include <stdio.h> #include <string.h> char buffer[512]; FILE *fp = ... ; ... while(fgets(buffer, sizeof buffer, fp) != NULL){ char *sptr; if(sptr = strchr(buffer, '\n')) *sptr = '\0'; printf("%s\n", buffer); } 4.18. V chem otlichie puts(s); i fputs(s,fp); ? Otvet: puts vydaet stroku s v kanal stdout. Pri etom puts vydaet snachala stroku s, a zatem - dopolnitel'no - simvol perevoda stroki '\n'. Funkciya zhe fputs simvol perevoda stroki ne dobavlyaet. Uproshchenno: fputs(s, fp) char *s; FILE *fp; { while(*s) putc(*s++, fp); } puts(s) char *s; { fputs(s, stdout); putchar('\n'); } A. Bogatyrev, 1992-95 - 154 - Si v UNIX 4.19. Najdite oshibki v programme: #include <stdio.h> main() { int fp; int i; char str[20]; fp = fopen("fajl"); fgets(stdin, str, sizeof str); for( i = 0; i < 40; i++ ); fputs(fp, "Tekst, vyvodimyj v fajl:%s",str ); fclose("fajl"); } Moral': nado byt' vnimatel'nee k formatu vyzova i smyslu bibliotechnyh funkcij. 4.20. Napishite programmu, kotoraya raspechatyvaet samuyu dlinnuyu stroku iz fajla vvoda i ee dlinu. 4.21. Napishite programmu, kotoraya vydaet n-uyu stroku fajla. Nomer stroki i imya fajla zadayutsya kak argumenty main(). 4.22. Napishite programmu slice -sKakoj +skol'ko fajl kotoraya vydaet skol'ko strok fajla fajl, nachinaya so stroki nomer sKakoj (numeraciya strok s edinicy). #include <stdio.h> #include <ctype.h> long line, count, nline, ncount; /* nuli */ char buf[512]; void main(int argc, char **argv){ char c; FILE *fp; argc--; argv++; /* Razbor klyuchej */ while((c = **argv) == '-' || c == '+'){ long atol(), val; char *s = &(*argv)[1]; if( isdigit(*s)){ val = atol(s); if(c == '-') nline = val; else ncount = val; } else fprintf(stderr,"Neizvestnyj klyuch %s\n", s-1); argc--; ++argv; } if( !*argv ) fp = stdin; else if((fp = fopen(*argv, "r")) == NULL){ fprintf(stderr, "Ne mogu chitat' %s\n", *argv); exit(1); } for(line=1, count=0; fgets(buf, sizeof buf, fp); line++){ if(line >= nline){ fputs(buf, stdout); count++; } if(ncount && count == ncount) break; } A. Bogatyrev, 1992-95 - 155 - Si v UNIX fclose(fp); /* eto ne obyazatel'no pisat' yavno */ } /* End_Of_File */ 4.23. Sostav'te programmu, kotoraya raspechatyvaet poslednie n strok fajla vvoda. 4.24. Napishite programmu, kotoraya delit vhodnoj fajl na fajly po n strok v kazhdom. 4.25. Napishite programmu, kotoraya chitaet 2 fajla i pechataet ih vperemezhku: odna stroka iz pervogo fajla, drugaya - iz vtorogo. Pridumajte, kak postupit', esli fajly soderzhat raznoe chislo strok. 4.26. Napishite programmu sravneniya dvuh fajlov, kotoraya budet pechatat' pervuyu iz razlichayushchihsya strok i poziciyu simvola, v kotorom oni razlichayutsya. 4.27. Napishite programmu dlya interaktivnoj raboty s fajlom. Snachala u vas zaprashi- vaetsya imya fajla, a zatem vam vydaetsya menyu: 1. Zapisat' tekst v fajl. 2. Dopisat' tekst k koncu fajla. 3. Prosmotret' fajl. 4. Udalit' fajl. 5. Zakonchit' rabotu. Tekst vvoditsya v fajl postrochno s klaviatury. Konec vvoda - EOF (t.e. CTRL/D), libo odinochnyj simvol '.' v nachale stroki. Vydavajte chislo vvedennyh strok. Prosmotr fajla dolzhen vestis' postranichno: posle vydachi ocherednoj porcii strok vydavajte podskazku --more-- _ (kursor ostaetsya v toj zhe stroke i oboznachen podcherkom) i ozhidajte nazhatiya klavishi. Otvet 'q' zavershaet prosmotr. Esli fajl, kotoryj vy hotite prosmotret', ne sushchest- vuet - vydavajte soobshchenie ob oshibke. Posle vypolneniya dejstviya programma vnov' zaprashivaet imya fajla. Esli vy otve- tite vvodom pustoj stroki (srazu nazhmete <ENTER>, to dolzhno ispol'zovat'sya imya fajla, vvedennoe na predydushchem shage. Imya fajla, predlagaemoe po umolchaniyu, prinyato pisat' v zaprose v [] skobkah. Vvedite imya fajla [oldfile.txt]: _ Kogda vy nauchites' rabotat' s ekranom displeya (sm. glavu "|krannye biblioteki"), perepishite menyu i vydachu soobshchenij s ispol'zovaniem pozicionirovaniya kursora v zadan- noe mesto ekrana i s vydeleniem teksta inversiej. Dlya vybora imeni fajla predlozhite menyu: otsortirovannyj spisok imen vseh fajlov tekushchego kataloga (po povodu polucheniya spiska fajlov sm. glavu pro vzaimodejstvie s UNIX). Prosto dlya raspechatki tekushchego kataloga na ekrane mozhno takzhe ispol'zovat' vyzov system("ls -x"); a dlya schityvaniya kataloga v programmu|- FILE *fp = popen("ls *.c", "r"); ... fgets(...,fp); ... // v cikle, poka ne EOF pclose(fp); (v etom primere chitayutsya tol'ko imena .c fajlov). 4.28. Napishite programmu udaleniya n-oj stroki iz fajla; vstavki stroki posle m-oj. K sozhaleniyu, eto vozmozhno tol'ko putem perepisyvaniya vsego fajla v drugoe mesto (bez nenuzhnoj stroki) i posleduyushchego ego pereimenovaniya. A. Bogatyrev, 1992-95 - 156 - Si v UNIX 4.29. Sostav'te programmu perekodirovki teksta, nabitogo v kodirovke KOI-8, v al'- ternativnuyu kodirovku i naoborot. Dlya etogo sleduet sostavit' tablicu perekodirovki iz 256 simvolov: c_new=TABLE[c_old]; Dlya resheniya obratnoj zadachi ispol'zujte stan- dartnuyu funkciyu strchr(). Programma chitaet odin fajl i sozdaet novyj. 4.30. Napishite programmu, delyashchuyu bol'shoj fajl na kuski zadannogo razmera (ne v strokah, a v kilobajtah). |ta programma mozhet primenyat'sya dlya zapisi slishkom bol'- shogo fajla na diskety (fajl rezhetsya na chasti i zapisyvaetsya na neskol'ko disket). #include <fcntl.h> #include <stdio.h> #define min(a,b) (((a) < (b)) ? (a) : (b)) #define KB 1024 /* kilobajt */ #define PORTION (20L* KB) /* < 32768 */ long ONEFILESIZE = (300L* KB); extern char *strrchr(char *, char); extern long atol (char *); extern errno; /* sistemnyj kod oshibki */ char buf[PORTION]; /* bufer dlya kopirovaniya */ void main (int ac, char *av[]) { char name[128], *s, *prog = av[0]; int cnt=0, done=0, fdin, fdout; /* M_UNIX avtomaticheski opredelyaetsya * kompilyatorom v UNIX */ #ifndef M_UNIX /* t.e. MS DOS */ extern int _fmode; _fmode = O_BINARY; /* Zadaet rezhim otkrytiya i sozdaniya VSEH fajlov */ #endif if(av[1] && *av[1] == '-'){ /* razmer odnogo kuska */ ONEFILESIZE = atol(av[1]+1) * KB; av++; ac--; } if (ac < 2){ fprintf(stderr, "Usage: %s [-size] file\n", prog); exit(1); } if ((fdin = open (av[1], O_RDONLY)) < 0) { fprintf (stderr, "Cannot read %s\n", av[1]); exit (2); } if ((s = strrchr (av[1], '.'))!= NULL) *s = '\0'; do { unsigned long sent; sprintf (name, "%s.%d", av[1], ++cnt); if ((fdout = creat (name, 0644)) < 0) { fprintf (stderr, "Cannot create %s\n", name); exit (3); } sent = 0L; /* skol'ko bajt pereslano */ for(;;){ unsigned isRead, /* prochitano read-om */ need = min(ONEFILESIZE - sent, PORTION); if( need == 0 ) break; sent += (isRead = read (fdin, buf, need)); errno = 0; if (write (fdout, buf, isRead) != isRead && errno){ perror("write"); exit(4); } else if (isRead < need){ done++; break; } } if(close (fdout) < 0){ perror("Malo mesta na diske"); exit(5); } printf("%s\t%lu bajt\n", name, sent); } while( !done ); exit(0); } A. Bogatyrev, 1992-95 - 157 - Si v UNIX 4.31. Napishite obratnuyu programmu, kotoraya skleivaet neskol'ko fajlov v odin. |to analog komandy cat s edinstvennym otlichiem: rezul'tat vydaetsya ne v standartnyj vyvod, a v fajl, ukazannyj v stroke argumentov poslednim. Dlya vydachi v standartnyj vyvod sleduet ukazat' imya "-". #include <fcntl.h> #include <stdio.h> void main (int ac, char **av){ int i, err = 0; FILE *fpin, *fpout; if (ac < 3) { fprintf(stderr,"Usage: %s from... to\n", av[0]); exit(1); } fpout = strcmp(av[ac-1], "-") ? /* otlichno ot "-" */ fopen (av[ac-1], "wb") : stdout; for (i = 1; i < ac-1; i++) { register int c; fprintf (stderr, "%s\n", av[i]); if ((fpin = fopen (av[i], "rb")) == NULL) { fprintf (stderr, "Cannot read %s\n", av[i]); err++; continue; } while ((c = getc (fpin)) != EOF) putc (c, fpout); fclose (fpin); } fclose (fpout); exit (err); } Obe eti programmy mogut bez izmenenij translirovat'sya i v MS DOS i v UNIX. UNIX prosto ignoriruet bukvu b v otkrytii fajla "rb", "wb". Pri rabote s read my mogli by otkryvat' fajl kak #ifdef M_UNIX # define O_BINARY 0 #endif int fdin = open( av[1], O_RDONLY | O_BINARY); 4.32. Kakim obrazom standartnyj vvod pereklyuchit' na vvod iz zadannogo fajla, a stan- dartnyj vyvod - v fajl? Kak proverit', sushchestvuet li fajl; pust li on? Kak nado otkryvat' fajl dlya dopisyvaniya informacii v konec sushchestvuyushchego fajla? Kak nado otk- ryvat' fajl, chtoby poperemenno zapisyvat' i chitat' tot zhe fajl? Ukazanie: sm. fopen, freopen, dup2, stat. Otvet pro perenapravleniya vvoda: sposob 1 (bibliotechnye funkcii) #include <stdio.h> ... freopen( "imya_fajla", "r", stdin ); sposob 2 (sistemnye vyzovy) #include <fcntl.h> int fd; ... fd = open( "imya_fajla", O_RDONLY ); dup2 ( fd, 0 ); /* 0 - standartnyj vvod */ close( fd ); /* fd bol'she ne nuzhen - zakryt' ego, chtob ne zanimal mesto v tablice */ A. Bogatyrev, 1992-95 - 158 - Si v UNIX sposob 3 (sistemnye vyzovy) #include <fcntl.h> int fd; ... fd = open( "imya_fajla", O_RDONLY )