; close (0); /* 0 - standartnyj vvod */ fcntl (fd, F_DUPFD, 0 ); /* 0 - standartnyj vvod */ close (fd); |to perenapravlenie vvoda sootvetstvuet konstrukcii $ a.out < imya_fajla napisannoj na komandnom yazyke SiSHell. Dlya perenapravleniya vyvoda zamenite 0 na 1, stdin na stdout, open na creat, "r" na "w". Rassmotrim mehaniku raboty vyzova dup2 |-: new = open("fajl1",...); dup2(new, old); close(new); tablica otkrytyh fajlov processa ...## ## new----##---> fajl1 new---##---> fajl1 ## ## old----##---> fajl2 old---## fajl2 ## ## 0:do vyzova 1:razryv svyazi old s fajl2 dup2() (zakrytie kanala old, esli on byl otkryt) ## ## new----##--*--> fajl1 new ## *----> fajl1 ## | ## | old----##--* old--##--* ## ## 2:ustanovka old na fajl1 3:posle operatora close(new); na etom dup2 zavershen. deskriptor new zakryt. Zdes' fajl1 i fajl2 - svyazuyushchie struktury "otkrytyj fajl" v yadre, o kotoryh rasskazy- valos' vyshe (v nih soderzhatsya ukazateli chteniya/zapisi). Posle vyzova dup2 deskriptory new i old ssylayutsya na obshchuyu takuyu strukturu i poetomu imeyut odin i tot zhe R/W- ukazatel'. |to oznachaet, chto v programme new i old yavlyayutsya sinonimami i mogut ispol'zovat'sya dazhe vperemezhku: dup2(new, old); write(new, "a", 1); write(old, "b", 1); write(new, "c", 1); zapishet v fajl1 stroku "abc". Programma ____________________ |- Funkciya int system(char *komanda); vypolnyaet komandu, zapisannuyu v stroke komanda, vyzyvaya dlya etogo interpretator ko- mand /bin/sh -c "komanda" A. Bogatyrev, 1992-95 - 159 - Si v UNIX int fd; printf( "Hi there\n"); fd = creat( "newout", 0640 ); dup2(fd, 1); close(fd); printf( "Hey, You!\n"); vydast pervoe soobshchenie na terminal, a vtoroe - v fajl newout, poskol'ku printf vydaet dannye v kanal stdout, svyazannyj s deskriptorom 1. 4.33. Napishite programmu, kotoraya budet vydavat' podryad v standartnyj vyvod vse fajly, ch'i imena ukazany v argumentah komandnoj stroki. Ispol'zujte argc dlya organi- zacii cikla. Dobav'te skvoznuyu numeraciyu strok i pechat' nomera stroki. 4.34. Napishite programmu, raspechatyvayushchuyu pervuyu direktivu preprocessora, vstretiv- shuyusya v fajle vvoda. #include <stdio.h> char buf[512], word[] = "#"; main(){ char *s; int len = strlen(word); while((s=fgets(buf, sizeof buf, stdin)) && strncmp(s, word, len)); fputs(s? s: "Ne najdeno.\n", stdout); } 4.35. Napishite programmu, kotoraya pereklyuchaet svoj standartnyj vyvod v novyj fajl imyaFajla kazhdyj raz, kogda vo vhodnom potoke vstrechaetsya stroka vida >>>imyaFajla Otvet: #include <stdio.h> char line[512]; main(){ FILE *fp = fopen("00", "w"); while(gets(line) != NULL) if( !strncmp(line, ">>>", 3)){ if( freopen(line+3, "a", fp) == NULL){ fprintf(stderr, "Can't write to '%s'\n", line+3); fp = fopen("00", "a"); } } else fprintf(fp, "%s\n", line); } 4.36. Biblioteka buferizovannogo obmena stdio soderzhit funkcii, podobnye nekotorym sistemnym vyzovam. Vot funkcii - analogi read i write: Standartnaya funkciya fread iz biblioteki standartnyh funkcij Si prednaznachena dlya chteniya netekstovoj (kak pravilo) informacii iz fajla: ____________________ i vozvrashchaet kod otveta etoj programmy. Funkciya popen (pipe open) takzhe zapuskaet interpretator komand, pri etom perenapraviv ego standartnyj vyvod v trubu (pipe). Drugoj konec etoj truby mozhno chitat' cherez kanal fp, t.e. mozhno prochest' v svoyu prog- rammu vydachu zapushchennoj komandy. ____________________ |- dup2 chitaetsya kak "dup to", v anglijskom zhargone prinyato oboznachat' predlog "to" cifroj 2, poskol'ku slova "to" i "two" proiznosyatsya odinakovo: "tu". "From me 2 You". Takzhe 4 chitaetsya kak "for". A. Bogatyrev, 1992-95 - 160 - Si v UNIX int fread(addr, size, count, fp) register char *addr; unsigned size, count; FILE *fp; { register c; unsigned ndone=0, sz; if(size) for( ; ndone < count ; ndone++){ sz = size; do{ if((c = getc(fp)) >= 0 ) *addr++ = c; else return ndone; }while( --sz ); } return ndone; } Zamet'te, chto count - eto ne kolichestvo BAJT (kak v read), a kolichestvo SHTUK razmerom size bajt. Funkciya vydaet chislo celikom prochitannyh eyu SHTUK. Sushchestvuet analogichnaya funkciya fwrite dlya zapisi v fajl. Primer: #include <stdio.h> #define MAXPTS 200 #define N 127 char filename[] = "pts.dat"; struct point { int x,y; } pts[MAXPTS], pp= { -1, -2}; main(){ int n, i; FILE *fp = fopen(filename, "w"); for(i=0; i < N; i++) /* generaciya tochek */ pts[i].x = i, pts[i].y = i * i; /* zapis' massiva iz N tochek v fajl */ fwrite((char *)pts, sizeof(struct point), N, fp); fwrite((char *)&pp, sizeof pp, 1, fp); fp = freopen(filename, "r", fp); /* ili fclose(fp); fp=fopen(filename, "r"); */ /* chtenie tochek iz fajla v massiv */ n = fread(pts, sizeof pts[0], MAXPTS, fp); for(i=0; i < n; i++) printf("Tochka #%d(%d,%d)\n",i,pts[i].x,pts[i].y); } Fajly, sozdannye fwrite, ne perenosimy na mashiny drugogo tipa, poskol'ku v nih hra- nitsya ne tekst, a dvoichnye dannye v formate, ispol'zuemom dannym processorom. Takoj fajl ne mozhet byt' ponyat chelovekom - on ne soderzhit izobrazhenij dannyh v vide teksta, a soderzhit "syrye" bajty. Poetomu chashche pol'zuyutsya funkciyami raboty s tekstovymi faj- lami: fprintf, fscanf, fputs, fgets. Dannye, hranimye v vide teksta, imeyut eshche odno preimushchestvo pomimo perenosimosti: ih legko pri nuzhde podpravit' tekstovym redakto- rom. Zato oni zanimayut bol'she mesta! Analogom sistemnogo vyzova lseek sluzhit funkciya fseek: fseek(fp, offset, whence); Ona polnost'yu analogichna lseek, za isklyucheniem vozvrashchaemogo eyu znacheniya. Ona NE vozvrashchaet novuyu poziciyu ukazatelya chteniya/zapisi! CHtoby uznat' etu poziciyu primenya- etsya special'naya funkciya long ftell(fp); Ona vnosit popravku na polozhenie ukazatelya v bufere kanala fp. fseek sbrasyvaet flag "byl dostignut konec fajla", kotoryj proveryaetsya makrosom feof(fp); A. Bogatyrev, 1992-95 - 161 - Si v UNIX 4.37. Najdite oshibku v programme (programma raspechatyvaet kornevoj katalog v "sta- rom" formate katalogov - s fiksirovannoj dlinoj imen): #include <stdio.h> #include <sys/types.h> #include <sys/dir.h> main(){ FILE *fp; struct direct d; char buf[DIRSIZ+1]; buf[DIRSIZ] = '\0'; fp = fopen( '/', "r" ); while( fread( &d, sizeof d, 1, fp) == 1 ){ if( !d.d_ino ) continue; /* fajl stert */ strncpy( buf, d.d_name, DIRSIZ); printf( "%s\n", buf ); } fclose(fp); } Ukazanie: smotri v fopen(). Vnimatel'nee k strokam i simvolam! '/' i "/" - eto sovershenno raznye veshchi (hotya sintaksicheskoj oshibki net!). Peredelajte etu programmu, chtoby nazvanie kataloga postupalo iz argumentov main (a esli nazvanie ne zadano - ispol'zujte tekushchij katalog "."). 4.38. Funkciyami fputs( stroka, fp); printf( format, ...); fprintf(fp, format, ...); nevozmozhno vyvesti stroku format, soderzhashchuyu v seredine bajt '\0', poskol'ku on slu- zhit dlya nih priznakom konca stroki. Odnako takoj bajt mozhet ponadobit'sya v fajle, esli my formiruem nekotorye netekstovye dannye, naprimer upravlyayushchuyu posledovatel'- nost' pereklyucheniya shriftov dlya printera. Kak byt'? Est' mnogo variantov resheniya. Pust' my hotim vydat' v kanal fp posledovatel'nost' iz 4h bajt "\033e\0\5". My mozhem sdelat' eto posimvol'no: putc('\033',fp); putc('e', fp); putc('\000',fp); putc('\005',fp); (mozhno prosto v cikle), libo ispol'zovat' odin iz sposobov: fprintf( fp, "\033e%c\5", '\0'); write ( fileno(fp), "\033e\0\5", 4 ); fwrite ( "\033e\0\5", sizeof(char), 4, fp); gde 4 - kolichestvo vyvodimyh bajtov. 4.39. Napishite funkcii dlya "bystrogo dostupa" k strokam fajla. Ideya takova: snachala prochitat' ves' fajl ot nachala do konca i smeshcheniya nachal strok (adresa po fajlu) zapomnit' v massiv chisel tipa long (tochnee, off_t), ispol'zuya funkcii fgets() i ftell(). Dlya bystrogo chteniya n-oj stroki ispol'zujte funkcii fseek() i fgets(). #include <stdio.h> #define MAXLINES 2000 /* Maksim. chislo strok v fajle*/ FILE *fp; /* Ukazatel' na fajl */ int nlines; /* CHislo strok v fajle */ long offsets[MAXLINES];/* Adresa nachal strok */ extern long ftell();/*Vydaet smeshchenie ot nachala fajla*/ A. Bogatyrev, 1992-95 - 162 - Si v UNIX char buffer[256]; /* Bufer dlya chteniya strok */ /* Razmetka massiva adresov nachal strok */ void getSeeks(){ int c; offsets[0] =0L; while((c = getc(fp)) != EOF) if(c =='\n') /* Konec stroki - nachalo novoj */ offsets[++nlines] = ftell(fp); /* Esli poslednyaya stroka fajla ne imeet \n na konce, */ /* no ne pusta, to ee vse ravno nado poschitat' */ if(ftell(fp) != offsets[nlines]) nlines++; printf( "%d strok v fajle\n", nlines); } char *getLine(n){ /* Prochest' stroku nomer n */ fseek(fp, offsets[n], 0); return fgets(buffer, sizeof buffer, fp); } void main(){ /* pechat' fajla zadom-napered */ int i; fp = fopen("INPUT", "r"); getSeeks(); for( i=nlines-1; i>=0; --i) printf( "%3d:%s", i, getLine(i)); } 4.40. CHto budet vydano na ekran v rezul'tate vypolneniya programmy? #include <stdio.h> main(){ printf( "Hello, " ); printf( "sunny " ); write( 1, "world", 5 ); } Otvet: ochen' hochetsya otvetit', chto budet napechatano "Hello, sunny world", poskol'ku printf vyvodit v kanal stdout, svyazannyj s deskriptorom 1, a deskriptor 1 svyazan po- umolchaniyu s terminalom. Uvy, eta dogadka verna lish' otchasti! Budet napechatano "worldHello, sunny ". |to proishodit potomu, chto vyvod pri pomoshchi funkcii printf buferizovan, a pri pomoshchi sisvyzova write - net. printf pomeshchaet stroku snachala v bufer kanala stdout, zatem write vydaet svoe soobshchenie neposredstvenno na ekran, zatem po okonchanii programmy bufer vytalkivaetsya na ekran. CHtoby poluchit' pravil'nyj effekt, sleduet pered write() napisat' vyzov yavnogo vytalkivaniya bufera kanala stdout: fflush( stdout ); Eshche odno vozmozhnoe reshenie - otmena buferizacii kanala stdout: pered pervym printf mozhno napisat' setbuf(stdout, NULL); Imejte v vidu, chto kanal vyvoda soobshchenij ob oshibkah stderr ne buferizovan ishodno, poetomu vydavaemye v nego soobshcheniya pechatayutsya nemedlenno. Moral': nado byt' ochen' ostorozhnym pri smeshannom ispol'zovanii buferizovannogo i nebuferizovannogo obmena. A. Bogatyrev, 1992-95 - 163 - Si v UNIX Nekotorye kanaly buferizuyutsya tak, chto bufer vytalkivaetsya ne tol'ko pri zapol- nenii, no i pri postuplenii simvola '\n' ("postrochnaya buferizaciya"). Kanal stdout imenno takov: printf("Hello\n"); pechataetsya srazu (t.k. printf vyvodit v stdout i est' '\n'). Vklyuchit' takoj rezhim buferizacii mozhno tak: setlinebuf(fp); ili v drugih versiyah setvbuf(fp, NULL, _IOLBF, BUFSIZ); Uchtite, chto lyuboe izmenenie sposoba buferizacii dolzhno byt' sdelano DO pervogo obra- shcheniya k kanalu! 4.41. Napishite programmu, vydayushchuyu tri zvukovyh signala. Gudok na terminale vyzyva- etsya vydachej simvola '\7' ('\a' po standartu ANSI). CHtoby gudki zvuchali razdel'no, nado delat' pauzu posle kazhdogo iz nih. (Uchtite, chto vyvod pri pomoshchi printf() i putchar() buferizovan, poetomu posle vydachi kazhdogo gudka (v bufer) nado vyzyvat' funkciyu fflush() dlya sbrosa bufera). Otvet: Sposob 1: register i; for(i=0; i<3; i++){ putchar( '\7' ); fflush(stdout); sleep(1); /* pauza 1 sek. */ } Sposob 2: register i; for(i=0; i<3; i++){ write(1, "\7", 1 ); sleep(1); } 4.42. Pochemu zaderzhka ne oshchushchaetsya? printf( "Pauza..."); sleep ( 5 ); /* zhdem 5 sek. */ printf( "prodolzhaem\n" ); Otvet: iz-za buferizacii kanala stdout. Pervaya fraza popadaet v bufer i, esli on ne zapolnilsya, ne vydaetsya na ekran. Dal'she programma "molchalivo" zhdet 5 sekund. Obe frazy budut vydany uzhe posle zaderzhki! CHtoby pervyj printf() vydal svoyu frazu DO zaderzhki, sleduet pered funkciej sleep() vstavit' vyzov fflush(stdout) dlya yavnogo vytalkivaniya bufera. Zamechanie: kanal stderr ne buferizovan, poetomu problemu mozhno reshit' i tak: fprintf( stderr, "Pauza..." ); 4.43. Eshche odin primer pro buferizaciyu. Pochemu programma pechataet EOF? #include <stdio.h> FILE *fwr, *frd; char b[40], *s; int n = 1917; main(){ fwr = fopen( "aFile", "w" ); A. Bogatyrev, 1992-95 - 164 - Si v UNIX frd = fopen( "aFile", "r" ); fprintf( fwr, "%d: Hello, dude!", n); s = fgets( b, sizeof b, frd ); printf( "%s\n", s ? s : "EOF" ); } Otvet: potomu chto k momentu chteniya bufer kanala fwr eshche ne vytolknut v fajl: fajl pust! Nado vstavit' fflush(fwr); posle fprintf(). Vot eshche podobnyj sluchaj: FILE *fp = fopen("users", "w"); ... fprintf(fp, ...); ... system("sort users | uniq > 00; mv 00 users"); K momentu vyzova komandy sortirovki bufer kanala fp (tochnee, poslednij iz nakoplennyh za vremya raboty buferov) mozhet byt' eshche ne vytolknut v fajl. Sleduet libo zakryt' fajl fclose(fp) neposredstvenno pered vyzovom system, libo vstavit' tuda zhe fflush(fp); 4.44. V UNIX mnogie vneshnie ustrojstva (prakticheski vse!) s tochki zreniya programm yavlyayutsya prosto fajlami. Fajly-ustrojstva imeyut imena, no ne zanimayut mesta na diske (ne imeyut blokov). Zato im sootvetstvuyut special'nye programmy-drajvery v yadre. Pri otkrytii takogo fajla-ustrojstva my na samom dele inicializiruem drajver etogo ust- rojstva, i v dal'nejshem on vypolnyaet nashi zaprosy read, write, lseek apparatno- zavisimym obrazom. Dlya operacij, specifichnyh dlya dannogo ustrojstva, predusmotren sisvyzov ioctl (input/output control): ioctl(fd, ROD_RABOTY, argument); gde argument chasto byvaet adresom struktury, soderzhashchej paket argumentov, a ROD_RABOTY - odno iz celyh chisel, specifichnyh dlya dannogo ustrojstva (dlya kazhdogo ustr-va est' svoj sobstvennyj spisok dopustimyh operacij). Obychno ROD_RABOTY imeet nekotoroe mnemonicheskoe oboznachenie. V kachestve primera privedem operaciyu TCGETA, primenimuyu tol'ko k terminalam i uznayushchuyu tekushchie mody drajvera terminala (sm. glavu "|krannye biblioteki"). To, chto eta operaciya neprimenima k drugim ustrojstvam i k obychnym fajlam (ne ustrojstvam), pozvolyaet nam ispol'zovat' ee dlya proverki - yavlyaetsya li otkrytyj fajl terminalom (ili klaviaturoj): #include <termio.h> int isatty(fd){ struct termio tt; return ioctl(fd, TCGETA, &tt) < 0 ? 0 : 1; } main(){ printf("%s\n", isatty(0 /* STDIN */)? "term":"no"); } Funkciya isatty yavlyaetsya standartnoj funkciej|-. Est' "psevdoustrojstva", kotorye predstavlyayut soboj drajvery logicheskih ust- rojstv, ne svyazannyh napryamuyu s apparaturoj, libo svyazannyh lish' kosvenno. Primerom takogo ustrojstva yavlyaetsya psevdoterminal (sm. primer v prilozhenii). Naibolee upot- rebitel'ny dva psevdoustrojstva: /dev/null |to ustrojstvo, predstavlyayushchee soboj "chernuyu dyru". CHtenie iz nego nemedlenno vydaet priznak konca fajla: read(...)==0; a zapisyvaemaya v nego informaciya nigde ne sohranyaetsya (propadaet). |tot fajl ispol'zuetsya, naprimer, v tom sluchae, kogda my hotim proignorirovat' vyvod kakoj-libo programmy (soobshcheniya ob oshibkah, trassirovku), nigde ego ne sohranyaya. Togda my prosto perenapravlyaem ee vyvod v /dev/null: A. Bogatyrev, 1992-95 - 165 - Si v UNIX $ a.out > /dev/null & Eshche odin primer ispol'zovaniya: $ cp /dev/hd00 /dev/null Soderzhimoe vsego vinchestera kopiruetsya "v nikuda". Pri etom, esli na diske est' sbojnye bloki - sistema vydaet na konsol' soobshcheniya ob oshibkah chteniya. Tak my mozhem bystro vyyasnit', est' li na diske plohie bloki. /dev/tty Otkrytie fajla s takim imenem v dejstvitel'nosti otkryvaet dlya nas upravlyayushchij terminal, na kotorom zapushchena dannaya programma; dazhe esli ee vvod i vyvod byli perenapravleny v kakie-to drugie fajly|=. Poetomu, esli my hotim vydat' soobshche- nie, kotoroe dolzhno poyavit'sya imenno na ekrane, my dolzhny postupat' tak: #include <stdio.h> void message(char *s){ FILE *fptty = fopen("/dev/tty", "w"); fprintf(fptty, "%s\n", s); fclose (fptty); } main(){ message("Tear down the wall!"); } |to ustrojstvo dostupno i dlya zapisi (na ekran) i dlya chteniya (s klaviatury). Fajly ustrojstv nechuvstvitel'ny k flagu otkrytiya O_TRUNC - on ne imeet dlya nih smysla i prosto ignoriruetsya. Poetomu nevozmozhno sluchajno unichtozhit' fajl-ustrojstvo (k pri- meru /dev/tty) vyzovom fd=creat("/dev/tty", 0644); Fajly-ustrojstva sozdayutsya vyzovom mknod, a unichtozhayutsya obychnym unlink-om. Bolee podrobno pro eto - v glave "Vzaimodejstvie s UNIX". 4.45. |mulyaciya osnov biblioteki STDIO, po motivam 4.2 BSD. #include <fcntl.h> #define BUFSIZ 512 /* standartnyj razmer bufera */ #define _NFILE 20 #define EOF (-1) /* priznak konca fajla */ #define NULL ((char *) 0) #define IOREAD 0x0001 /* dlya chteniya */ #define IOWRT 0x0002 /* dlya zapisi */ #define IORW 0x0004 /* dlya chteniya i zapisi */ #define IONBF 0x0008 /* ne buferizovan */ #define IOTTY 0x0010 /* vyvod na terminal */ #define IOALLOC 0x0020 /* vydelen bufer malloc-om */ #define IOEOF 0x0040 /* dostignut konec fajla */ #define IOERR 0x0080 /* oshibka chteniya/zapisi */ ____________________ |- Zametim eshche, chto esli deskriptor fd svyazan s terminalom, to mozhno uznat' polnoe imya etogo ustrojstva vyzovom standartnoj funkcii extern char *ttyname(); char *tname = ttyname(fd); Ona vydast stroku, podobnuyu "/dev/tty01". Esli fd ne svyazan s terminalom - ona vernet A. Bogatyrev, 1992-95 - 166 - Si v UNIX extern char *malloc(); extern long lseek(); typedef unsigned char uchar; uchar sibuf[BUFSIZ], sobuf[BUFSIZ]; typedef struct _iobuf { int cnt; /* schetchik */ uchar *ptr, *base; /* ukazatel' v bufer i na ego nachalo */ int bufsiz, flag, file; /* razmer bufera, flagi, deskriptor */ } FILE; FILE iob[_NFILE] = { { 0, NULL, NULL, 0, IOREAD, 0 }, { 0, NULL, NULL, 0, IOWRT|IOTTY, 1 }, { 0, NULL, NULL, 0, IOWRT|IONBF, 2 }, }; #define stdin (&iob[0]) #define stdout (&iob[1]) #define stderr (&iob[2]) #define putchar(c) putc((c), stdout) #define getchar() getc(stdin) #define fileno(fp) ((fp)->file) #define feof(fp) (((fp)->flag & IOEOF) != 0) #define ferror(fp) (((fp)->flag & IOERR) != 0) #define clearerr(fp) ((void) ((fp)->flag &= ~(IOERR | IOEOF))) #define getc(fp) (--(fp)->cnt < 0 ? \ filbuf(fp) : (int) *(fp)->ptr++) #define putc(x, fp) (--(fp)->cnt < 0 ? \ flsbuf((uchar) (x), (fp)) : \ (int) (*(fp)->ptr++ = (uchar) (x))) int fputc(int c, FILE *fp){ return putc(c, fp); } int fgetc( FILE *fp){ return getc(fp); } ____________________ NULL. ____________________ |= Ssylka na upravlyayushchij terminal processa hranitsya v u-area kazhdogo processa: u_ttyp, u_ttyd, poetomu yadro v sostoyanii opredelit' kakoj nastoyashchij terminal sleduet otkryt' dlya vas. Esli raznye processy otkryvayut /dev/tty, oni mogut otkryt' v itoge raznye terminaly, t.e. odno imya privodit k raznym ustrojstvam! Smotri glavu pro UNIX. A. Bogatyrev, 1992-95 - 167 - Si v UNIX /* Otkrytie fajla */ FILE *fopen(char *name, char *how){ register FILE *fp; register i, rw; for(fp = iob, i=0; i < _NFILE; i++, fp++) if(fp->flag == 0) goto found; return NULL; /* net svobodnogo slota */ found: rw = how[1] == '+'; if(*how == 'r'){ if((fp->file = open(name, rw ? O_RDWR:O_RDONLY)) < 0) return NULL; fp->flag = IOREAD; } else { if((fp->file = open(name, (rw ? O_RDWR:O_WRONLY)| O_CREAT | (*how == 'a' ? O_APPEND : O_TRUNC), 0666 )) < 0) return NULL; fp->flag = IOWRT; } if(rw) fp->flag = IORW; fp->bufsiz = fp->cnt = 0; fp->base = fp->ptr = NULL; return fp; } /* Prinuditel'nyj sbros bufera */ void fflush(FILE *fp){ uchar *base; int full= 0; if((fp->flag & (IONBF|IOWRT)) == IOWRT && (base = fp->base) != NULL && (full=fp->ptr - base) > 0){ fp->ptr = base; fp->cnt = fp->bufsiz; if(write(fileno(fp), base, full) != full) fp->flag |= IOERR; } } /* Zakrytie fajla */ void fclose(FILE *fp){ if((fp->flag & (IOREAD|IOWRT|IORW)) == 0 ) return; fflush(fp); close(fileno(fp)); if(fp->flag & IOALLOC) free(fp->base); fp->base = fp->ptr = NULL; fp->cnt = fp->bufsiz = fp->flag = 0; fp->file = (-1); } /* Zakrytie fajlov pri exit()-e */ void _cleanup(){ register i; for(i=0; i < _NFILE; i++) fclose(iob + i); } /* Zavershit' tekushchij process */ void exit(uchar code){ _cleanup(); _exit(code); /* Sobstvenno sistemnyj vyzov */ } A. Bogatyrev, 1992-95 - 168 - Si v UNIX /* Prochest' ocherednoj bufer iz fajla */ int filbuf(FILE *fp){ static uchar smallbuf[_NFILE]; if(fp->flag & IORW){ if(fp->flag & IOWRT){ fflush(fp); fp->flag &= ~IOWRT; } fp->flag |= IOREAD; /* operaciya chteniya */ } if((fp->flag & IOREAD) == 0 || feof(fp)) return EOF; while( fp->base == NULL ) /* otvesti bufer */ if( fp->flag & IONBF ){ /* nebuferizovannyj */ fp->base = &smallbuf[fileno(fp)]; fp->bufsiz = sizeof(uchar); } else if( fp == stdin ){ /* staticheskij bufer */ fp->base = sibuf; fp->bufsiz = sizeof(sibuf); } else if((fp->base = malloc(fp->bufsiz = BUFSIZ)) == NULL) fp->flag |= IONBF; /* ne budem buferizovat' */ else fp->flag |= IOALLOC; /* bufer vydelen */ if( fp == stdin && (stdout->flag & IOTTY)) fflush(stdout); fp->ptr = fp->base; /* sbrosit' na nachalo bufera */ if((fp->cnt = read(fileno(fp), fp->base, fp->bufsiz)) == 0 ){ fp->flag |= IOEOF; if(fp->flag & IORW) fp->flag &= ~IOREAD; return EOF; } else if( fp->cnt < 0 ){ fp->flag |= IOERR; fp->cnt = 0; return EOF; } return getc(fp); } A. Bogatyrev, 1992-95 - 169 - Si v UNIX /* Vytolknut' ocherednoj bufer v fajl */ int flsbuf(int c, FILE *fp){ uchar *base; int full, cret = c; if( fp->flag & IORW ){ fp->flag &= ~(IOEOF|IOREAD); fp->flag |= IOWRT; /* operaciya zapisi */ } if((fp->flag & IOWRT) == 0) return EOF; tryAgain: if(fp->flag & IONBF){ /* ne buferizovan */ if(write(fileno(fp), &c, 1) != 1) { fp->flag |= IOERR; cret=EOF; } fp->cnt = 0; } else { /* kanal buferizovan */ if((base = fp->base) == NULL){ /* bufera eshche net */ if(fp == stdout){ if(isatty(fileno(stdout))) fp->flag |= IOTTY; else fp->flag &= ~IOTTY; fp->base = fp->ptr = sobuf; /* staticheskij bufer */ fp->bufsiz = sizeof(sobuf); goto tryAgain; } if((base = fp->base = malloc(fp->bufsiz = BUFSIZ))== NULL){ fp->bufsiz = 0; fp->flag |= IONBF; goto tryAgain; } else fp->flag |= IOALLOC; } else if ((full = fp->ptr - base) > 0) if(write(fileno(fp), fp->ptr = base, full) != full) { fp->flag |= IOERR; cret = EOF; } fp->cnt = fp->bufsiz - 1; *base++ = c; fp->ptr = base; } return cret; } /* Vernut' simvol v bufer */ int ungetc(int c, FILE *fp){ if(c == EOF || fp->flag & IONBF || fp->base == NULL) return EOF; if((fp->flag & IOREAD)==0 || fp->ptr <= fp->base) if(fp->ptr == fp->base && fp->cnt == 0) fp->ptr++; else return EOF; fp->cnt++; return(* --fp->ptr = c); } /* Izmenit' razmer bufera */ void setbuffer(FILE *fp, uchar *buf, int size){ fflush(fp); if(fp->base && (fp->flag & IOALLOC)) free(fp->base); fp->flag &= ~(IOALLOC|IONBF); if((fp->base = fp->ptr = buf) == NULL){ fp->flag |= IONBF; fp->bufsiz = 0; } else fp->bufsiz = size; fp->cnt = 0; } A. Bogatyrev, 1992-95 - 170 - Si v UNIX /* "Peremotat'" fajl v nachalo */ void rewind(FILE *fp){ fflush(fp); lseek(fileno(fp), 0L, 0); fp->cnt = 0; fp->ptr = fp->base; clearerr(fp); if(fp->flag & IORW) fp->flag &= ~(IOREAD|IOWRT); } /* Pozicionirovanie ukazatelya chteniya/zapisi */ #ifdef COMMENT base ptr sluchaj IOREAD | |<----cnt---->| 0L |b u |f e r | |=======######@@@@@@@@@@@@@@======== fajl file | |<-p->|<-dl-->| |<----pos---->| | | |<----offset(new)-->| | |<----RWptr---------------->| gde pos = RWptr - cnt; // ukazatel' s popravkoj offset = pos + p = RWptr - cnt + p = lseek(file,0L,1) - cnt + p otsyuda: (dlya SEEK_SET) p = offset+cnt-lseek(file,0L,1); ili (dlya SEEK_CUR) dl = RWptr - offset = p - cnt lseek(file, dl, 1); Uslovie, chto ukazatel' mozhno sdvinut' prosto v bufere: if( cnt > 0 && p <= cnt && base <= ptr + p ){ ptr += p; cnt -= p; } #endif /*COMMENT*/ A. Bogatyrev, 1992-95 - 171 - Si v UNIX int fseek(FILE *fp, long offset, int whence){ register resync, c; long p = (-1); clearerr(fp); if( fp->flag & (IOWRT|IORW)){ fflush(fp); if(fp->flag & IORW){ fp->cnt = 0; fp->ptr = fp->base; fp->flag &= ~IOWRT; } p = lseek(fileno(fp), offset, whence); } else if( fp->flag & IOREAD ){ if(whence < 2 && fp->base && !(fp->flag & IONBF)){ c = fp->cnt; p = offset; if(whence == 0) /* SEEK_SET */ p += c - lseek(fileno(fp), 0L, 1); else offset -= c; if(!(fp->flag & IORW) && c > 0 && p <= c && p >= fp->base - fp->ptr ){ fp->ptr += (int) p; fp->cnt -= (int) p; return 0; /* done */ } resync = offset & 01; } else resync = 0; if(fp->flag & IORW){ fp->ptr = fp->base; fp->flag &= ~IOREAD; resync = 0; } p = lseek(fileno(fp), offset-resync, whence); fp->cnt = 0; /* vynudit' filbuf(); */ if(resync) getc(fp); } return (p== -1 ? -1 : 0); } /* Uznat' tekushchuyu poziciyu ukazatelya */ long ftell(FILE *fp){ long tres; register adjust; if(fp->cnt < 0) fp->cnt = 0; if(fp->flag & IOREAD) adjust = -(fp->cnt); else if(fp->flag & (IOWRT|IORW)){ adjust = 0; if(fp->flag & IOWRT && fp->base && !(fp->flag & IONBF)) /* buferizovan */ adjust = fp->ptr - fp->base; } else return (-1L); if((tres = lseek(fileno(fp), 0L, 1)) < 0) return tres; return (tres + adjust); } A. Bogatyrev, 1992-95 - 172 - Si v UNIX 5. Struktury dannyh. Struktury ("zapisi") predstavlyayut soboj agregaty raznorodnyh dannyh (polej raz- nogo tipa); v otlichie ot massivov, gde vse elementy imeyut odin i tot zhe tip. struct { int x, y; /* dva celyh polya */ char s[10]; /* i odno - dlya stroki */ } s1; Strukturnyj tip mozhet imet' imya: struct XYS { int x, y; /* dva celyh polya */ char str[10]; /* i odno - dlya stroki */ }; Zdes' my ob®yavili tip, no ne otveli ni odnoj peremennoj etogo tipa (hotya mogli by). Teper' opishem peremennuyu etogo tipa i ukazatel' na nee: struct XYS s2, *sptr = &s2; Dostup k polyam struktury proizvoditsya po imeni polya (a ne po indeksu, kak u massi- vov): imya_strukturnoj_peremennoj.imya_polya ukazatel'_na_strukturu -> imya_polya to est' ne a #define VES 0 struct { int ves, rost; } x; #define ROST 1 x.rost = 175; int x[2]; x[ROST] = 175; Naprimer s1.x = 13; strcpy(s2.str, "Finish"); sptr->y = 27; Struktura mozhet soderzhat' struktury drugogo tipa v kachestve polej: struct XYS_Z { struct XYS xys; int z; } a1; a1.xys.x = 71; a1.z = 12; Struktura togo zhe samogo tipa ne mozhet soderzhat'sya v kachestve polya - rekursivnye opredeleniya zapreshcheny. Zato neredko ispol'zuyutsya polya - ssylki na struktury takogo zhe tipa (ili drugogo). |to pozvolyaet organizovyvat' spiski struktur: struct node { int value; struct node *next; }; Ochen' chasto ispol'zuyutsya massivy struktur: A. Bogatyrev, 1992-95 - 173 - Si v UNIX struct XYS array[20]; int i = 5, j; array[i].x = 12; j = array[i].x; Staticheskie struktury mozhno opisyvat' s inicializaciej, perechislyaya znacheniya ih polej v {} cherez zapyatuyu: extern struct node n2; struct node n1 = { 1, &n2 }, n2 = { 2, &n1 }, n3 = { 3, NULL }; V etom primere n2 opisano predvaritel'no dlya togo, chtoby &n2 v stroke inicializacii n1 bylo opredeleno. Struktury odinakovogo tipa mozhno prisvaivat' celikom (chto sootvetstvuet prisvai- vaniyu kazhdogo iz polej): struct XYS s1, s2; ... s2 = s1; v otlichie ot massivov, kotorye prisvaivat' celikom nel'zya: int a[5], b[5]; a = b; /* OSHIBOCHNO ! */ Primer obrashcheniya k polyam struktury: typedef struct _Point { short x, y; /* koordinaty tochki */ char *s; /* metka tochki */ } Point; Point p; Point *pptr; short *iptr; struct _Curve { Point points[25]; /* vershiny lomannoj */ int color; /* cvet linii */ } aLine[10], *linePtr = & aLine[0]; ... pptr = &p; /* ukazatel' na strukturu p */ p.x = 1; p.y = 2; p.s = "Grue"; linePtr->points[2].x = 54; aLine[5].points[0].y = 17; V y r a zh e n i e znachenie ---------+------------+------------+-----------+----------- p.x | pptr->x | (*pptr).x | (&p)->x | 1 ---------+------------+------------+-----------+----------- &p->x | oshibka -----------+----------------+------------------+----------- iptr= &p.x | iptr= &pptr->x | iptr= &(pptr->x) | adres polya -----------+----------------+--------+---------+----------- *pptr->s | *(pptr->s) | *p.s | p.s[0] | 'G' -----------+----------------+--------+---------+----------- pptr->s[1] | (&p)->s[1] | p.s[1] | 'r' -----------+----------------+------------------+----------- &p->s[1] | oshibka -----------+----------------+------------------+----------- (*pptr).s | pptr->s | p.s | "Grue" -----------+----------------+------------------+----------- *pptr.s | oshibka -----------------------------------------------+----------- A. Bogatyrev, 1992-95 - 174 - Si v UNIX Voobshche (&p)->field = p.field pptr->field = (*pptr).field Ob®edineniya - eto agregaty dannyh, kotorye mogut hranit' v sebe znacheniya dannyh raznyh tipov na odnom i tom zhe meste. struct a{ int x, y; char *s; } A; union b{ int i; char *s; struct a aa; } B; Struktura: ________________________ A: | A.x int | Tri polya ------------------------ raspolozheny podryad. | A.y int | Poluchaetsya kak by ------------------------ "kartochka" s grafami. | A.s char * | ------------------------ A u ob®edinenij polya raspolozheny "parallel'no", na odnom meste v pamyati. _______________________________________________________ B: | B.i int | B.s char * | B.aa : B.aa.x int | -----------| | struct a : B.aa.y int | ---------------| : B.aa.s char * | |___________________________| |to kak by "yashchik" v kotoryj mozhno pomestit' znachenie lyubogo tipa iz perechislennyh, no ne VSE VMESTE ("i to i eto", kak u struktur), a PO OCHEREDI ("ili/ili"). Razmer ego dostatochno velik, chtob vmestit' samyj bol'shoj iz perechislennyh tipov dannyh. My mozhem zanesti v union znachenie i interpretirovat' ego kak drugoj tip dannyh - eto inogda ispol'zuetsya v mashinno-zavisimyh programmah. Vot primer, vyyasnyayushchij porya- dok bajtov v short chislah: union lb { char s[2]; short i; } x; unsigned hi, lo; x.i = (02 << 8) | 01; hi = x.s[1]; lo = x.s[0]; printf( "%d %d\n", hi, lo); ili tak: #include <stdio.h> union { int i; unsigned char s[sizeof(int)]; } u; void main(){ unsigned char *p; int n; u.i = 0x12345678; for(n=0, p=u.s; n < sizeof(int); n++, p++){ printf("%02X ", *p); } putchar('\n'); } A. Bogatyrev, 1992-95 - 175 - Si v UNIX ili poryadok slov v long chislah: union xx { long l; struct ab { short a; /* low word */ short b; /* high word */ } ab; } c; main(){ /* Na IBM PC 80386 pechataet 00020001 */ c.ab.a = 1; c.ab.b = 2; printf("%08lx\n", c.l ); } 5.1. Najdite oshibki v opisanii strukturnogo shablona: structure { int arr[12], char string, int *sum } 5.2. Razrabotajte strukturnyj shablon, kotoryj soderzhal by nazvanie mesyaca, trehbuk- vennuyu abbreviaturu mesyaca, kolichestvo dnej v mesyace i nomer mesyaca. Inicializirujte ego dlya nevisokosnogo goda. struct month { char name[10]; /* ili char *name; */ char abbrev[4]; /* ili char *abbrev; */ int days; int num; }; struct month months[12] = { /* indeks */ {"YAnvar'" , "YAnv", 31, 1 }, /* 0 */ {"Fevral'", "Fev", 28, 2 }, /* 1 */ ... {"Dekabr'", "Dek", 31, 12}, /* 11 */ }, *mptr = & months[0]; /* ili *mptr = months */ main(){ struct month *mptr; printf( "%s\n", mptr[1].name ); printf( "%s %d\n", mptr->name, mptr->num ); } Napishite funkciyu, sohranyayushchuyu massiv months v fajl; funkciyu, schityvayushchuyu ego iz fajla. Ispol'zujte fprintf i fscanf. V chem budet raznica v funkcii chteniya, kogda pole name opisano kak char name[10] i kak char *name? Otvet: vo vtorom sluchae dlya sohraneniya prochitannoj stroki nado zakazyvat' pamyat' dinamicheski pri pomoshchi malloc() i sohranyat' v nej stroku pri pomoshchi strcpy(), t.k. pamyat' dlya hraneniya samoj stroki v strukture ne zarezervirovana (a tol'ko dlya ukaza- telya na nee). Najdite oshibku v operatorah funkcii main(). Pochemu pechataetsya ne "Fevral'", a kakoj-to musor? Ukazanie: kuda ukazyvaet ukazatel' mptr, opisannyj v main()? Otvet: v "neizvestno kuda" - eto lokal'naya peremennaya (prichem ne poluchivshaya nachal'nogo znache- niya - v nej soderzhitsya musor), a ne to zhe samoe, chto ukazatel' mptr, opisannyj vyshe! Uberite opisanie mptr iz main. A. Bogatyrev, 1992-95 - 176 - Si v UNIX Zametim, chto dlya raspechatki vseh ili neskol'kih polej struktury sleduet YAVNO perechislit' v printf() vse nuzhnye polya i ukazat' formaty, sootvetstvuyushchie tipam etih polej. Ne sushchestvuet formata ili standartnoj