Ocenite etot tekst:


 © Copyright Andrej Bogatyrev. 1992-95
 Email: abs@decart.msu.su
 Txt version is located at

A. Bogatyrev, 1992-95 - 1 - Si v UNIX Um podoben zheludku. Vazhno ne to, skol'ko ty v nego vlozhish', a to, skol'ko on smozhet perevarit'. V etoj knige vy najdete ryad zadach, primerov, algoritmov, sovetov i stilistiches- kih zamechanij po ispol'zovaniyu yazyka programmirovaniya "C" (Si) v srede operacionnoj sistemy UNIX. Zdes' sobrany etyudy raznoj slozhnosti i "shtrihi k portretu" yazyka Si. Takzhe opisany razlichnye "podvodnye kamni" na kotoryh neredko terpyat krushenie novichki v Si. V etom smysle etu knigu mozhno mestami nazvat' "Kak ne nado programmirovat' na Si". V bol'shinstve sluchaev v kachestve platformy ispol'zuetsya personal'nyj komp'yuter IBM PC s kakoj-libo sistemoj UNIX, libo SPARCstation 20 s sistemoj Solaris 2 (tozhe UNIX svr4), no mnogie primery bez kakih-libo izmenenij (libo s minimumom takovyh) mogut byt' pereneseny v sredu MS DOS|=, libo na drugoj tip mashiny s sistemoj UNIX. |to vasha VTORAYA kniga po Si. |ta kniga ne uchebnik, a hrestomatiya k uchebniku. Ona ne yavlyaetsya ni sistematicheskim kursom po Si, ni spravochnikom po nemu, i prednaz- nachena ne dlya odnorazovogo posledovatel'nogo prochteniya, a dlya chteniya v neskol'ko pro- hodov na raznyh etapah vashej "zrelosti". Poetomu chitat' ee sleduet vmeste s "nastoya- shchim" uchebnikom po Si, sredi kotoryh naibolee izvestna kniga Kernigana i Ritchi. |ta kniga - ne POSLEDNYAYA vasha kniga po Si. Vo-pervyh potomu, chto koe-chto v yazyke vse zhe menyaetsya so vremenem, hotya i nastal chas, kogda standart na yazyk Si nakonec prinyat... No poyavilsya yazyk C++, kotoryj razvivaetsya dovol'no dinamichno. Eshche est' Objective-C. Vo-vtoryh potomu, chto est' biblioteki i sistemnye vyzovy, kotorye raz- vivayutsya vsled za razvitiem UNIX i drugih operacionnyh sistem. Sleduyushchimi vashimi (nastol'nymi) knigami dolzhny stat' "Spravochnoe rukovodstvo": man2 (po sistemnym vyzo- vam), man3 (po bibliotechnym funkciyam). Moshch' yazyka Si - v sushchestvuyushchem mnogoobrazii bibliotek. Proshu vas s pervyh zhe shagov sledit' za stilem oformleniya svoih programm. Delajte otstupy, pishite kommentarii, ispol'zujte osmyslennye imena peremennyh i funkcij, otdelyajte logicheskie chasti programmy drug ot druga pustymi strokami. Pomnite, chto "lishnie" probely i pustye stroki v Si dopustimy vezde, krome izobrazhenij konstant i imen. Programmy na Si, nabitye v odnu kolonku (kak na FORTRAN-e) ochen' tyazhelo chitat' i ponimat'. Iz-za etogo byvaet trudno nahodit' poteryannye skobki { i }, poteryannye simvoly `;' i drugie oshibki. Sushchestvuet neskol'ko "shkol" oformleniya programm - priglyadites' k primeram v etoj knige i v drugih istochnikah - i vyberite lyubuyu! Nichego strashnogo, esli vy budete smeshivat' eti stili. No - PODALXSHE OT FORTRAN-a !!! Programmu mozhno avtomaticheski sformatirovat' k "kanonicheskomu" vidu pri pomoshchi, naprimer, programmy cb. cb < NashFajl.c > /tmp/$$ mv /tmp/$$ NashFajl.c no luchshe srazu oformlyat' programmu pravil'no. Vydelyajte logicheski samostoyatel'nye ("zamknutye") chasti programmy v funkcii (dazhe esli oni budut vyzyvat'sya edinstvennyj raz). Funkcii - ne prosto sredstvo izbezhat' povtoreniya odnih i teh zhe operatorov v tekste programmy, no i sredstvo strukturirovaniya processa programmirovaniya, delayushchee programmu bolee ponyatnoj. Vo- pervyh, vy mozhete v drugoj programme ispol'zovat' tekst uzhe napisannoj vami ranee funkcii vmesto togo, chtoby pisat' ee zanovo. Vo-vtoryh, operaciyu, oformlennuyu v vide funkcii, mozhno rassmatrivat' kak nedelimyj primitiv (ot dovol'no prostogo po smyslu, vrode strcmp, strcpy, do dovol'no slozhnogo - qsort, malloc, gets) i zabyt' o ego vnutrennem ustrojstve (eto horosho - nado men'she pomnit'). ____________________ |= MS DOS - torgovyj znak firmy Microsoft Corporation. (chitaetsya "Majkrosoft"); DOS - diskovaya operacionnaya sistema. A. Bogatyrev, 1992-95 - 2 - Si v UNIX Ne gonites' za kratkost'yu v ushcherb yasnosti. Si pozvolyaet poroj pisat' takie vyra- zheniya, nad kotorymi mozhno polchasa lomat' golovu. Esli zhe ih zapisat' menee mudreno, no chut' dlinnee - oni samoochevidny (i etim bolee zashchishcheny ot oshibok). V sisteme UNIX vy mozhete posmotret' opisanie lyuboj komandy sistemy ili funkcii Si, nabrav komandu man nazvanieFunkcii (man - ot slova manual, "rukovodstvo"). Eshche odno naputstvie: uchite anglijskij yazyk! Prakticheski vse yazyki programmirova- niya ispol'zuyut anglijskie slova (v kachestve klyuchevyh slov, terminov, imen peremennyh i funkcij). Poetomu luchshe ponimat' znachenie etih slov (hotya i vospriyatie ih kak prosto nekih simvolov tozhe imeet opredelennye dostoinstva). Obratno - programmirova- nie na Si pomozhet vam vyuchit' anglijskij. Po razlichnym prichinam na territorii Rossii sejchas ispol'zuetsya mnogo raznyh vos'mibitnyh russkih kodirovok. Sredi nih: KOI-8 Istoricheski prinyataya na russkih UNIX sistemah - samaya rannyaya iz poyavivshihsya. Otlichaetsya tem svojstvom, chto esli u nee obrezan vos'moj bit: c & 0177 - to ona vse zhe chitaema s terminala kak transliteraciya latinskih bukv. Imenno etoj kodi- rovkoj pol'zuetsya avtor etoj knigi (kak i bol'shinstvo UNIX-sites seti RelCom). ISO 8859/5 |to amerikanskij standart na russkuyu kodirovku. A russkie programmisty k ee razrabotke ne imeyut nikakogo otnosheniya. Eyu pol'zuetsya bol'shinstvo kommercheskih baz dannyh. Microsoft 1251 |to ta kodirovka, kotoroj pol'zuetsya Microsoft Windows. Vozmozhno, chto imenno k etoj kodirovke pridut i UNIX sistemy (gipoteza 1994 goda). Al'ternativnaya kodirovka dlya MS DOS Russkaya kodirovka s psevdografikoj, ispol'zovavshayasya v MS DOS. Kodirovka dlya Macintosh |to velikoe "raznoobrazie" prichinyaet massu neudobstv. No, gospoda, eto Rossiya - chto znachit - shirota dushi i absolyutnyj bardak. Relax and enjoy. Mnogie primery v dannoj knige dany vmeste s otvetami - kak obrazcami dlya podra- zhaniya. Odnako my nadeemsya, chto Vy uderzhites' ot iskusheniya i snachala proverite svoi sily, a lish' potom posmotrite v otvet! Itak, chitaya primery - delajte po analogii. A. Bogatyrev, 1992-95 - 3 - Si v UNIX 1.1. Sostav'te programmu privetstviya s ispol'zovaniem funkcii printf. Po tradicii prinyato pechatat' frazu "Hello, world !" ("Zdravstvuj, mir !"). 1.2. Najdite oshibku v programme #include <stdio.h> main(){ printf("Hello, world\n"); } Otvet: raz ne ob®yavleno inache, funkciya main schitaetsya vozvrashchayushchej celoe znachenie (int). No funkciya main ne vozvrashchaet nichego - v nej prosto net operatora return. Korrektno bylo by tak: #include <stdio.h> main(){ printf("Hello, world\n"); return 0; } ili #include <stdio.h> void main(){ printf("Hello, world\n"); exit(0); } a uzh sovsem korrektno - tak: #include <stdio.h> int main(int argc, char *argv[]){ printf("Hello, world\n"); return 0; } 1.3. Najdite oshibki v programme #include studio.h main { int i i := 43 print ('V godu i nedel'') } 1.4. CHto budet napechatano v privedennom primere, kotoryj yavlyaetsya chast'yu polnoj programmy: int n; n = 2; printf ("%d + %d = %d\n", n, n, n + n); 1.5. V chem sostoyat oshibki? A. Bogatyrev, 1992-95 - 4 - Si v UNIX if( x > 2 ) then x = 2; if x < 1 x = 1; Otvet: v Si net klyuchevogo slova then, usloviya v operatorah if, while dolzhny brat'sya v ()-skobki. 1.6. Napishite programmu, pechatayushchuyu vashe imya, mesto raboty i adres. V pervom vari- ante programmy ispol'zujte bibliotechnuyu funkciyu printf, a vo vtorom - puts. 1.7. Sostav'te programmu s ispol'zovaniem sleduyushchih postfiksnyh i prefiksnyh opera- cij: a = b = 5 a + b a++ + b ++a + b --a + b a-- + b Raspechatajte poluchennye znacheniya i proanalizirujte rezul'tat. 1.8. Cikl for ________________________________________________________________________________ for(INIT; CONDITION; INCR) BODY ________________________________________________________________________________ INIT; repeat: if(CONDITION){ BODY; cont: INCR; goto repeat; } out: ; Cikl while ________________________________________________________________________________ while(COND) BODY ________________________________________________________________________________ cont: repeat: if(CONDITION){ BODY; goto repeat; } out: ; A. Bogatyrev, 1992-95 - 5 - Si v UNIX Cikl do ________________________________________________________________________________ do BODY while(CONDITION) ________________________________________________________________________________ cont: repeat: BODY; if(CONDITION) goto repeat; out: ; V operatorah cikla vnutri tela cikla BODY mogut prisutstvovat' operatory break i continue; kotorye oznachayut na nashih shemah sleduyushchee: #define break goto out #define continue goto cont 1.9. Sostav'te programmu pechati pryamougol'nogo treugol'nika iz zvezdochek * ** *** **** ***** ispol'zuya cikl for. Vvedite peremennuyu, znacheniem kotoroj yavlyaetsya razmer kateta tre- ugol'nika. 1.10. Napishite operatory Si, kotorye vydayut stroku dliny WIDTH, v kotoroj snachala soderzhitsya x0 simvolov '-', zatem w simvolov '*', i do konca stroki - vnov' simvoly '-'. Otvet: int x; for(x=0; x < x0; ++x) putchar('-'); for( ; x < x0 + w; x++) putchar('*'); for( ; x < WIDTH ; ++x) putchar('-'); putchar('\n'); libo for(x=0; x < WIDTH; x++) putchar( x < x0 ? '-' : x < x0 + w ? '*' : '-' ); putchar('\n'); 1.11. Napishite programmu s ciklami, kotoraya risuet treugol'nik: * *** ***** ******* ********* A. Bogatyrev, 1992-95 - 6 - Si v UNIX Otvet: /* Treugol'nik iz zvezdochek */ #include <stdio.h> /* Pechat' n simvolov c */ printn(c, n){ while( --n >= 0 ) putchar(c); } int lines = 10; /* chislo strok treugol'nika */ void main(argc, argv) char *argv[]; { register int nline; /* nomer stroki */ register int naster; /* kolichestvo zvezdochek v stroke */ register int i; if( argc > 1 ) lines = atoi( argv[1] ); for( nline=0; nline < lines ; nline++ ){ naster = 1 + 2 * nline; /* lidiruyushchie probely */ printn(' ', lines-1 - nline); /* zvezdochki */ printn('*', naster); /* perevod stroki */ putchar( '\n' ); } exit(0); /* zavershenie programmy */ } 1.12. V chem sostoit oshibka? main(){ /* pechat' frazy 10 raz */ int i; while(i < 10){ printf("%d-yj raz\n", i+1); i++; } } Otvet: avtomaticheskaya peremennaya i ne byla proinicializirovana i soderzhit ne 0, a kakoe-to proizvol'noe znachenie. Cikl mozhet vypolnit'sya ne 10, a lyuboe chislo raz (v tom chisle i 0 po sluchajnosti). Ne zabyvajte inicializirovat' peremennye, voz'mite opisanie s inicializaciej za pravilo! int i = 0; Esli by peremennaya i byla staticheskoj, ona by imela nachal'noe znachenie 0. V dannom primere bylo by eshche luchshe ispol'zovat' cikl for, v kotorom vse operacii nad indeksom cikla sobrany v odnom meste - v zagolovke cikla: for(i=0; i < 10; i++) printf(...); A. Bogatyrev, 1992-95 - 7 - Si v UNIX 1.13. Vspomogatel'nye peremennye, ne nesushchie smyslovoj nagruzki (vrode schetchika pov- torenij cikla, ne ispol'zuemogo v samom tele cikla) prinyato po tradicii oboznachat' odnobukvennymi imenami, vrode i, j. Bolee togo, vozmozhny dazhe takie kur'ezy: main(){ int _ ; for( _ = 0; _ < 10; _++) printf("%d\n", _ ); } osnovannye na tom, chto podcherk v identifikatorah - polnopravnaya bukva. 1.14. Najdite 2 oshibki v programme: main(){ int x = 12; printf( "x=%d\n" ); int y; y = 2 * x; printf( "y=%d\n", y ); } Kommentarij: v tele funkcii vse opisaniya dolzhny idti pered vsemi vypolnyaemymi opera- torami (krome operatorov, vhodyashchih v sostav opisanij s inicializaciej). Ochen' chasto posle vneseniya pravok v programmu nekotorye opisaniya okazyvayutsya posle vypolnyaemyh operatorov. Imenno poetomu rekomenduetsya otdelyat' stroki opisaniya peremennyh ot vypolnyaemyh operatorov pustymi strokami (v etoj knige eto chasto ne delaetsya dlya eko- nomii mesta). 1.15. Najdite oshibku: int n; n = 12; main(){ int y; y = n+2; printf( "%d\n", y ); } Otvet: vypolnyaemyj operator n=12 nahoditsya vne tela kakoj-libo funkcii. Sleduet vnesti ego v main() posle opisaniya peremennoj y, libo perepisat' ob®yavlenie pered main() v vide int n = 12; V poslednem sluchae prisvaivanie peremennoj n znacheniya 12 vypolnit kompilyator eshche vo vremya kompilyacii programmy, a ne sama programma pri svoem zapuske. Tochno tak zhe pro- ishodit so vsemi staticheskimi dannymi (opisannymi kak static, libo raspolozhennymi vne vseh funkcij); prichem esli ih nachal'noe znachenie ne ukazano yavno - to podrazumevaetsya 0 ('\0', NULL, ""). Odnako nulevye znacheniya ne hranyatsya v skompilirovannom vypolnyae- mom fajle, a trebuemaya "chistaya" pamyat' raspisyvaetsya pri starte programmy. 1.16. Po povodu opisaniya peremennoj s inicializaciej: TYPE x = vyrazhenie; yavlyaetsya (pochti) ekvivalentom dlya TYPE x; /* opisanie */ x = vyrazhenie; /* vychislenie nachal'nogo znacheniya */ A. Bogatyrev, 1992-95 - 8 - Si v UNIX Rassmotrim primer: #include <stdio.h> extern double sqrt(); /* kvadratnyj koren' */ double x = 1.17; double s12 = sqrt(12.0); /* #1 */ double y = x * 2.0; /* #2 */ FILE *fp = fopen("out.out", "w"); /* #3 */ main(){ double ss = sqrt(25.0) + x; /* #4 */ ... } Stroki s metkami #1, #2 i #3 oshibochny. Pochemu? Otvet: pri inicializacii staticheskih dannyh (a s12, y i fp takovymi i yavlyayutsya, tak kak opisany vne kakoj-libo funkcii) vyrazhenie dolzhno soderzhat' tol'ko konstanty, poskol'ku ono vychislyaetsya KOMPILYATOROM. Poetomu ni ispol'zovanie znachenij peremennyh, ni vyzovy funkcij zdes' nedopustimy (no mozhno brat' adresa ot peremennyh). V stroke #4 my inicializiruem avtomaticheskuyu peremennuyu ss, t.e. ona otvoditsya uzhe vo vremya vypolneniya programmy. Poetomu vyrazhenie dlya inicializacii vychislyaetsya uzhe ne kompilyatorom, a samoj programmoj, chto daet nam pravo ispol'zovat' peremennye, vyzovy funkcij i.t.p., to est' vyrazheniya yazyka Si bez ogranichenij. 1.17. Napishite programmu, realizuyushchuyu eho-pechat' vvodimyh simvolov. Programma dolzhna zavershat' rabotu pri poluchenii priznaka EOF. V UNIX pri vvode s klaviatury priznak EOF obychno oboznachaetsya odnovremennym nazhatiem klavish CTRL i D (CTRL chut' ran'she), chto v dal'nejshem budet oboznachat'sya CTRL/D; a v MS DOS - klavish CTRL/Z. Ispol'zujte getchar() dlya vvoda bukvy i putchar() dlya vyvoda. 1.18. Napishite programmu, podschityvayushchuyu chislo simvolov postupayushchih so standartnogo vvoda. Kakie dostoinstva i nedostatki u sleduyushchej realizacii: #include <stdio.h> main(){ double cnt = 0.0; while (getchar() != EOF) ++cnt; printf("%.0f\n", cnt ); } Otvet: i dostoinstvo i nedostatok v tom, chto schetchik imeet tip double. Dostoinstvo - mozhno podschitat' ochen' bol'shoe chislo simvolov; nedostatok - operacii s double obychno vypolnyayutsya gorazdo medlennee, chem s int i long (do desyati raz), programma budet rabotat' dol'she. V povsednevnyh zadachah vam vryad li ponadobitsya imet' schetchik, otlichnyj ot long cnt; (pechatat' ego nado po formatu "%ld"). 1.19. Sostav'te programmu perekodirovki vvodimyh simvolov so standartnogo vvoda po sleduyushchemu pravilu: a -> b b -> c c -> d ... z -> a drugoj simvol -> * Kody strochnyh latinskih bukv raspolozheny podryad po vozrastaniyu. 1.20. Sostav'te programmu perekodirovki vvodimyh simvolov so standartnogo vvoda po sleduyushchemu pravilu: A. Bogatyrev, 1992-95 - 9 - Si v UNIX B -> A C -> B ... Z -> Y drugoj simvol -> * Kody propisnyh latinskih bukv takzhe raspolozheny po vozrastaniyu. 1.21. Napishite programmu, pechatayushchuyu nomer i kod vvedennogo simvola v vos'merichnom i shestnadcaterichnom vide. Zamet'te, chto esli vy naberete na vvode stroku simvolov i nazhmete klavishu ENTER, to programma napechataet vam na odin simvol bol'she, chem vy nab- rali. Delo v tom, chto kod klavishi ENTER, zavershivshej vvod stroki - simvol '\n' - tozhe popadaet v vashu programmu (na ekrane on otobrazhaetsya kak perevod kursora v nachalo sleduyushchej stroki!). 1.22. Razberites', v chem sostoit raznica mezhdu simvolami '0' (cifra nul') i '\0' (nulevoj bajt). Napechatajte printf( "%d %d %c\n", '\0', '0', '0' ); Postav'te opyt: chto pechataet programma? main(){ int c = 060; /* kod simvola '0' */ printf( "%c %d %o\n", c, c, c); } Pochemu pechataetsya 0 48 60? Teper' napishite vmesto int c = 060; strochku char c = '0'; 1.23. CHto napechataet programma? #include <stdio.h> void main(){ printf("ab\0cd\nxyz"); putchar('\n'); } Zapomnite, chto '\0' sluzhit priznakom konca stroki v pamyati, a '\n' - v fajle. CHto v stroke "abcd\n" na konce neyavno uzhe raspolozhen nulevoj bajt: 'a','b','c','d','\n','\0' CHto stroka "ab\0cd\nxyz" - eto 'a','b','\0','c','d','\n','x','y',z','\0' CHto stroka "abcd\0" - izbytochna, poskol'ku budet imet' na konce dva nulevyh bajta (chto ne vredno, no zachem?). CHto printf pechataet stroku do nulevogo bajta, a ne do zakryvayushchej kavychki. Programma eta napechataet ab i perevod stroki. Vopros: chemu raven sizeof("ab\0cd\nxyz")? Otvet: 10. 1.24. Napishite programmu, pechatayushchuyu celye chisla ot 0 do 100. 1.25. Napishite programmu, pechatayushchuyu kvadraty i kuby celyh chisel. A. Bogatyrev, 1992-95 - 10 - Si v UNIX 1.26. Napishite programmu, pechatayushchuyu summu kvadratov pervyh n celyh chisel. 1.27. Napishite programmu, kotoraya perevodit sekundy v dni, chasy, minuty i sekundy. 1.28. Napishite programmu, perevodyashchuyu skorost' iz kilometrov v chas v metry v sekun- dah. 1.29. Napishite programmu, shifruyushchuyu tekst fajla putem zameny znacheniya simvola (nap- rimer, znachenie simvola C zamenyaetsya na C+1 ili na ~C ). 1.30. Napishite programmu, kotoraya pri vvedenii s klaviatury bukvy pechataet na termi- nale klyuchevoe slovo, nachinayushcheesya s dannoj bukvy. Naprimer, pri vvedenii bukvy 'b' pechataet "break". 1.31. Napishite programmu, otgadyvayushchuyu zadumannoe vami chislo v predelah ot 1 do 200, pol'zuyas' podskazkoj s klaviatury "=" (ravno), "<" (men'she) i ">" (bol'she). Dlya uga- dyvaniya chisla ispol'zujte metod deleniya popolam. 1.32. Napishite programmu, pechatayushchuyu stepeni dvojki 1, 2, 4, 8, ... Zamet'te, chto, nachinaya s nekotorogo n, rezul'tat stanovitsya otricatel'nym iz-za pere- polneniya celogo. 1.33. Napishite podprogrammu vychisleniya kvadratnogo kornya s ispol'zovaniem metoda kasatel'nyh (N'yutona): x(0) = a 1 a x(n+1) = - * ( ---- + x(n)) 2 x(n) Iterirovat', poka ne budet | x(n+1) - x(n) | < 0.001 Vnimanie! V dannoj zadache massiv ne nuzhen. Dostatochno hranit' tekushchee i predydu- shchee znacheniya x i obnovlyat' ih posle kazhdoj iteracii. 1.34. Napishite programmu, raspechatyvayushchuyu prostye chisla do 1000. 1, 2, 3, 5, 7, 11, 13, 17, ... A. Bogatyrev, 1992-95 - 11 - Si v UNIX /*#!/bin/cc primes.c -o primes -lm * Prostye chisla. */ #include <stdio.h> #include <math.h> int debug = 0; /* Koren' kvadratnyj iz chisla po metodu N'yutona */ #define eps 0.0001 double sqrt (x) double x; { double sq, sqold, EPS; if (x < 0.0) return -1.0; if (x == 0.0) return 0.0; /* mozhet privesti k deleniyu na 0 */ EPS = x * eps; sq = x; sqold = x + 30.0; /* != sq */ while (fabs (sq * sq - x) >= EPS) { /* fabs( sq - sqold )>= EPS */ sqold = sq; sq = 0.5 * (sq + x / sq); } return sq; } /* tablica prostyh chisel */ int is_prime (t) register int t; { register int i, up; int not_div; if (t == 2 || t == 3 || t == 5 || t == 7) return 1; /* prime */ if (t % 2 == 0 || t == 1) return 0; /* composite */ up = ceil (sqrt ((double) t)) + 1; i = 3; not_div = 1; while (i <= up && not_div) { if (t % i == 0) { if (debug) fprintf (stderr, "%d podelilos' na %d\n", t, i); not_div = 0; break; } i += 2; /* * Net smysla proveryat' chetnye, * potomu chto esli delitsya na 2*n, * to delitsya i na 2, * a etot sluchaj uzhe obrabotan vyshe. */ } return not_div; } A. Bogatyrev, 1992-95 - 12 - Si v UNIX #define COL 6 int n; main (argc, argv) char **argv; { int i, j; int n; if( argc < 2 ){ fprintf( stderr, "Vyzov: %s chislo [-]\n", argv[0] ); exit(1); } i = atoi (argv[1]); /* stroka -> celoe, eyu izobrazhaemoe */ if( argc > 2 ) debug = 1; printf ("\t*** Tablica prostyh chisel ot 2 do %d ***\n", i); n = 0; for (j = 1; j <= i; j++) if (is_prime (j)){ /* raspechatka v COL kolonok */ printf ("%3d%s", j, n == COL-1 ? "\n" : "\t"); if( n == COL-1 ) n = 0; else n++; } printf( "\n---\n" ); exit (0); } 1.35. Sostav'te programmu vvoda dvuh kompleksnyh chisel v vide A + B * I (kazhdoe na otdel'noj stroke) i pechati ih proizvedeniya v tom zhe vide. Ispol'zujte scanf i printf. Pered tem, kak ispol'zovat' scanf, prover'te sebya: chto neverno v nizheprivedennom ope- ratore? int x; scanf( "%d", x ); Otvet: dolzhno byt' napisano "ADRES ot x", to est' scanf( "%d", &x ); 1.36. Napishite podprogrammu vychisleniya kornya uravneniya f(x)=0 metodom deleniya otrezka popolam. Privedem realizaciyu etogo algoritma dlya poiska celochislennogo kvad- ratnogo kornya iz celogo chisla (etot algoritm mozhet ispol'zovat'sya, naprimer, v mashin- noj grafike pri risovanii dug): /* Maksimal'noe unsigned long chislo */ #define MAXINT (~0L) /* Opredelim imya-sinonim dlya tipa unsigned long */ typedef unsigned long ulong; /* Funkciya, koren' kotoroj my ishchem: */ #define FUNC(x, arg) ((x) * (x) - (arg)) /* togda x*x - arg = 0 oznachaet x*x = arg, to est' * x = koren'_kvadratnyj(arg) */ /* Nachal'nyj interval. Dolzhen vybirat'sya ishodya iz * osobennostej funkcii FUNC */ #define LEFT_X(arg) 0 #define RIGHT_X(arg) (arg > MAXINT)? MAXINT : (arg/2)+1; /* KORENX KVADRATNYJ, okruglennyj vniz do celogo. * Reshaetsya po metodu deleniya otrezka popolam: * FUNC(x, arg) = 0; x = ? A. Bogatyrev, 1992-95 - 13 - Si v UNIX */ ulong i_sqrt( ulong arg ) { register ulong mid, /* seredina intervala */ rgt, /* pravyj kraj intervala */ lft; /* levyj kraj intervala */ lft = LEFT_X(arg); rgt = RIGHT_X(arg); do{ mid = (lft + rgt + 1 )/2; /* +1 dlya oshibok okrugleniya pri celochislennom delenii */ if( FUNC(mid, arg) > 0 ){ if( rgt == mid ) mid--; rgt = mid ; /* priblizit' pravyj kraj */ } else lft = mid ; /* priblizit' levyj kraj */ } while( lft < rgt ); return mid; } void main(){ ulong i; for(i=0; i <= 100; i++) printf("%ld -> %lu\n", i, i_sqrt(i)); } Ispol'zovannoe nami pri ob®yavlenii peremennyh klyuchevoe slovo register oznachaet, chto peremennaya yavlyaetsya CHASTO ISPOLXZUEMOJ, i kompilyator dolzhen popytat'sya razmestit' ee na registre processora, a ne v steke (za schet chego uvelichitsya skorost' obrashcheniya k etoj peremennoj). |to slovo ispol'zuetsya kak register tip peremennaya; register peremennaya; /* podrazumevaetsya tip int */ Ot registrovyh peremennyh nel'zya brat' adres: &peremennaya oshibochno. 1.37. Napishite programmu, vychislyayushchuyu chisla treugol'nika Paskalya i pechatayushchuyu ih v vide treugol'nika. C(0,n) = C(n,n) = 1 n = 0... C(k,n+1) = C(k-1,n) + C(k,n) k = 1..n n - nomer stroki V raznyh variantah ispol'zujte cikly, rekursiyu. 1.38. Napishite funkciyu vychisleniya opredelennogo integrala metodom Monte-Karlo. Dlya etogo vam pridetsya napisat' generator sluchajnyh chisel. Si predostavlyaet standartnyj datchik CELYH ravnomerno raspredelennyh psevdosluchajnyh chisel: esli vy hotite poluchit' celoe chislo iz intervala [A..B], ispol'zujte int x = A + rand() % (B+1-A); CHtoby poluchat' raznye posledovatel'nosti sleduet zadavat' nekij nachal'nyj parametr posledovatel'nosti (eto nazyvaetsya "randomizaciya") pri pomoshchi srand( chislo ); /* luchshe nechetnoe */ CHtoby povtorit' odnu i tu zhe posledovatel'nost' sluchajnyh chisel neskol'ko raz, vy dolzhny postupat' tak: srand(NBEG); x=rand(); ... ; x=rand(); /* i povtorit' vse snachala */ srand(NBEG); x=rand(); ... ; x=rand(); Ispol'zuemyj metod polucheniya sluchajnyh chisel takov: A. Bogatyrev, 1992-95 - 14 - Si v UNIX static unsigned long int next = 1L; int rand(){ next = next * 1103515245 + 12345; return ((unsigned int)(next/65536) % 32768); } void srand(seed) unsigned int seed; { next = seed; } Dlya randomizacii chasto pol'zuyutsya takim priemom: char t[sizeof(long)]; time(t); srand(t[0] + t[1] + t[2] + t[3] + getpid()); 1.39. Napishite funkciyu vychisleniya opredelennogo integrala po metodu Simpsona. /*#!/bin/cc $* -lm * Vychislenie integrala po metodu Simpsona */ #include <math.h> extern double integral(), sin(), fabs(); #define PI 3.141593 double myf(x) double x; { return sin(x / 2.0); } int niter; /* nomer iteracii */ void main(){ double integral(); printf("%g\n", integral(0.0, PI, myf, 0.000000001)); /* Zamet'te, chto myf, a ne myf(). * Tochnoe znachenie integrala ravno 2.0 */ printf("%d iteracij\n", niter ); } A. Bogatyrev, 1992-95 - 15 - Si v UNIX double integral(a, b, f, eps) double a, b; /* koncy otrezka */ double eps; /* trebuemaya tochnost' */ double (*f)(); /* podyntegral'naya funkciya */ { register long i; double fab = (*f)(a) + (*f)(b); /* summa na krayah */ double h, h2; /* shag i udvoennyj shag */ long n, n2; /* chislo tochek razbieniya i ono zhe udvoennoe */ double Sodd, Seven; /* summa znachenij f v nechetnyh i v chetnyh tochkah */ double S, Sprev;/* znachenie integrala na dannoj i na predydushchej iteraciyah */ double x; /* tekushchaya abscissa */ niter = 0; n = 10L; /* chetnoe chislo */ n2 = n * 2; h = fabs(b - a) / n2; h2 = h * 2.0; /* Vychislyaem pervoe priblizhenie */ /* Summa po nechetnym tochkam: */ for( Sodd = 0.0, x = a+h, i = 0; i < n; i++, x += h2 ) Sodd += (*f)(x); /* Summa po chetnym tochkam: */ for( Seven = 0.0, x = a+h2, i = 0; i < n-1; i++, x += h2 ) Seven += f(x); /* Predvaritel'noe znachenie integrala: */ S = h / 3.0 * (fab + 4.0 * Sodd + 2.0 * Seven ); do{ niter++; Sprev = S; /* Vychislyaem integral s polovinnym shagom */ h2 = h; h /= 2.0; if( h == 0.0 ) break; /* poterya znachimosti */ n = n2; n2 *= 2; Seven = Seven + Sodd; /* Vychislyaem summu po novym tochkam: */ for( Sodd = 0.0, x = a+h, i = 0; i < n; i++, x += h2 ) Sodd += (*f)(x); /* Znachenie integrala */ S = h / 3.0 * (fab + 4.0 * Sodd + 2.0 * Seven ); } while( niter < 31 && fabs(S - Sprev) / 15.0 >= eps ); /* Ispol'zuem uslovie Runge dlya okonchaniya iteracij */ return ( 16.0 * S - Sprev ) / 15.0 ; /* Vozvrashchaem utochnennoe po Richardsonu znachenie */ } A. Bogatyrev, 1992-95 - 16 - Si v UNIX 1.40. Gde oshibka? struct time_now{ int hour, min, sec; } X = { 13, 08, 00 }; /* 13 chasov 08 minut 00 sek.*/ Otvet: 08 - vos'merichnoe chislo (tak kak nachinaetsya s nulya)! A v vos'merichnyh chislah cifry 8 i 9 ne byvayut. 1.41. Dan tekst: int i = -2; i <<= 2; printf("%d\n", i); /* pechat' sdvinutogo i : -8 */ i >>= 2; printf("%d\n", i); /* pechataetsya -2 */ Zakommentiruem dve stroki (isklyuchaya ih iz programmy): int i = -2; i <<= 2; /* printf("%d\n", i); /* pechat' sdvinutogo i : -8 */ i >>= 2; */ printf("%d\n", i); /* pechataetsya -2 */ Pochemu teper' voznikaet oshibka? Ukazanie: gde konchaetsya kommentarij? Otvet: Si ne dopuskaet vlozhennyh kommentariev. Vmesto etogo chasto ispol'zuyutsya konstrukcii vrode: #ifdef COMMENT ... zakommentirovannyj tekst ... #endif /*COMMENT*/ i vrode /**/ printf("here");/* otladochnaya vydacha vklyuchena */ /* printf("here");/* otladochnaya vydacha vyklyuchena */ ili /* vyklyucheno(); /**/ vklyucheno(); /**/ A vot deshevyj sposob bystro isklyuchit' operator (s vozmozhnost'yu vosstanovleniya) - konec kommentariya zanimaet otdel'nuyu stroku, chto pozvolyaet otredaktirovat' takoj tekst redaktorom pochti ne sdvigaya kursor: /*printf("here"); */ 1.42. Pochemu programma pechataet nevernoe znachenie dlya i2 ? A. Bogatyrev, 1992-95 - 17 - Si v UNIX int main(int argc, char *argv[]){ int i1, i2; i1 = 1; /* Inicializiruem i1 / i2 = 2; /* Inicializiruem i2 */ printf("Numbers %d %d\n", i1, i2); return(0); } Otvet: v pervom operatore prisvaivaniya ne zakryt kommentarij - ves' vtoroj operator prisvaivaniya polnost'yu proignorirovalsya! Pravil'nyj variant: int main(int argc, char *argv[]){ int i1, i2; i1 = 1; /* Inicializiruem i1 */ i2 = 2; /* Inicializiruem i2 */ printf("Numbers %d %d\n", i1, i2); return(0); } 1.43. A vot "shal'noj" kommentarij. void main(){ int n = 10; int *ptr = &n; int x, y = 40; x = y/*ptr /* dolzhno byt' 4 */ + 1; printf( "%d\n", x ); /* pyat' */ exit(0); } /* ili takoj primer iz zhizni - vzyat iz perepiski v Relcom */ ... cost = nRecords/*pFactor /* divided by Factor, and */ + fixMargin; /* plus the precalculated */ ... Rezul'tat nepredskazuem. Delo v tom, chto y/*ptr prevratilos' v nachalo kommentariya! Poetomu binarnye operacii prinyato okruzhat' probelami. x = y / *ptr /* dolzhno byt' 4 */ + 1; 1.44. Najdite oshibki v direktivah preprocessora Si |- (vertikal'naya cherta oboznachaet levyj kraj fajla). ____________________ |- Preprocessor Si - eto programma /lib/cpp A. Bogatyrev, 1992-95 - 18 - Si v UNIX | | #include <stdio.h> |#include < sys/types.h > |# define inc (x) ((x) + 1) |#define N 12; |#define X -2 | |... printf( "n=%d\n", N ); |... p = 4-X; Otvet: v pervoj direktive stoit probel pered #. Diez dolzhen nahodit'sya v pervoj pozicii stroki. Vo vtoroj direktive v <> nahodyatsya lishnie probely, ne otnosyashchiesya k imeni fajla - preprocessor ne najdet takogo fajla! V dannom sluchae "krasota" poshla vo vred delu. V tret'ej - mezhdu imenem makro inc i ego argumentom v kruglyh skobkah (x) stoit probel, kotoryj izmenyaet ves' smysl makroopredeleniya: vmesto makrosa s parametrom inc(x) my poluchaem, chto slovo inc budet zamenyat'sya na (x)((x)+1). Zametim odnako, chto probely posle # pered imenem direktivy vpolne dopustimy. V chetvertom sluchae pokazana harakternaya opechatka - simvol ; posle opredeleniya. V rezul'tate napi- sannyj printf() zamenitsya na printf( "n=%d\n", 12; ); gde lishnyaya ; dast sintaksicheskuyu oshibku. V pyatom sluchae oshibki net, no nas ozhidaet nepriyatnost' v stroke p=4-X; kotoraya rasshiritsya v stroku p=4--2; yavlyayushchuyusya sintaksicheski nevernoj. CHtoby izbezhat' podob- noj situacii, sledovalo by napisat' p = 4 - X; /* cherez probely */ no eshche proshche (i luchshe) vzyat' makroopredelenie v skobki: #define X (-2) 1.45. Napishite funkciyu max(x, y), vozvrashchayushchuyu bol'shee iz dvuh znachenij. Napishite analogichnoe makroopredelenie. Napishite makroopredeleniya min(x, y) i abs(x) (abs - modul' chisla). Otvet: #define abs(x) ((x) < 0 ? -(x) : (x)) #define min(x,y) (((x) < (y)) ? (x) : (y)) Zachem x vzyat v kruglye skobki (x)? Predpolozhim, chto my napisali #define abs(x) (x < 0 ? -x : x ) vyzyvaem abs(-z) abs(a|b) poluchaem (-z < 0 ? --z : -z ) (a|b < 0 ? -a|b : a|b ) U nas poyavilas' "dikaya" operaciya --z; a vyrazhenie a|b<0 sootvetstvuet a|(b<0), s sov- sem drugim poryadkom operacij! Poetomu zaklyuchenie vseh argumentov makrosa v ego tele v kruglye skobki pozvolyaet izbezhat' mnogih neozhidannyh problem. Priderzhivajtes' etogo pravila! Vot primer, pokazyvayushchij zachem polezno brat' v skobki vse opredelenie: #define div(x, y) (x)/(y) Pri vyzove A. Bogatyrev, 1992-95 - 19 - Si v UNIX z = sizeof div(1, 2); prevratitsya v z = sizeof(1) / (2); chto ravno sizeof(int)/2, a ne sizeof(int). Variant #define div(x, y) ((x) / (y)) budet rabotat' pravil'no. 1.46. Makrosy, v otlichie ot funkcij, mogut porozhdat' nepredvidennye pobochnye effekty: int sqr(int x){ return x * x; } #define SQR(x) ((x) * (x)) main(){ int y=2, z; z = sqr(y++); printf("y=%d z=%d\n", y, z); y = 2; z = SQR(y++); printf("y=%d z=%d\n", y, z); } Vyzov funkcii sqr pechataet "y=3 z=4", kak my i ozhidali. Makros zhe SQR rasshiryaetsya v z = ((y++) * (y++)); i rezul'tatom budet "y=4 z=6", gde z sovsem ne pohozhe na kvadrat chisla 2. 1.47. ANSI preprocessor|- yazyka Si imeet operator ## - "sklejka leksem": #define VAR(a, b) a ## b #define CV(x) command_ ## x main(){ int VAR(x, 31) = 1; /* prevratitsya v int x31 = 1; */ int CV(a) = 2; /* dast int command_a = 2; */ ... } Starye versii preprocessora ne obrabatyvayut takoj operator, poetomu ran'she ispol'zo- valsya takoj tryuk: #define VAR(a, b) a/**/b v kotorom predpolagaetsya, chto preprocessor udalyaet kommentarii iz teksta, ne zamenyaya ih na probely. |to ne vsegda tak, poetomu takaya konstrukciya ne mobil'na i pol'zo- vat'sya eyu ne rekomenduetsya. 1.48. Napishite programmu, raspechatyvayushchuyu maksimal'noe i minimal'noe iz ryada chisel, vvodimyh s klaviatury. Ne hranite vvodimye chisla v massive, vychislyajte max i min srazu pri vvode ocherednogo chisla! ____________________ |- ANSI - American National Standards Institute, razrabotavshij standart na yazyk Si i ego okruzhenie. A. Bogatyrev, 1992-95 - 20 - Si v UNIX #include <stdio.h> main(){ int max, min, x, n; for( n=0; scanf("%d", &x) != EOF; n++) if( n == 0 ) min = max = x; else{ if( x > max ) max = x; if( x < min ) min = x; } printf( "Vveli %d chisel: min=%d max=%d\n", n, min, max); } Napishite analogichnuyu programmu dlya poiska maksimuma i minimuma sredi elementov mas- siva, iznachal'no min=max=array[0]; 1.49. Napishite programmu, kotoraya sortiruet massiv zadannyh chisel po vozrastaniyu (ubyvaniyu) metodom puzyr'kovoj sortirovki. Kogda vy stanete bolee opytny v Si, napi- shite sortirovku metodom SHella. /* * Sortirovka po metodu SHella. * Sortirovke podvergaetsya massiv ukazatelej na dannye tipa obj. * v------.-------.------.-------.------0 * ! ! ! ! * * * * * * elementy tipa obj * Programma vzyata iz knigi Kernigana i Ritchi. */ #include <stdio.h> #include <string.h> #include <locale.h> #define obj char static shsort (v,n,compare) int n; /* dlina massiva */ obj *v[]; /* massiv ukazatelej */ int (*compare)(); /* funkciya sravneniya sosednih elementov */ { int g, /* rasstoyanie, na kotorom proishodit sravnenie */ i,j; /* indeksy sravnivaemyh elementov */ obj *temp; for( g = n/2 ; g > 0 ; g /= 2 ) for( i = g ; i < n ; i++ ) for( j = i-g ; j >= 0 ; j -= g ) { if((*compare)(v[j],v[j+g]) <= 0) break; /* uzhe v pravil'nom poryadke */ /* obmenyat' ukazateli */ temp = v[j]; v[j] = v[j+g]; v[j+g] = temp; /* V kachestve uprazhneniya mozhete napisat' * pri pomoshchi curses-a programmu, * vizualiziruyushchuyu process sortirovki: * naprimer, izobrazhayushchuyu etu perestanovku * elementov massiva */ } } A. Bogatyrev, 1992-95 - 21 - Si v UNIX /* sortirovka strok */ ssort(v) obj **v; { extern less(); /* funkciya sravneniya strok */ int len; /* podschet chisla strok */ len=0; while(v[len]) len++; shsort(v,len,less); } /* Funkciya sravneniya strok. * Vernut' celoe men'she nulya, esli a < b * nol', esli a == b * bol'she nulya, esli a > b */ less(a,b) obj *a,*b; { return strcoll(a,b); /* strcoll - analog strcmp, * no s uchetom alfavitnogo poryadka bukv. */ } char *strings[] = { "YAsha", "Fedya", "Kolya", "Grisha", "Serezha", "Misha", "Andrej Ivanovich", "Vas'ka", NULL }; int main(){ char **next; setlocale(LC_ALL, ""); ssort( strings ); /* raspechatka */ for( next = strings ; *next ; next++ ) printf( "%s\n", *next ); return 0; } 1.50. Realizujte algoritm bystroj sortirovki. A. Bogatyrev, 1992-95 - 22 - Si v UNIX /* Algoritm bystroj sortirovki. Rabota algoritma "animiruetsya" * (animate-ozhivlyat') pri pomoshchi biblioteki curses. * cc -o qsort qsort.c -lcurses -ltermcap */ #include "curses.h" #define N 10 /* dlina massiva */ /* massiv, podlezhashchij sortirovke */ int target [N] = { 7, 6, 10, 4, 2, 9, 3, 8, 5, 1 }; int maxim; /* maksimal'nyj element massiva */ /* quick sort */ qsort (a, from, to) int a[]; /* sortiruemyj massiv */ int from; /* levyj nachal'nyj indeks */ int to; /* pravyj konechnyj indeks */ { register i, j, x, tmp; if( from >= to ) return; /* chislo elementov <= 1 */ i = from; j = to; x = a[ (i+j) / 2 ]; /* znachenie iz serediny */ do{ /* suzhenie vpravo */ while( a[i] < x ) i++ ; /* suzhenie vlevo */ while( x < a[j] ) j--; if( i <= j ){ /* obmenyat' */ tmp = a[i]; a[i] = a[j] ; a[j] = tmp; i++; j--; demochanges(); /* vizualizaciya */ } } while( i <= j ); /* Teper' obe chasti soshlis' v odnoj tochke. * Dlina levoj chasti = j - from + 1 * pravoj = to - i + 1 * Vse chisla v levoj chasti men'she vseh chisel v pravoj. * Teper' nado prosto otsortirovat' kazhduyu chast' v otdel'nosti. * Snachala sortiruem bolee korotkuyu (dlya ekonomii pamyati * v steke ). Rekursiya: */ if( (j - from) < (to - i) ){ qsort( a, from, j ); qsort( a, i, to ); } else { qsort( a, i, to ); qsort( a, from, j ); } } A. Bogatyrev, 1992-95 - 23 - Si v UNIX int main (){ register i; initscr(); /* zapusk curses-a */ /* poisk maksimal'nogo chisla v massive */ for( maxim = target[0], i = 1 ; i < N ; i++ ) if( target[i] > maxim ) maxim = target[i]; demochanges(); qsort( target, 0, N-1 ); demochanges(); mvcur( -1, -1, LINES-1, 0); /* kursor v levyj nizhnij ugol */ endwin(); /* zavershit' rabotu s curses-om */ return 0; } #define GAPY 2 #define GAPX 20 /* narisovat' kartinku */ demochanges(){ register i, j; int h = LINES - 3 * GAPY - N; int height; erase(); /* zachistit' okno */ attron( A_REVERSE ); /* risuem matricu uporyadochennosti */ for( i=0 ; i < N ; i++ ) for( j = 0; j < N ; j++ ){ move( GAPY + i , GAPX + j * 2 ); addch( target[i] >= target[j] ? '*' : '.' ); addch( ' ' ); /* Risovat' '*' esli elementy * idut v nepravil'nom poryadke. * Vozmozhen variant proverki target[i] > target[j] */ } attroff( A_REVERSE ); /* massiv */ for( i = 0 ; i < N ; i++ ){ move( GAPY + i , 5 ); printw( "%4d", target[i] ); height = (long) h * target[i] / maxim ; for( j = 2 * GAPY + N + (h - height) ; j < LINES - GAPY; j++ ){ move( j, GAPX + i * 2 ); addch( '|' ); } } refresh(); /* proyavit' kartinku */ sleep(1); } A. Bogatyrev, 1992-95 - 24 - Si v UNIX 1.51. Realizujte privedennyj fragment programmy bez ispol'zovaniya operatora goto i bez metok. if ( i > 10 ) goto M1; goto M2; M1: j = j + i; flag = 2; goto M3; M2: j = j - i; flag = 1; M3: ; Zamet'te, chto pomechat' mozhno tol'ko operator (mozhet byt' pustoj); poetomu ne mozhet vstretit'sya fragment { ..... Label: } a tol'ko { ..... Label: ; } 1.52. V kakom sluchae opravdano ispol'zovanie operatora goto? Otvet: pri vyhode iz vlozhennyh ciklov, t.k. operator break pozvolyaet vyjti tol'ko iz samogo vnutrennego cikla (na odin uroven'). 1.53. K kakomu if-u otnositsya else? if(...) ... if(...) ... else ... Otvet: ko vtoromu (k blizhajshemu predshestvuyushchemu, dlya kotorogo net drugogo else). Voobshche zhe luchshe yavno rasstavlyat' skobki (dlya yasnosti): if(...){ ... if(...) ... else ... } if(...){ ... if(...) ... } else ... 1.54. Makroopredelenie, ch'e telo predstavlyaet soboj posledovatel'nost' operatorov v {...} skobkah (blok), mozhet vyzvat' problemy pri ispol'zovanii ego v uslovnom opera- tore if s else-chast'yu: #define MACRO { x=1; y=2; } if(z) MACRO; else .......; My poluchim posle makrorasshireniya if(z) { x=1; y=2; } /* konec if-a */ ; else .......; /* else ni k chemu ne otnositsya */ to est' sintaksicheski oshibochnyj fragment, tak kak dolzhno byt' libo if(...) odin_operator; else ..... libo if(...){ posledovatel'nost'; ...; operatorov; } else ..... gde tochka-s-zapyatoj posle } ne nuzhna. S etim yavleniem boryutsya, oformlyaya blok {...} v vide do{...}while(0) #define MACRO do{ x=1; y=2; }while(0) Telo takogo "cikla" vypolnyaetsya edinstvennyj raz, pri etom my poluchaem pravil'nyj tekst: A. Bogatyrev, 1992-95 - 25 - Si v UNIX if(z) do{ x=1; y=2; }while(0); else .......; 1.55. V chem oshibka (dlya znayushchih yazyk "Paskal'")? int x = 12; if( x < 20 and x > 10 ) printf( "O'K\n"); else if( x > 100 or x < 0 ) printf( "Bad x\n"); else printf( "x=%d\n", x); Napishite #define and && #define or || 1.56. Pochemu programma zaciklivaetsya? My hotim podschitat' chislo probelov i tabulya- cij v nachale stroki: int i = 0; char *s = " 3 spaces"; while(*s == ' ' || *s++ == '\t') printf( "Probel %d\n", ++i); Otvet: logicheskie operacii || i && vypolnyayutsya sleva napravo; kak tol'ko kakoe-to uslovie v || okazyvaetsya istinnym (a v && lozhnym) - dal'nejshie usloviya prosto ne vychislyayutsya. V nashem sluchae uslovie *s==' ' srazu zhe verno, i operaciya s++ iz vtorogo usloviya ne vypolnyaetsya! My dolzhny byli napisat' hotya by tak: while(*s == ' ' || *s == '\t'){ printf( "Probel %d\n", ++i); s++; } S drugoj storony, eto svojstvo || i && cherezvychajno polezno, naprimer: if( x != 0.0 && y/x < 1.0 ) ... ; Esli by my ne vstavili proverku na 0, my mogli by poluchit' delenie na 0. V dannom zhe sluchae pri x==0 delenie prosto ne budet vychislyat'sya. Vot eshche primer: int a[5], i; for(i=0; i < 5 && a[i] != 0; ++i) ...; Esli i vyjdet za granicu massiva, to sravnenie a[i] s nulem uzhe ne budet vychislyat'sya, t.e. popytki prochest' element ne vhodyashchij v massiv ne proizojdet. |to svojstvo && pozvolyaet pisat' dovol'no neochevidnye konstrukcii, vrode if((cond) && f()); chto okazyvaetsya ekvivalentnym if( cond ) f(); Voobshche zhe if(C1 && C2 && C3) DO; ekvivalentno if(C1) if(C2) if(C3) DO; i dlya "ili" A. Bogatyrev, 1992-95 - 26 - Si v UNIX if(C1 || C2 || C3) DO; ekvivalentno if(C1) goto ok; else if(C2) goto ok; else if(C3){ ok: DO; } Vot eshche primer, pol'zuyushchijsya etim svojstvom || #include <stdio.h> main(argc, argv) int argc; char *argv[]; { FILE *fp; if(argc < 2 || (fp=fopen(argv[1], "r")) == NULL){ fprintf(stderr, "Plohoe imya fajla\n"); exit(1); /* zavershit' programmu */ } ... } Esli argc==1, to argv[1] ne opredeleno, odnako v etom sluchae popytki otkryt' fajl s imenem argv[1] prosto ne budet predprinyato! Nizhe priveden eshche odin soderzhatel'nyj primer, predstavlyayushchij soboj odnu iz voz- mozhnyh shem napisaniya "dvuyazychnyh" programm, t.e. vydayushchih soobshcheniya na odnom iz dvuh yazykov po vashemu zhelaniyu. Proveryaetsya peremennaya okruzheniya MSG (ili LANG): YAZYK: 1) "MSG=engl" anglijskij 2) MSG net v okruzhenii anglijskij 3) "MSG=rus" russkij Pro okruzhenie i funkciyu getenv() smotri v glave "Vzaimodejstvie s UNIX", pro strchr() - v glave "Massivy i stroki". #include <stdio.h> int _ediag = 0; /* yazyk diagnostik: 1-russkij */ extern char *getenv(), *strchr(); #define ediag(e,r) (_ediag?(r):(e)) main(){ char *s; _ediag = ((s=getenv("MSG")) != NULL && strchr("rRrR", *s) != NULL); printf(ediag("%d:english\n", "%d:russkij\n"), _ediag); } Esli peremennaya MSG ne opredelena, to s==NULL i funkciya strchr(s,...) ne vyzyvaetsya (ee pervyj frgument ne dolzhen byt' NULL-om). Zdes' ee mozhno bylo by uproshchenno zame- nit' na *s=='r'; togda esli s ravno NULL, to obrashchenie *s bylo by nezakonno (obrashche- nie po ukazatelyu NULL daet nepredskazuemye rezul'taty i, skoree vsego, vyzovet krah programmy). 1.57. Inogda logicheskoe uslovie mozhno sdelat' bolee ponyatnym, ispol'zuya pravila de- Morgana: a && b = ! ( !a || !b ) a || b = ! ( !a && !b ) a takzhe uchityvaya, chto ! !a = a ! (a == b) = (a != b) Naprimer: A. Bogatyrev, 1992-95 - 27 - Si v UNIX if( c != 'a' && c != 'b' && c != 'c' )...; prevrashchaetsya v if( !(c == 'a' || c == 'b' || c == 'c')) ...; 1.58. Primer, v kotorom ispol'zuyutsya pobochnye effekty vychisleniya vyrazhenij. Obychno znachenie vyrazheniya prisvaivaetsya nekotoroj peremennoj, no eto ne neobhodimo. Poetomu mozhno ispol'zovat' svojstva vychisleniya && i || v vyrazheniyah (hotya eto ne est' samyj ponyatnyj sposob napisaniya programm, skoree nekotoryj rod izvrashcheniya). Ogranichenie tut takovo: vse chasti vyrazheniya dolzhny vozvrashchat' znacheniya. #include <stdio.h> extern int errno; /* kod sistemnoj oshibki */ FILE *fp; int openFile(){ errno = 0; fp = fopen("/etc/inittab", "r"); printf("fp=%x\n", fp); return(fp == NULL ? 0 : 1); } int closeFile(){ printf("closeFile\n"); if(fp) fclose(fp); return 0; } int die(int code){ printf("exit(%d)\n", code); exit(code); return 0; } void main(){ char buf[2048]; if( !openFile()) die(errno); closeFile(); openFile() || die(errno); closeFile(); /* esli fajl otkrylsya, to die() ne vychislyaetsya */ openFile() ? 0 : die(errno); closeFile(); if(openFile()) closeFile(); openFile() && closeFile(); /* vychislit' closeFile() tol'ko esli openFile() udalos' */ openFile() && (printf("%s", fgets(buf, sizeof buf, fp)), closeFile()); } V poslednej stroke ispol'zovan operator "zapyataya": (a,b,c) vozvrashchaet znachenie vyra- zheniya c. 1.59. Napishite funkciyu, vychislyayushchuyu summu massiva zadannyh chisel. 1.60. Napishite funkciyu, vychislyayushchuyu srednee znachenie massiva zadannyh chisel. 1.61. CHto budet napechatano v rezul'tate raboty sleduyushchego cikla? for ( i = 36; i > 0; i /= 2 ) printf ( "%d%s", i, i==1 ? ".\n":", "); A. Bogatyrev, 1992-95 - 28 - Si v UNIX Otvet: 36, 18, 9, 4, 2, 1. 1.62. Najdite oshibki v sleduyushchej programme: main { int i, j, k(10); for ( i = 0, i <= 10, i++ ){ k[i] = 2 * i + 3; for ( j = 0, j <= i, j++ ) printf ("%i\n", k[j]); } } Obratite vnimanie na format %i, sushchestvuet li takoj format? Est' li eto tot format, po kotoromu sleduet pechatat' znacheniya tipa int? 1.63. Napishite programmu, kotoraya raspechatyvaet elementy massiva. Napishite prog- rammu, kotoraya raspechatyvaet elementy massiva po 5 chisel v stroke. 1.64. Sostav'te programmu schityvaniya strok simvolov iz standartnogo vvoda i pechati nomera vvedennoj stroki, adresa stroki v pamyati |VM, znacheniya stroki, dliny stroki. 1.65. Stilisticheskoe zamechanie: v operatore return vozvrashchaemoe vyrazhenie ne obyaza- tel'no dolzhno byt' v ()-skobkah. Delo v tom, chto return - ne funkciya, a operator. return vyrazhenie; return (vyrazhenie); Odnako esli vy vyzyvaete funkciyu (naprimer, exit) - to argumenty dolzhny byt' v krug- lyh skobkah: exit(1); no ne exit 1; 1.66. Izbegajte situacii, kogda funkciya v raznyh vetvyah vychisleniya to vozvrashchaet nekotoroe znachenie, to ne vozvrashchaet nichego: int func (int x) { if( x > 10 ) return x*2; if( x == 10 ) return (10); /* a zdes' - neyavnyj return; bez znacheniya */ } pri x < 10 funkciya vernet nepredskazuemoe znachenie! Mnogie kompilyatory raspoznayut takie situacii i vydayut preduprezhdenie. 1.67. Napishite programmu, zaprashivayushchuyu vashe imya i "privetstvuyushchuyu" vas. Napishite funkciyu chteniya stroki. Ispol'zujte getchar() i printf(). Otvet: #include <stdio.h> /* standard input/output */ main(){ char buffer[81]; int i; printf( "Vvedite vashe imya:" ); while((i = getstr( buffer, sizeof buffer )) != EOF){ printf( "Zdravstvuj, %s\n", buffer ); printf( "Vvedite vashe imya:" ); } } getstr( s, maxlen ) char *s; /* kuda pomestit' stroku */ int maxlen; /* dlina bufera: A. Bogatyrev, 1992-95 - 29 - Si v UNIX maks. dlina stroki = maxlen-1 */ { int c; /* ne char! (pochemu ?) */ register int i = 0; maxlen--; /* rezerviruem bajt pod konechnyj '\0' */ while(i < maxlen && (c = getchar()) != '\n' && c != EOF ) s[i++] = c; /* obratite vnimanie, chto sam simvol '\n' * v stroku ne popadet */ s[i] = '\0'; /* priznak konca stroki */ return (i == 0 && c == EOF) ? EOF : i; /* vernem dlinu stroki */ } Vot eshche odin variant funkcii chteniya stroki: v nashem primere ee sleduet vyzyvat' kak fgetstr(buffer,sizeof(buffer),stdin); |to podpravlennyj variant standartnoj funkcii fgets (v nej stroki @1 i @2 obmenyany mestami). char *fgetstr(char *s, int maxlen, register FILE *fp){ register c; register char *cs = s; while(--maxlen > 0 && (c = getc(fp)) != EOF){ if(c == '\n') break; /* @1 */ *cs++ = c; /* @2 */ } if(c == EOF && cs == s) return NULL; /* Zamet'te, chto pri EOF stroka s ne menyaetsya! */ *cs = '\0'; return s; } Issledujte povedenie etih funkcij, kogda vhodnaya stroka slishkom dlinnaya (dlinnee max- len). Zamechanie: vmesto nashej "rukopisnoj" funkcii getstr() my mogli by ispol'zovat' standartnuyu bibliotechnuyu funkciyu gets(buffer). 1.68. Ob®yasnite, pochemu d stalo otricatel'nym i pochemu %X pechataet bol'she F, chem v ishodnom chisle? Primer vypolnyalsya na 32-h bitnoj mashine. main(){ unsigned short u = 65535; /* 16 bit: 0xFFFF */ short d = u; /* 15 bit + znakovyj bit */ printf( "%X %d\n", d, d); /* FFFFFFFF -1 */ } Ukazanie: rassmotrite dvoichnoe predstavlenie chisel (smotri prilozhenie). Kakie prive- deniya tipov zdes' proishodyat? 1.69. Pochemu 128 prevratilos' v otricatel'noe chislo? main() { /*signed*/ char c = 128; /* bity: 10000000 */ unsigned char uc = 128; int d = c; /* ispol'zuetsya 32-h bitnyj int */ printf( "%d %d %x\n", c, d, d ); /* -128 -128 ffffff80 */ d = uc; printf( "%d %d %x\n", uc, d, d ); /* 128 128 80 */ } A. Bogatyrev, 1992-95 - 30 - Si v UNIX Otvet: pri privedenii char k int rasshirilsya znakovyj bit (7-oj), zanyav vsyu starshuyu chast' slova. Znakovyj bit int-a stal raven 1, chto yavlyaetsya priznakom otricatel'nogo chisla. To zhe budet proishodit' so vsemi znacheniyami c iz diapazona 128..255 (soderzha- shchimi bit 0200). Pri privedenii unsigned char k int znakovyj bit ne rasshiryaetsya. Mozhno bylo postupit' eshche i tak: printf( "%d\n", c & 0377 ); Zdes' c privoditsya k tipu int (potomu chto pri ispol'zovanii v argumentah funkcii tip char VSEGDA privoditsya k tipu int), zatem &0377 zanulit starshij bajt poluchennogo celogo chisla (sostoyashchij iz bitov 1), snova prevrativ chislo v polozhitel'noe. 1.70. Pochemu printf("%d\n", '\377' == 0377 ); printf("%d\n", '\xFF' == 0xFF ); pechataet 0 (lozh')? Otvet: po toj zhe prichine, po kotoroj printf("%d %d\n", '\377', 0377); pechataet -1 255, a imenno: char '\377' privoditsya v vyrazheniyah k celomu rasshireniem znakovogo bita (a 0377 - uzhe celoe). 1.71. Rassmotrim programmu #include <stdio.h> int main(int ac, char **av){ int c; while((c = getchar()) != EOF) switch(c){ case 'y': printf("Bukva y\n"); break; case 'j': printf("Bukva j\n"); break; default: printf("Bukva s kodom %d\n", c); break; } return 0; } Ona rabotaet tak: % a.out jfyv Bukva s kodom 202 Bukva s kodom 198 Bukva s kodom 217 Bukva s kodom 215 Bukva s kodom 10 ^D % Vypolnyaetsya vsegda default, pochemu ne vypolnyayutsya case 'y' i case 'j'? Otvet: russkie bukvy imeyut vos'moj bit (levyj) ravnyj 1. V case takoj bajt pri- voditsya k tipu int rasshireniem znakovogo bita. V itoge poluchaetsya otricatel'noe chislo. Primer: void main(void){ int c = 'j'; printf("%d\n", c); } pechataet -54 A. Bogatyrev, 1992-95 - 31 - Si v UNIX Resheniem sluzhit podavlenie rasshireniya znakovogo bita: #include <stdio.h> /* Odno iz dvuh */ #define U(c) ((c) & 0xFF) #define UC(c) ((unsigned char) (c)) int main(int ac, char **av){ int c; while((c = getchar()) != EOF) switch(c){ case U('y'): printf("Bukva y\n"); break; case UC('j'): printf("Bukva j\n"); break; default: printf("Bukva s kodom %d\n", c); break; } return 0; } Ona rabotaet pravil'no: % a.out jfyv Bukva j Bukva s kodom 198 Bukva y Bukva s kodom 215 Bukva s kodom 10 ^D % Vozmozhno takzhe ispol'zovanie kodov bukv: case 0312: no eto gorazdo menee naglyadno. Podavlenie znakovogo bita neobhodimo takzhe i v opera- torah if: int c; ... if(c == 'j') ... sleduet zamenit' na if(c == UC('j')) ... Sleva zdes' - signed int, pravuyu chast' kompilyator tozhe privodit k signed int. Priho- ditsya yavno govorit', chto sprava - unsigned. 1.72. Rassmotrim programmu, kotoraya dolzhna napechatat' chisla ot 0 do 255. Dlya etih chisel v kachestve schetchika dostatochen odin bajt: int main(int ac, char *av[]){ unsigned char ch; for(ch=0; ch < 256; ch++) printf("%d\n", ch); return 0; } Odnako eta programma zaciklivaetsya, poskol'ku v moment, kogda ch==255, eto znachenie men'she 256. Sleduyushchim shagom vypolnyaetsya ch++, i ch stanovitsya ravno 0, ibo dlya char A. Bogatyrev, 1992-95 - 32 - Si v UNIX vychisleniya vedutsya po modulyu 256 (2 v 8 stepeni). To est' v dannom sluchae 255+1=0 Reshenij sushchestvuet dva: pervoe - prevratit' unsigned char v int. Vtoroe - vsta- vit' yavnuyu proverku na poslednee znachenie diapazona. int main(int ac, char *av[]){ unsigned char ch; for(ch=0; ; ch++){ printf("%d\n", ch); if(ch == 255) break; } return 0; } 1.73. Podumajte, pochemu dlya unsigned a, b, c; a < b + c ne ekvivalentno a - b < c (pervoe - bolee korrektno). Namek v vide primera (on vypolnyalsya na 32-bitnoj mashine): a = 1; b = 3; c = 2; printf( "%u\n", a - b ); /* 4294967294, hotya v normal'noj arifmetike 1 - 3 = -2 */ printf( "%d\n", a < b + c ); /* 1 */ printf( "%d\n", a - b < c ); /* 0 */ Mogut li unsigned chisla byt' otricatel'nymi? 1.74. Dan tekst: short x = 40000; printf("%d\n", x); Pechataetsya -25536. Ob®yasnite effekt. Ukazanie: kakovo naibol'shee predstavimoe korot- koe celoe (16 bitnoe)? CHto na samom dele okazalos' v x? (lishnie sleva bity - obruba- yutsya). 1.75. Pochemu v primere double x = 5 / 2; printf( "%g\n", x ); znachenie x ravno 2 a ne 2.5 ? Otvet: proizvoditsya celochislennoe delenie, zatem v prisvaivanii celoe chislo 2 privoditsya k tipu double. CHtoby poluchilsya otvet 2.5, nado pisat' odnim iz sleduyushchih sposobov: double x = 5.0 / 2; x = 5 / 2.0; x = (double) 5 / 2; x = 5 / (double) 2; x = 5.0 / 2.0; to est' v vyrazhenii dolzhen byt' hot' odin operand tipa double. Ob®yasnite, pochemu sleduyushchie tri operatora vydayut takie znacheniya: A. Bogatyrev, 1992-95 - 33 - Si v UNIX double g = 9.0; int t = 3; double dist = g * t * t / 2; /* 40.5 */ dist = g * (t * t / 2); /* 36.0 */ dist = g * (t * t / 2.0); /* 40.5 */ V kakih sluchayah delenie celochislennoe, v kakih - veshchestvennoe? Pochemu? 1.76. Stranslirujte primer na mashine s dlinoj slova int ravnoj 16 bit: long n = 1024 * 1024; long nn = 512 * 512; printf( "%ld %ld\n", n, nn ); Pochemu pechataetsya 0 0 a ne 1048576 262144? Otvet: rezul'tat umnozheniya (2**20 i 2**18) - eto celoe chislo; odnako ono slishkom veliko dlya sohraneniya v 16 bitah, poetomu starshie bity obrubayutsya. Poluchaetsya 0. Zatem v prisvaivanii eto uzhe obrublennoe znachenie privoditsya k tipu long (32 bita) - eto vse ravno budet 0. CHtoby poluchit' korrektnyj rezul'tat, nado chtoby vyrazhenie sprava ot = uzhe imelo tip long i srazu sohranyalos' v 32 bitah. Dlya etogo ono dolzhno imet' hot' odin operand tipa long: long n = (long) 1024 * 1024; long nn = 512 * 512L; 1.77. Najdite oshibku v operatore: x - = 4; /* vychest' iz x chislo 4 */ Otvet: mezhdu `-' i `=' ne dolzhno byt' probela. Operaciya vida x @= expr; oznachaet x = x @ expr; (gde @ - odna iz operacij + - * / % ^ >> << & |), prichem x zdes' vychislyaetsya edinst- vennyj raz (t.e. takaya forma ne tol'ko koroche i ponyatnee, no i ekonomichnee). Odnako imeetsya tonkoe otlichie a=a+n ot a+=n; ono zaklyuchaetsya v tom, skol'ko raz vychislyaetsya a. V sluchae a+=n edinozhdy; v sluchae a=a+n dva raza. A. Bogatyrev, 1992-95 - 34 - Si v UNIX #include <stdio.h> static int x = 0; int *iaddr(char *msg){ printf("iaddr(%s) for x=%d evaluated\n", msg, x); return &x; } int main(){ static int a[4]; int *p, i; printf( "1: "); x = 0; (*iaddr("a"))++; printf( "2: "); x = 0; *iaddr("b") += 1; printf( "3: "); x = 0; *iaddr("c") = *iaddr("d") + 1; for(i=0, p = a; i < sizeof(a)/sizeof(*a); i++) a[i] = 0; *p++ += 1; for(i=0; i < sizeof(a)/sizeof(*a); i++) printf("a[%d]=%d ", i, a[i]); printf("offset=%d\n", p - a); for(i=0, p = a; i < sizeof(a)/sizeof(*a); i++) a[i] = 0; *p++ = *p++ + 1; for(i=0; i < sizeof(a)/sizeof(*a); i++) printf("a[%d]=%d ", i, a[i]); printf("offset=%d\n", p - a); return 0; } Vydacha: 1: iaddr(a) for x=0 evaluated 2: iaddr(b) for x=0 evaluated 3: iaddr(d) for x=0 evaluated iaddr(c) for x=0 evaluated a[0]=1 a[1]=0 a[2]=0 a[3]=0 offset=1 a[0]=1 a[1]=0 a[2]=0 a[3]=0 offset=2 Zamet'te takzhe, chto a[i++] += z; eto a[i] = a[i] + z; i++; a vovse ne a[i++] = a[i++] + z; 1.78. Operaciya y = ++x; ekvivalentna y = (x = x+1, x); a operaciya y = x++; ekvivalentna y = (tmp = x, x = x+1, tmp); ili y = (x += 1) - 1; gde tmp - vremennaya psevdoperemennaya togo zhe tipa, chto i x. Operaciya `,' vydaet A. Bogatyrev, 1992-95 - 35 - Si v UNIX znachenie poslednego vyrazheniya iz perechislennyh (podrobnee sm. nizhe). Pust' x=1. Kakie znacheniya budut prisvoeny x i y posle vypolneniya operatora y = ++x + ++x + ++x; 1.79. Pust' i=4. Kakie znacheniya budut prisvoeny x i i posle vypolneniya operatora x = --i + --i + --i; 1.80. Pust' x=1. Kakie znacheniya budut prisvoeny x i y posle vypolneniya operatora y = x++ + x++ + x++; 1.81. Pust' i=4. Kakie znacheniya budut prisvoeny i i y posle vypolneniya operatora y = i-- + i-- + i--; 1.82. Korrektny li operatory char *p = "Jabberwocky"; char s[] = "0123456789?"; int i = 0; s[i] = p[i++]; ili *p = *++p; ili s[i] = i++; ili dazhe *p++ = f( *p ); Otvet: net, standart ne predusmatrivaet, kakaya iz chastej prisvaivaniya vychislyaetsya pervoj: levaya ili pravaya. Poetomu vse mozhet rabotat' tak, kak my i podrazumevali, no mozhet i inache! Kakoe i ispol'zuetsya v s[i]: 0 ili uzhe 1 (++ uzhe sdelan ili net), to est' int i = 0; s[i] = i++; eto s[0] = 0; ili zhe s[1] = 0; ? Kakoe p budet ispol'zovano v levoj chasti *p: uzhe prodvinutoe ili staroe? Eshche bolee eta ideya dramatizirovana v s[i++] = p[i++]; Zametim eshche, chto v int i=0, j=0; s[i++] = p[j++]; takoj problemy ne voznikaet, poskol'ku indeksy oboih v chastyah prisvaivaniya nezavi- simy. Zato analogichnaya problema vstaet v if( a[i++] < b[i] )...; Poryadok vychisleniya operandov ne opredelen, poetomu neyasno, chto budet sdelano prezhde: vzyato znachenie b[i] ili znachenie a[i++] (togda budet vzyato b[i+1] ). Nado pisat' tak, chtoby ne polagat'sya na osobennosti vashego kompilyatora: if( a[i] < b[i+1] )...; ili *p = *(p+1); i++; ++p; A. Bogatyrev, 1992-95 - 36 - Si v UNIX Tverdo usvojte, chto i++ i ++i ne tol'ko vydayut znacheniya i i i+1 sootvetstvenno, no i izmenyayut znachenie i. Poetomu eti operatory NE NADO ispol'zovat' tam, gde po smyslu trebuetsya i+1, a ne i=i+1. Tak dlya sravneniya sosednih elementov massiva if( a[i] < a[i+1] ) ... ; /* verno */ if( a[i] < a[++i] ) ... ; /* neverno */ 1.83. Poryadok vychisleniya operandov v binarnyh vyrazheniyah ne opredelen (chto ran'she vychislyaetsya - levyj operand ili zhe pravyj ?). Tak primer int f(x,s) int x; char *s; { printf( "%s:%d ", s, x ); return x; } main(){ int x = 1; int y = f(x++, "f1") + f(x+=2, "f2"); printf("%d\n", y); } mozhet pechatat' libo f1:1 f2:4 5 libo f2:3 f1:3 6 v zavisimosti ot osobennostej povedeniya vashego kompilyatora (kakaya iz dvuh f() vypol- nitsya pervoj: levaya ili pravaya?). Eshche primer: int y = 2; int x = ((y = 4) * y ); printf( "%d\n", x ); Mozhet byt' napechatano libo 16, libo 8 v zavisimosti ot povedeniya kompilyatora, t.e. dannyj operator nemobilen. Sleduet napisat' y = 4; x = y * y; 1.84. Zakonen li operator f(x++, x++); ili f(x, x++); Otvet: Net, poryadok vychisleniya argumentov funkcij ne opredelen. Po toj zhe prichine my ne mozhem pisat' f( c = getchar(), c ); a dolzhny pisat' c = getchar(); f(c, c); (esli my imenno eto imeli v vidu). Vot eshche primer: ... case '+': push(pop()+pop()); break; case '-': push(pop()-pop()); break; ... A. Bogatyrev, 1992-95 - 37 - Si v UNIX sleduet zamenit' na ... case '+': push(pop()+pop()); break; case '-': { int x = pop(); int y = pop(); push(y - x); break; } ... I eshche primer: int x = 0; printf( "%d %d\n", x = 2, x ); /* 2 0 libo 2 2 */ Nel'zya takzhe struct pnt{ int x; int y; }arr[20]; int i=0; ... scanf( "%d%d", & arr[i].x, & arr[i++].y ); poskol'ku i++ mozhet sdelat'sya ran'she, chem chtenie v x. Eshche primer: main(){ int i = 3; printf( "%d %d %d\n", i += 7, i++, i++ ); } kotoryj pokazyvaet, chto na IBM PC |- i PDP-11 |= argumenty funkcij vychislyayutsya sprava nalevo (primer pechataet 12 4 3). Vprochem, drugie kompilyatory mogut vychislyat' ih sleva napravo (kak i podskazyvaet nam zdravyj smysl). 1.85. Programma pechataet libo x=1 libo x=0 v zavisimosti ot KOMPILYATORA - vychislya- etsya li ran'she pravaya ili levaya chast' operatora vychitaniya: #include <stdio.h> void main(){ int c = 1; int x = c - c++; printf( "x=%d c=%d\n", x, c ); exit(0); } CHto vy imeli v vidu ? left = c; right = c++; x = left - right; ili right = c++; left = c; x = left - right; A esli kompilyator eshche i rasparallelit vychislenie left i right - to odna programma v raznye momenty vremeni smozhet davat' raznye rezul'taty. ____________________ |- IBM ("Aj-bi-em") - International Buisiness Machines Corporation. Personal'nye komp'yutery IBM PC postroeny na baze mikroprocessorov firmy Intel. |= PDP-11 - (Programmed Data Processor) - komp'yuter firmy DEC (Digital Equipment Corporation), u nas izvestnyj kak SM-1420. |ta zhe firma vypuskaet mashinu VAX. A. Bogatyrev, 1992-95 - 38 - Si v UNIX Vot eshche dostojnaya zadachka: x = c-- - --c; /* c-----c */ 1.86. Napishite programmu, kotoraya ustanavlivaet v 1 bit 3 i sbrasyvaet v 0 bit 6. Bity v slove numeruyutsya s nulya sprava nalevo. Otvet: int x = 0xF0; x |= (1 << 3); x &= ~(1 << 6); V programmah chasto ispol'zuyut bitovye maski kak flagi nekotoryh parametrov (priznak - est' ili net). Naprimer: #define A 0x08 /* vhod svoboden */ #define B 0x40 /* vyhod svoboden */ ustanovka flagov : x |= A|B; sbros flagov : x &= ~(A|B); proverka flaga A : if( x & A ) ...; proverka, chto oba flaga est': if((x & (A|B)) == (A|B))...; proverka, chto oboih net : if((x & (A|B)) == 0 )...; proverka, chto est' hot' odin: if( x & (A|B))...; proverka, chto est' tol'ko A : if((x & (A|B)) == A)...; proverka, v kakih flagah razlichayutsya x i y : diff = x ^ y; 1.87. V programmah inogda trebuetsya ispol'zovat' "mnozhestvo": kazhdyj dopustimyj ele- ment mnozhestva imeet nomer i mozhet libo prisutstvovat' v mnozhestve, libo otsutstvo- vat'. CHislo vhozhdenij ne uchityvaetsya. Mnozhestva prinyato modelirovat' pri pomoshchi bitovyh shkal: #define SET(n,a) (a[(n)/BITS] |= (1L <<((n)%BITS))) #define CLR(n,a) (a[(n)/BITS] &= ~(1L <<((n)%BITS))) #define ISSET(n,a) (a[(n)/BITS] & (1L <<((n)%BITS))) #define BITS 8 /* bits per char (bitov v bajte) */ /* Perechislimyj tip */ enum fruit { APPLE, PEAR, ORANGE=113, GRAPES, RAPE=125, CHERRY}; /* shkala: n iz intervala 0..(25*BITS)-1 */ static char fr[25]; main(){ SET(GRAPES, fr); /* dobavit' v mnozhestvo */ if(ISSET(GRAPES, fr)) printf("here\n"); CLR(GRAPES, fr); /* udalit' iz mnozhestva */ } 1.88. Napishite programmu, raspechatyvayushchuyu vse vozmozhnye perestanovki massiva iz N elementov. Algoritm budet rekursivnym, naprimer takim: v kachestve pervogo elementa perestanovki vzyat' i-yj element massiva. Iz ostavshihsya elementov massiva (esli takie est') sostavit' vse perestanovki poryadka N-1. Vydat' vse perestanovki poryadka N, poluchayushchiesya sklejkoj i-ogo elementa i vseh (po ocheredi) perestanovok poryadka N-1. Vzyat' sleduyushchee i i vse povtorit'. Glavnaya problema zdes' - organizovat' ostavshiesya posle izvlecheniya i-ogo elementa elementy massiva v udobnuyu strukturu dannyh (chtoby postoyanno ne kopirovat' massiv). Mozhno ispol'zovat', naprimer, bitovuyu shkalu uzhe vybrannyh elementov. Vospol'zuemsya dlya etogo makrosami iz predydushchego paragrafa: A. Bogatyrev, 1992-95 - 39 - Si v UNIX /* GENERATOR PERESTANOVOK IZ n |LEMENTOV PO m */ extern void *calloc(unsigned nelem, unsigned elsize); /* Dinamicheskij vydelitel' pamyati, zachishchennoj nulyami. * |to standartnaya bibliotechnaya funkciya. * Obratnaya k nej - free(); */ extern void free(char *ptr); static int N, M, number; static char *scale; /* shkala vybrannyh elementov */ int *res; /* rezul'tat */ /* ... tekst opredelenij SET, CLR, ISSET, BITS ... */ static void choose(int ind){ if(ind == M){ /* raspechatat' perestanovku */ register i; printf("Rasstanovka #%04d", ++number); for(i=0; i < M; i++) printf(" %2d", res[i]); putchar('\n'); return; } else /* Vybrat' ocherednoj ind-tyj element perestanovki * iz chisla eshche ne vybrannyh elementov. */ for(res[ind] = 0; res[ind] < N; ++res[ind]) if( !ISSET(res[ind], scale)) { /* element eshche ne byl vybran */ SET(res[ind], scale); /* vybrat' */ choose(ind+1); CLR(res[ind], scale); /* osvobodit' */ } } void arrange(int n, int m){ res = (int *) calloc(m, sizeof(int)); scale = (char *) calloc((n+BITS-1)/BITS, 1); M = m; N = n; number = 0; if( N >= M ) choose(0); free((char *) res); free((char *) scale); } void main(int ac, char **av){ if(ac != 3){ printf("Arg count\n"); exit(1); } arrange(atoi(av[1]), atoi(av[2])); } Programma dolzhna vydat' n!/(n-m)! rasstanovok, gde x! = 1*2*...*x - funkciya "fakto- rial". Po opredeleniyu 0! = 1. Poprobujte peredelat' etu programmu tak, chtoby oche- rednaya perestanovka pechatalas' po zaprosu: res = init_iterator(n, m); /* pechatat' varianty, poka oni est' */ while( next_arrangement (res)) print_arrangement(res, m); clean_iterator(res); 1.89. Napishite makroopredeleniya ciklicheskogo sdviga peremennoj tipa unsigned int na skew bit vlevo i vpravo (ROL i ROR). Otvet: #define BITS 16 /* pust' celoe sostoit iz 16 bit */ #define ROL(x,skew) x=(x<<(skew))|(x>>(BITS-(skew))) #define ROR(x,skew) x=(x>>(skew))|(x<<(BITS-(skew))) A. Bogatyrev, 1992-95 - 40 - Si v UNIX Vot kak rabotaet ROL(x, 2) pri BITS=6 |abcdef| ishodno abcdef00 << 2 0000abcdef >> 4 ------ operaciya | cdefab rezul'tat V sluchae signed int potrebuetsya nakladyvat' masku pri sdvige vpravo iz-za togo, chto levye bity pri >> ne zapolnyayutsya nulyami. Privedem primer dlya sdviga peremennoj tipa signed char (po umolchaniyu vse char - znakovye) na 1 bit vlevo: #define CHARBITS 8 #define ROLCHAR1(x) x=(x<<1)|((x>>(CHARBITS-1)) & 01) sootvetstvenno dlya sdviga na 2 bita nado delat' & 03 na 3 & 07 na 4 & 017 na skew & ~(~0 << skew) 1.90. Napishite programmu, kotoraya invertiruet (t.e. zamenyaet 1 na 0 i naoborot) N bitov, nachinayushchihsya s pozicii P, ostavlyaya drugie bity bez izmeneniya. Vozmozhnyj otvet: unsigned x, mask; mask = ~(~0 << N) << P; x = (x & ~mask) | (~x & mask); /* xnew */ Gde maska poluchaetsya tak: ~0 = 11111....11111 ~0 << N = 11111....11000 /* N nulej */ ~(~0 << N) = 00000....00111 /* N edinic */ ~(~0 << N) << P = 0...01110...00 /* N edinic na mestah P+N-1..P */ 1.91. Operacii umnozheniya * i deleniya / i % obychno dostatochno medlenny. V kritichnyh po skorosti funkciyah mozhno predprinyat' nekotorye ruchnye optimizacii, svyazannye s predstavleniem chisel v dvoichnom kode (horoshij kompilyator delaet eto sam!) - pol'zuyas' tem, chto operacii +, &, >> i << gorazdo bystree. Pust' u nas est' unsigned int x; (dlya signed operaciya >> mozhet ne zapolnyat' osvobozhdayushchiesya levye bity nulem!) i 2**n oznachaet 2 v stepeni n. Togda: x * (2**n) = x << n x / (2**n) = x >> n x % (2**n) = x - ((x >> n) << n) x % (2**n) = x & (2**n - 1) eto 11...111 n dvoichnyh edinic Naprimer: A. Bogatyrev, 1992-95 - 41 - Si v UNIX x * 8 = x << 3; x / 8 = x >> 3; /* delenie nacelo */ x % 8 = x & 7; /* ostatok ot deleniya */ x * 80 = x*64 + x*16 = (x << 6) + (x << 4); x * 320 = (x * 80) * 4 = (x * 80) << 2 = (x << 8) + (x << 6); x * 21 = (x << 4) + (x << 2) + x; x & 1 = x % 2 = chetnoe(x)? 0:1 = nechetnoe(x)? 1:0; x & (-2) = x & 0xFFFE = | esli x = 2*k to 2*k | esli x = 2*k + 1 to 2*k | to est' okruglyaet do chetnogo Ili formula dlya vychisleniya kolichestva dnej v godu (visokosnyj/prostoj): days_in_year = (year % 4 == 0) ? 366 : 365; zamenyaem na days_in_year = ((year & 0x03) == 0) ? 366 : 365; Vot eshche odno poleznoe ravenstvo: x = x & (a|~a) = (x & a) | (x & ~a) = (x&a) + (x&~a) iz chego vytekaet, naprimer x - (x % 2**n) = x - (x & (2**n - 1)) = = x & ~(2**n - 1) = (x>>n) << n x - (x%8) = x-(x&7) = x & ~7 Poslednyaya stroka mozhet byt' ispol'zovana v funkcii untab() v glave "Tekstovaya obra- botka". 1.92. Obychno my vychislyaem min(a,b) tak: #define min(a, b) (((a) < (b)) ? (a) : (b)) ili bolee razvernuto if(a < b) min = a; else min = b; Zdes' est' operaciya sravneniya i uslovnyj perehod. Odnako, esli (a < b) ekvivalentno usloviyu (a - b) < 0, to my mozhem izbezhat' sravneniya. |to predpolozhenie verno pri (unsigned int)(a - b) <= 0x7fffffff. chto, naprimer, verno esli a i b - oba neotricatel'nye chisla mezhdu 0 i 0x7fffffff. Pri etih usloviyah min(a, b) = b + ((a - b) & ((a - b) >> 31)); Kak eto rabotaet? Rassmotrim dva sluchaya: A. Bogatyrev, 1992-95 - 42 - Si v UNIX Sluchaj 1: a < b Zdes' (a - b) < 0, poetomu starshij (levyj, znakovyj) bit raznosti (a - b) raven 1. Sledovatel'no, (a - b) >> 31 == 0xffffffff, i my imeem: min(a, b) = b + ((a - b) & ((a - b) >> 31)) = b + ((a - b) & (0xffffffff)) = b + (a - b) = a chto korrektno. Sluchaj 2: a >= b Zdes' (a - b) >= 0, poetomu starshij bit raznosti (a - b) raven 0. Togda (a - b) >> 31 == 0, i my imeem: min(a, b) = b + ((a - b) & ((a - b) >> 31)) = b + ((a - b) & (0x00000000)) = b + (0) = b chto takzhe korrektno. Stat'ya predostavlena by Jeff Bonwick. 1.93. Est' li bystryj sposob opredelit', yavlyaetsya li X stepen'yu dvojki? Da, est'. int X yavlyaetsya stepen'yu dvojki togda i tol'ko togda, kogda (X & (X - 1)) == 0 (v chastnosti 2 zdes' okazhetsya stepen'yu dvojki). Kak eto rabotaet? Pust' X != 0. Esli X - celoe, to ego dvoichnoe predstavlenie takovo: X = bbbbbbbbbb10000... gde 'bbb' predstavlyaet nekie bity, '1' - mladshij bit, i vse ostal'nye bity pravee - nuli. Poetomu: X = bbbbbbbbbb10000... X - 1 = bbbbbbbbbb01111... ------------------------------------ X & (X - 1) = bbbbbbbbbb00000... Drugimi slovami, X & (X-1) imeet effekt obnuleniya poslednego edinichnogo bita. Esli X - stepen' dvojki, to on soderzhit v dvoichnom predstavlenii rovno ODIN takoj bit, poe- tomu ego gashenie obrashchaet rezul'tat v nol'. Esli X - ne stepen' dvojki, to v slove est' hotya by DVA edinichnyh bita, poetomu X & (X-1) dolzhno soderzhat' hotya by odin iz ostavshihsya edinichnyh bitov - to est' ne ravnyat'sya nulyu. Sledstviem etogo sluzhit programma, vychislyayushchaya chislo edinichnyh bitov v slove X: int popc; for (popc = 0; X != 0; X &= X - 1) popc++; Pri etom potrebuetsya ne 32 iteracii (chislo bit v int), a rovno stol'ko, skol'ko edi- nichnyh bitov est' v X. Stat'ya predostavlena by Jeff Bonwick. A. Bogatyrev, 1992-95 - 43 - Si v UNIX 1.94. Funkciya dlya poiska nomera pozicii starshego edinichnogo bita v slove. Ispol'zu- etsya binarnyj poisk: poziciya nahoditsya maksimum za 5 iteracij (dvoichnyj logarifm 32h), vmesto 32 pri linejnom poiske. int highbit (unsigned int x) { int i; int h = 0; for (i = 16; i >= 1; i >>= 1) { if (x >> i) { h += i; x >>= i; } } return (h); } Stat'ya predostavlena by Jeff Bonwick. 1.95. Napishite funkciyu, okruglyayushchuyu svoj argument vniz do stepeni dvojki. #include <stdio.h> #define INT short #define INFINITY (-999) /* Funkciya, vydayushchaya chislo, yavlyayushcheesya okrugleniem vniz * do stepeni dvojki. * Naprimer: * 0000100010111000110 * zamenyaetsya na * 0000100000000000000 * to est' ostaetsya tol'ko starshij bit. * V parametr power2 vozvrashchaetsya nomer bita, * to est' pokazatel' stepeni dvojki. Esli chislo == 0, * to eta stepen' ravna minus beskonechnosti. */ A. Bogatyrev, 1992-95 - 44 - Si v UNIX unsigned INT round2(unsigned INT x, int *power2){ /* unsigned - chtoby chislo rassmatrivalos' kak * bitovaya shkala, a sdvig >> zapolnyal levye bity * nulem, a ne rasshiryal vpravo znakovyj bit. * Ideya funkcii: sdvigat' chislo >> poka ne poluchitsya 1 * (mozhno bylo by vybrat' 0). * Zatem sdvinut' << na stol'ko zhe razryadov, pri etom vse pravye * razryady zapolnyatsya nulem, chto i trebovalos'. */ int n = 0; if(x == 0){ *power2 = -INFINITY; return 0; } if(x == 1){ *power2 = 0; return 1; } while(x != 1){ x >>= 1; n++; if(x == 0 || x == (unsigned INT)(-1)){ printf("Vizhu %x: pohozhe, chto >> rasshiryaet znakovyj bit.\n" "Zaciklilis'!!!\n", x); return (-1); } } x <<= n; *power2 = n; return x; } int counter[ sizeof(unsigned INT) * 8]; int main(void){ unsigned INT i; int n2; for(i=0; ; i++){ round2(i, &n2); if(n2 == -INFINITY) continue; counter[n2]++; /* Nel'zya pisat' for(i=0; i < (unsigned INT)(-1); i++) * potomu chto takoj cikl beskonechen! */ if(i == (unsigned INT) (-1)) break; } for(i=0; i < sizeof counter/sizeof counter[0]; i++) printf("counter[%u]=%d\n", i, counter[i]); return 0; } 1.96. Esli nekotoraya vychislitel'naya funkciya budet vyzyvat'sya mnogo raz, ne sleduet prenebregat' vozmozhnost'yu postroit' tablicu reshenij, gde znachenie vychislyaetsya odin raz dlya kazhdogo vhodnogo znacheniya, zato potom beretsya neposredstvenno iz tablicy i ne vychislyaetsya voobshche. Primer: podschet chisla edinichnyh bit v bajte. Napominayu: bajt sostoit iz 8 bit. A. Bogatyrev, 1992-95 - 45 - Si v UNIX #include <stdio.h> int nbits_table[256]; int countBits(unsigned char c){ int nbits = 0; int bit; for(bit = 0; bit < 8; bit++){ if(c & (1 << bit)) nbits++; } return nbits; } void generateTable(){ int c; for(c=0; c < 256; c++){ nbits_table[ (unsigned char) c ] = countBits(c); /* printf("%u=%d\n", c, nbits_table[ c & 0377 ]); */ } } int main(void){ int c; unsigned long bits = 0L; unsigned long bytes = 0L; generateTable(); while((c = getchar()) != EOF){ bytes++; bits += nbits_table[ (unsigned char) c ]; } printf("%lu bajt\n", bytes); printf("%lu edinichnyh bit\n", bits); printf("%lu nulevyh bit\n", bytes*8 - bits); return 0; } 1.97. Napishite makros swap(x, y), obmenivayushchij znacheniyami dva svoih argumenta tipa int. #define swap(x,y) {int tmp=(x);(x)=(y);(y)=tmp;} ... swap(A, B); ... Kak mozhno obojtis' bez vremennoj peremennoj? Vvidu nekotoroj kur'eznosti poslednego sposoba, privodim otvet: int x, y; /* A B */ x = x ^ y; /* A^B B */ y = x ^ y; /* A^B A */ x = x ^ y; /* B A */ Zdes' ispol'zuetsya tot fakt, chto A^A daet 0. 1.98. Napishite funkciyu swap(x, y) pri pomoshchi ukazatelej. Zamet'te, chto v otlichie ot makrosa ee pridetsya vyzyvat' kak A. Bogatyrev, 1992-95 - 46 - Si v UNIX ... swap(&A, &B); ... Pochemu? 1.99. Primer ob®yasnyaet raznicu mezhdu formal'nym i fakticheskim parametrom. Termin "formal'nyj" oznachaet, chto imya parametra mozhno proizvol'no zamenit' drugim (vo vsem tele funkcii), t.e. samo imya ne sushchestvenno. Tak f(x,y) { return(x + y); } i f(muzh,zhena) { return(muzh + zhena); } voploshchayut odnu i tu zhe funkciyu. "Fakticheskij" - oznachaet znachenie, davaemoe para- metru v moment vyzova funkcii: f(xyz, 43+1); V Si eto oznachaet, chto formal'nym parametram (v kachestve lokal'nyh peremennyh) pris- vaivayutsya nachal'nye znacheniya, ravnye znacheniyam fakticheskih parametrov: x = xyz; y = 43 + 1; /*v tele f-cii ih mozhno menyat'*/ Pri vyhode iz funkcii formal'nye parametry (i lokal'nye peremennye) razopredelyayutsya (i dazhe unichtozhayutsya, sm. sleduyushchij paragraf). Imena formal'nyh parametrov mogut "perekryvat'" (delat' nevidimymi, override) odnoimennye global'nye peremennye na vremya vypolneniya dannoj funkcii. CHto pechataet programma? char str[] = "stroka1"; char lin[] = "stroka2"; f(str) char str[]; /* formal'nyj parametr. */ { printf( "%s %s\n", str, str ); } main(){ char *s = lin; /* fakticheskij parametr: */ f(str); /* massiv str */ f(lin); /* massiv lin */ f(s); /* peremennaya s */ f("stroka3"); /* konstanta */ f(s+2); /* znachenie vyrazheniya */ } Obratite vnimanie, chto parametr str iz f(str) i massiv str[] - eto dve sovershenno RAZNYE veshchi, hotya i nazyvayushchiesya odinakovo. Pereimenujte argument funkcii f i pere- pishite ee v vide f(ss) char ss[]; /* formal'nyj parametr. */ { printf( "%s %s\n", ss, str ); } CHto pechataetsya teper'? Sostav'te analogichnyj primer s celymi chislami. 1.100. Pogovorim bolee podrobno pro oblast' vidimosti imen. int x = 12; f(x){ int y = x*x; if(x) f(x - 1); } main(){ int x=173, z=21; f(2); } Lokal'nye peremennye i argumenty funkcii otvodyatsya v steke pri vyzove funkcii i A. Bogatyrev, 1992-95 - 47 - Si v UNIX unichtozhayutsya pri vyhode iz nee: -+ +- vershina steka |lokal y=0 | |argument x=0 | f(0) |---------------|--------- "kadr" |lokal y=1 | frame |argument x=1 | f(1) |---------------|--------- |lokal y=4 | |argument x=2 | f(2) |---------------|--------- |lokal z=21 | auto: |lokal x=173 | main() ================================== dno steka static: global x=12 ================================== Avtomaticheskie lokal'nye peremennye i argumenty funkcii vidimy tol'ko v tom vyzove funkcii, v kotorom oni otvedeny; no ne vidimy ni v vyzyvayushchih, ni v vyzyvaemyh funk- ciyah (t.e. vidimost' ih ogranichena ramkami svoego "kadra" steka). Staticheskie glo- bal'nye peremennye vidimy v lyubom kadre, esli tol'ko oni ne "perekryty" (zasloneny) odnoimennoj lokal'noj peremennoj (ili formalom) v dannom kadre. CHto napechataet programma? Postarajtes' otvetit' na etot vopros ne vypolnyaya programmu na mashine! x1 x2 x3 x4 x5 int x = 12; /* x1 */ | . . . . f(){ |___ . . . int x = 8; /* x2, perekrytie */ : | . . . printf( "f: x=%d\n", x ); /* x2 */ : | . . . x++; /* x2 */ : | . . . } :--+ . . . g(x){ /* x3 */ :______ . . printf( "g: x=%d\n", x ); /* x3 */ : | . . x++; /* x3 */ : | . . } :-----+ . . h(){ :_________ . int x = 4; /* x4 */ : | . g(x); /* x4 */ : |___ { int x = 55; } /* x5 */ : : | printf( "h: x=%d\n", x ); /* x4 */ : |--+ } :--------+ main(){ | f(); h(); | printf( "main: x=%d\n", x ); /* x1 */ | } ---- Otvet: f: x=8 g: x=4 h: x=4 main: x=12 Obratite vnimanie na funkciyu g. Argumenty funkcii sluzhat kopiyami fakticheskih para- metrov (t.e. yavlyayutsya lokal'nymi peremennymi funkcii, proinicializirovannymi znacheni- yami fakticheskih parametrov), poetomu ih izmenenie ne privodit k izmeneniyu faktiches- kogo parametra. CHtoby izmenyat' fakticheskij parametr, nado peredavat' ego adres! A. Bogatyrev, 1992-95 - 48 - Si v UNIX 1.101. Poyasnim poslednyuyu frazu. (Vnimanie! Vozmozhno, chto dannyj punkt vam sleduet chitat' POSLE glavy pro ukazateli). Pust' my hotim napisat' funkciyu, kotoraya obmeni- vaet svoi argumenty x i y tak, chtoby vypolnyalos' x < y. V kachestve znacheniya funkciya budet vydavat' (x+y)/2. Esli my napishem tak: int msort(x, y) int x, y; { int tmp; if(x > y){ tmp=x; x=y; y=tmp; } return (x+y)/2; } int x=20, y=8; main(){ msort(x,y); printf("%d %d\n", x, y); /* 20 8 */ } to my ne dostignem zhelaemogo effekta. Zdes' perestavlyayutsya x i y, kotorye yavlyayutsya lokal'nymi peremennymi, t.e. kopiyami fakticheskih parametrov. Poetomu vne funkcii eta perestanovka nikak ne proyavlyaetsya! CHtoby my mogli izmenit' argumenty, kopirovat'sya v lokal'nye peremennye dolzhny ne sami znacheniya argumentov, a ih adresa: int msort(xptr, yptr) int *xptr, *yptr; { int tmp; if(*xptr > *yptr){tmp= *xptr;*xptr= *yptr;*yptr=tmp;} return (*xptr + *yptr)/2; } int x=20, y=8, z; main(){ z = msort(&x,&y); printf("%d %d %d\n", x, y, z); /* 8 20 14 */ } Obratite vnimanie, chto teper' my peredaem v funkciyu ne znacheniya x i y, a ih adresa &x i &y. Imenno poetomu (chtoby x smog izmenit'sya) standartnaya funkciya scanf() trebuet ukazaniya adresov: int x; scanf("%d", &x); /* no ne scanf("%d", x); */ Zametim, chto adres ot arifmeticheskogo vyrazheniya ili ot konstanty (a ne ot peremennoj) vychislit' nel'zya, poetomu zakonny: int xx=12, *xxptr = &xx, a[2] = { 13, 17 }; int *fy(){ return &y; } msort(&x, &a[0]); msort(a+1, xxptr); msort(fy(), xxptr); no nezakonny msort(&(x+1), &y); i msort(&x, &17); Zametim eshche, chto pri rabote s adresami my mozhem napravit' ukazatel' v nevernoe mesto i poluchit' nepredskazuemye rezul'taty: msort(&xx - 20, a+40); (ukazateli ukazyvayut neizvestno na chto). Rezyume: esli argument sluzhit tol'ko dlya peredachi znacheniya V funkciyu - ego ne nado (hotya i mozhno) delat' ukazatelem na peremennuyu, soderzhashchuyu trebuemoe znachenie (esli tol'ko eto uzhe ne ukazatel'). Esli zhe argument sluzhit dlya peredachi znacheniya IZ funkcii - on dolzhen byt' ukazatelem na peremennuyu vozvrashchaemogo tipa (luchshe A. Bogatyrev, 1992-95 - 49 - Si v UNIX vozvrashchat' znachenie kak znachenie funkcii - return-om, no inogda nado vozvrashchat' nes- kol'ko znachenij - i etogo glavnogo "okoshka" ne hvataet). Kontrol'nyj vopros: chto pechataet fragment? int a=2, b=13, c; int f(x, y, z) int x, *y, z; { *y += x; x *= *y; z--; return (x + z - a); } main(){ c=f(a, &b, a+4); printf("%d %d %d\n",a,b,c); } (Otvet: 2 15 33) 1.102. Formal'nye argumenty funkcii - eto takie zhe lokal'nye peremennye. Parametry kak by opisany v samom vneshnem bloke funkcii: char *func1(char *s){ int s; /* oshibka: povtornoe opredelenie imeni s */ ... } int func2(int x, int y){ int z; ... } sootvetstvuet int func2(){ int x = bezymyannyj_argument_1_so_steka; int y = bezymyannyj_argument_2_so_steka; int z; ... } Moral' takova: formal'nye argumenty mozhno smelo izmenyat' i ispol'zovat' kak lokal'nye peremennye. 1.103. Vse parametry funkcii mozhno razbit' na 3 klassa: - in - vhodnye; - out - vyhodnye, sluzhashchie dlya vozvrata znacheniya iz funkcii; libo dlya izmeneniya dannyh, nahodyashchihsya po etomu adresu; - in/out - dlya peredachi znacheniya v funkciyu i iz funkcii. Dva poslednih tipa parametrov dolzhny byt' ukazatelyami. Inogda (osobenno v prototipah i v dokumentacii) byvaet polezno ukazyvat' klass parametra v vide kommentariya: int f( /*IN*/ int x, /*OUT*/ int *yp, /*INOUT*/ int *zp){ *yp = ++x + ++(*zp); return (*zp *= x) - 1; } int x=2, y=3, z=4, res; main(){ res = f(x, &y, &z); printf("res=%d x=%d y=%d z=%d\n",res,x,y,z); /* 14 2 8 15 */ } |to polezno potomu, chto inogda trudno ponyat' - zachem parametr opisan kak ukazatel'. To li po nemu vydaetsya iz funkcii informaciya, to li eto prosto ukazatel' na dannye (massiv), peredavaemye v funkciyu. V pervom sluchae ukazuemye dannye budut izmeneny, a vo vtorom - net. V pervom sluchae ukazatel' dolzhen ukazyvat' na zarezervirovannuyu nami A. Bogatyrev, 1992-95 - 50 - Si v UNIX oblast' pamyati, v kotoroj budet razmeshchen rezul'tat. Primer na etu temu est' v glave "Tekstovaya obrabotka" (funkciya bi_conv). 1.104. Izvesten takoj stil' oformleniya argumentov funkcii: void func( int arg1 , char *arg2 /* argument 2 */ , char *arg3[] , time_t time_stamp ){ ... } Sut' ego v tom, chto zapyatye pishutsya v stolbik i v odnu liniyu s ( i ) skobkami dlya argumentov. Pri takom stile legche dobavlyat' i udalyat' argumenty, chem pri versii s zapyatoj v konce. |tot zhe stil' primenim, naprimer, k perechislimym tipam: enum { red , green , blue }; Napishite programmu, formatiruyushchuyu zagolovki funkcij takim obrazom. 1.105. V chem oshibka? char *val(int x){ char str[20]; sprintf(str, "%d", x); return str; } void main(){ int x = 5; char *s = val(x); printf("The values:\n"); printf("%d %s\n", x, s); } Otvet: val vozvrashchaet ukazatel' na avtomaticheskuyu peremennuyu. Pri vyhode iz funkcii val() ee lokal'nye peremennye (v chastnosti str[]) v steke unichtozhayutsya - ukazatel' s teper' ukazyvaet na isporchennye dannye! Vozmozhnym resheniem problemy yavlyaetsya prevra- shchenie str[] v staticheskuyu peremennuyu (hranimuyu ne v steke): static char str[20]; Odnako takoj sposob ne pozvolit pisat' konstrukcii vida printf("%s %s\n", val(1), val(2)); tak kak pod oba vyzova val() ispol'zuetsya odin i tot zhe bufer str[] i budet pecha- tat'sya "1 1" libo "2 2", no ne "1 2". Bolee pravil'nym budet zadanie bufera dlya rezul'tata val() kak argumenta: char *val(int x, char str[]){ sprintf(str, "%d", x); return str; } void main(){ int x=5, y=7; char s1[20], s2[20]; printf("%s %s\n", val(x, s1), val(y, s2)); } A. Bogatyrev, 1992-95 - 51 - Si v UNIX 1.106. Kakovy oshibki (ne sintaksicheskie) v programme|-? main() { double y; int x = 12; y = sin (x); printf ("%s\n", y); } Otvet: - standartnaya bibliotechnaya funkciya sin() vozvrashchaet znachenie tipa double, no my nigde ne informiruem ob etom kompilyator. Poetomu on schitaet po umolchaniyu, chto eta funkciya vozvrashchaet znachenie tipa int i delaet v prisvaivanii y=sin(x) prive- denie tipa int k tipu levogo operanda, t.e. k double. V rezul'tate vozvrashchaemoe znachenie (a ono na samom dele - double) interpretiruetsya neverno (kak int), pod- vergaetsya privedeniyu tipa (kotoroe portit ego), i rezul'tat poluchaetsya sover- shenno ne takim, kak nado. Podobnaya zhe oshibka voznikaet pri ispol'zovanii funk- cij, vozvrashchayushchih ukazatel', naprimer, funkcij malloc() i itoa(). Poetomu esli my pol'zuemsya bibliotechnoj funkciej, vozvrashchayushchej ne int, my dolzhny predvari- tel'no (do pervogo ispol'zovaniya) opisat' ee, naprimer|=: extern double sin(); extern long atol(); extern char *malloc(), *itoa(); |to zhe otnositsya i k nashim sobstvennym funkciyam, kotorye my ispol'zuem prezhde, chem opredelyaem (poskol'ku iz zagolovka funkcii kompilyator obnaruzhit, chto ona vydaet ne celoe znachenie, uzhe posle togo, kak stransliruet obrashchenie k nej): /*extern*/ char *f(); main(){ char *s; s = f(1); puts(s); } char *f(n){ return "knights" + n; } Funkcii, vozvrashchayushchie celoe, opisyvat' ne trebuetsya. Opisaniya dlya nekotoryh standartnyh funkcij uzhe pomeshcheny v sistemnye include-fajly. Naprimer, opisaniya dlya matematicheskih funkcij (sin, cos, fabs, ...) soderzhatsya v fajle /usr/include/math.h. Poetomu my mogli by napisat' pered main #include <math.h> vmesto extern double sin(), cos(), fabs(); - bibliotechnaya funkciya sin() trebuet argumenta tipa double, my zhe peredaem ej argument tipa int (kotoryj koroche tipa double i imeet inoe vnutrennee predstav- lenie). On budet nepravil'no prointerpretirovan funkciej, t.e. my vychislim sinus otnyud' NE chisla 12. Sleduet pisat': y = sin( (double) x ); i sin(12.0); vmesto sin(12); ____________________ |- Dlya translyacii programmy, ispol'zuyushchej standartnye matematicheskie funkcii sin, cos, exp, log, sqrt, i.t.p. sleduet zadavat' klyuch kompilyatora -lm cc file.c -o file -lm |= Slovo extern ("vneshnyaya") ne yavlyaetsya obyazatel'nym, no yavlyaetsya priznakom horo- shego tona - vy soobshchaete programmistu, chitayushchemu etu programmu, chto dannaya funkciya realizovana v drugom fajle, libo voobshche yavlyaetsya standartnoj i beretsya iz biblioteki. A. Bogatyrev, 1992-95 - 52 - Si v UNIX - v printf my pechataem znachenie tipa double po nepravil'nomu formatu: sleduet ispol'zovat' format %g ili %f (a dlya vvoda pri pomoshchi scanf() - %lf). Ochen' chastoj oshibkoj yavlyaetsya pechat' znachenij tipa long po formatu %d vmesto %ld . Pervyh dvuh problem v sovremennom Si udaetsya izbezhat' blagodarya zadaniyu prototipov funkcij (o nih podrobno rasskazano nizhe, v konce glavy "Tekstovaya obrabotka"). Nap- rimer, sin imeet prototip double sin(double x); Tretyaya problema (oshibka v formate) ne mozhet byt' lokalizovana sredstvami Si i imeet bolee-menee priemlemoe reshenie lish' v yazyke C++ (streams). 1.107. Najdite oshibku: int sum(x,y,z){ return(x+y+z); } main(){ int s = sum(12,15); printf("%d\n", s); } Zametim, chto esli by dlya funkcii sum() byl zadan prototip, to kompilyator pojmal by etu nashu oploshnost'! Zamet'te, chto sejchas znachenie z v sum() nepredskazuemo. Esli by my vyzyvali s = sum(12,15,17,24); to lishnie argumenty byli by prosto proignorirovany (no i tut mozhet byt' syurpriz - argumenty mogli by ignorirovat'sya s LEVOGO konca spiska!). A vot primer opasnoj oshibki, kotoraya ne lovitsya dazhe prototipami: int x; scanf("%d%d", &x ); Vtoroe chislo po formatu %d budet schitano neizvestno po kakomu adresu i razrushit pamyat' programmy. Ni odin kompilyator ne proveryaet sootvetstvie chisla %-ov v stroke formata chislu argumentov scanf i printf. 1.108. CHto zdes' oznachayut vnutrennie (,,) v vyzove funkcii f() ? f(x, y, z){ printf("%d %d %d\n", x, y, z); } main(){ int t; f(1, (2, 3, 4), 5); f(1, (t=3,t+1), 5); } Otvet: (2,3,4) - eto operator "zapyataya", vydayushchij znachenie poslednego vyrazheniya iz spiska perechislennyh cherez zapyatuyu vyrazhenij. Zdes' budet napechatano 1 4 5. Kazhushchayasya dvojstvennost' voznikaet iz-za togo, chto argumenty funkcii tozhe perechislyayutsya cherez zapyatuyu, no eto sovsem drugaya sintaksicheskaya konstrukciya. Vot eshche primer: int y = 2, x; x = (y+4, y, y*2); printf("%d\n", x); /* 4 */ x = y+4, y, y*2 ; printf("%d\n", x); /* 6 */ x = (x=y+4, ++y, x*y); printf("%d\n", x); /* 18 */ Snachala obratim vnimanie na pervuyu stroku. |to - ob®yavlenie peremennyh x i y (prichem y - s inicializaciej), poetomu zapyataya zdes' - ne OPERATOR, a prosto razdelitel' ob®yavlyaemyh peremennyh! Dalee sleduyut tri stroki vypolnyaemyh operatorov. V pervom sluchae vypolnilos' x=y*2; vo vtorom x=y+4 (t.k. prioritet u prisvaivaniya vyshe, chem u A. Bogatyrev, 1992-95 - 53 - Si v UNIX zapyatoj). Obratite vnimanie, chto vyrazhenie bez prisvaivaniya (kotoroe mozhet voobshche ne imet' effekta ili imet' tol'ko pobochnyj effekt) vpolne zakonno: x+y; ili z++; ili x == y+1; ili x; V chastnosti, vse vyzovy funkcij-procedur imenno takovy (eto vyrazheniya bez operatora prisvaivaniya, imeyushchie pobochnyj effekt): f(12,x); putchar('Y'); v otlichie, skazhem, ot x=cos(0.5)/3.0; ili c=getchar(); Operator "zapyataya" razdelyaet vyrazheniya, a ne prosto operatory, poetomu esli hot' odin iz perechislennyh operatorov ne vydaet znacheniya, to eto yavlyaetsya oshibkoj: main(){ int i, x = 0; for(i=1; i < 4; i++) x++, if(x > 2) x = 2; /* ispol'zuj { ; } */ } operator if ne vydaet znacheniya. Takzhe logicheski oshibochno ispol'zovanie funkcii tipa void (ne vozvrashchayushchej znacheniya): void f(){} ... for(i=1; i < 4; i++) x++, f(); hotya kompilyator mozhet dopustit' takoe ispol'zovanie. Vot eshche odin primer togo, kak mozhno perepisat' odin i tot zhe fragment, primenyaya raznye sintaksicheskie konstrukcii: if( uslovie ) { x = 0; y = 0; } if( uslovie ) x = 0, y = 0; if( uslovie ) x = y = 0; 1.109. Najdite opechatku: switch(c){ case 1: x++; break; case 2: y++; break; defalt: z++; break; } Esli c=3, to z++ ne proishodit. Pochemu? (Potomu, chto defalt: - eto metka, a ne klyu- chevoe slovo default). 1.110. Pochemu programma zaciklivaetsya i pechataet sovsem ne to, chto nazhato na klavia- ture, a tol'ko 0 i 1? while ( c = getchar() != 'e') printf("%d %c\n, c, c); Otvet: dannyj fragment dolzhen byl vyglyadet' tak: while ((c = getchar()) != 'e') printf("%d %c\n, c, c); A. Bogatyrev, 1992-95 - 54 - Si v UNIX Sravnenie v Si imeet vysshij prioritet, nezheli prisvaivanie! Moral': nado byt' vnima- tel'nee k prioritetam operacij. Eshche odin primer na pohozhuyu temu: vmesto if( x & 01 == 0 ) ... if( c&0377 > 0300)...; nado: if( (x & 01) == 0 ) ... if((c&0377) > 0300)...; I eshche primer s analogichnoj oshibkoj: FILE *fp; if( fp = fopen( "fajl", "w" ) == NULL ){ fprintf( stderr, "ne mogu pisat' v fajl\n"); exit(1); } fprintf(fp,"Good bye, %s world\n","cruel"); fclose(fp); V etom primere fajl otkryvaetsya, no fp ravno 0 (logicheskoe znachenie!) i funkciya fprintf() ne srabatyvaet (programma padaet po zashchite pamyati|-). Isprav'te analogichnuyu oshibku (na prioritet operacij) v sleduyushchej funkcii: /* kopirovanie stroki from v to */ char *strcpy( to, from ) register char *from, *to; { char *p = to; while( *to++ = *from++ != '\0' ); return p; } 1.111. Sravneniya s nulem (0, NULL, '\0') v Si prinyato opuskat' (hotya eto ne vsegda sposobstvuet yasnosti). if( i == 0 ) ...; --> if( !i ) ... ; if( i != 0 ) ...; --> if( i ) ... ; naprimer, vmesto char s[20], *p ; for(p=s; *p != '\0'; p++ ) ... ; budet for(p=s; *p; p++ ) ... ; i vmesto char s[81], *gets(); while( gets(s) != NULL ) ... ; budet while( gets(s)) ... ; Perepishite strcpy v etom bolee lakonichnom stile. ____________________ |- "Padat'" - programmistskij zhargon. Oznachaet "avarijno zavershat'sya". "Zashchita pa- myati" - obrashchenie po nekorrektnomu adresu. V UNIX takaya oshibka lovitsya apparatno, i programma budet ubita odnim iz signalov: SIGBUS, SIGSEGV, SIGILL. Sistema soobshchit nechto vrode "oshibka shiny". Znajte, chto eto ne oshibka apparatury i ne sboj, a VASHA oshibka! A. Bogatyrev, 1992-95 - 55 - Si v UNIX 1.112. Istinno li vyrazhenie if( 2 < 5 < 4 ) Otvet: da! Delo v tom, chto Si ne imeet logicheskogo tipa, a vmesto "istina" i "lozh'" ispol'zuet celye znacheniya "ne 0" i "0" (logicheskie operacii vydayut 1 i 0). Dannoe vyrazhenie v uslovii if ekvivalentno sleduyushchemu: ((2 < 5) < 4) Znacheniem (2 < 5) budet 1. Znacheniem (1 < 4) budet tozhe 1 (istina). Takim obrazom my poluchaem sovsem ne to, chto ozhidalos'. Poetomu vmesto if( a < x < b ) nado pisat' if( a < x && x < b ) 1.113. Dannaya programma dolzhna pechatat' kody vvodimyh simvolov. Najdite opechatku; pochemu cikl srazu zavershaetsya? int c; for(;;) { printf("Vvedite ocherednoj simvol:"); c = getchar(); if(c = 'e') { printf("nazhato e, konec\n"); break; } printf( "Kod %03o\n", c & 0377 ); } Otvet: v if imeetsya opechatka: ispol'zovano `=' vmesto `=='. Prisvaivanie v Si (a takzhe operacii +=, -=, *=, i.t.p.) vydaet novoe znachenie levoj chasti, poetomu sintaksicheskoj oshibki zdes' net! Napisannyj operator ravnosilen c = 'e'; if( c ) ... ; i, poskol'ku 'e'!= 0, to uslovie okazyvaetsya istinnym! |to eshche i sledstvie togo, chto v Si net special'nogo logicheskogo tipa (istina/lozh'). Bud'te vnimatel'ny: kompilyator ne schitaet oshibkoj ispol'zovanie operatora = vmesto == vnutri uslovij if i uslovij ciklov (hotya nekotorye kompilyatory vydayut preduprezhdenie). Eshche analogichnaya oshibka: for( i=0; !(i = 15) ; i++ ) ... ; (cikl ne vypolnyaetsya); ili static char s[20] = " abc"; int i=0; while(s[i] = ' ') i++; printf("%s\n", &s[i]); /* dolzhno napechatat'sya abc */ (stroka zapolnyaetsya probelami i cikl ne konchaetsya). To, chto operator prisvaivaniya imeet znachenie, ves'ma udobno: int x, y, z; eto na samom dele x = y = z = 1; x = (y = (z = 1)); A. Bogatyrev, 1992-95 - 56 - Si v UNIX ili|- y=f( x += 2 ); // vmesto x+=2; y=f(x); if((y /= 2) > 0)...; // vmesto y/=2; if(y>0)...; Vot primer uproshchennoj igry v "ochko" (uproshchennoj - t.k. ne uchityvaetsya ogranichennost' chisla kart kazhdogo tipa v kolode (po 4 shtuki)): #include <stdio.h> main(){ int sum = 0, card; char answer[36]; srand( getpid()); /* randomizaciya */ do{ printf( "U vas %d ochkov. Eshche? ", sum); if( *gets(answer) == 'n' ) break; /* inache malovato budet */ printf( " %d ochkov\n", card = 6 + rand() % (11 - 6 + 1)); } while((sum += card) < 21); /* SIC ! */ printf ( sum == 21 ? "ochko\n" : sum > 21 ? "perebor\n": "%d ochkov\n", sum); } Vot eshche primer, ispol'zuyushchijsya dlya podscheta pravil'nogo razmera tablicy. Obratite vnimanie, chto prisvaivaniya ispol'zuyutsya v sravneniyah, v argumentah vyzova funkcii (printf), t.e. vezde, gde dopustimo vyrazhenie: #include <stdio.h> int width = 20; /* nachal'noe znachenie shiriny polya */ int len; char str[512]; main(){ while(gets(str)){ if((len = strlen(str)) > width){ fprintf(stderr,"width uvelichit' do %d\n", width=len); } printf("|%*.*s|\n", -width, width, str); } } Vyzyvaj etu programmu kak a.out < vhodnojFajl > /dev/null 1.114. Pochemu programma "zavisaet" (na samom dele - zaciklivaetsya) ? int x = 0; while( x < 100 ); printf( "%d\n", x++ ); printf( "VSE\n" ); Ukazanie: gde konchaetsya cikl while? Moral': ne nado stavit' ; gde popalo. Eshche moral': dazhe otstupy v oformlenii programmy ne yavlyayutsya garantiej otsutstviya oshibok v gruppirovke operatorov. 1.115. Voobshche, prioritety operacij v Si chasto ne sootvetstvuyut ozhidaniyam nashego zdravogo smysla. Naprimer, znacheniem vyrazheniya: x = 1 << 2 + 1 ; ____________________ |- Konstrukciya //tekst, kotoraya budet izredka popadat'sya v dal'nejshem - eto kommen- tarij v stile yazyka C++. Takoj kommentarij prostiraetsya ot simvola // do konca stroki. A. Bogatyrev, 1992-95 - 57 - Si v UNIX budet 8, a ne 5, poskol'ku slozhenie vypolnitsya pervym. Moral': v zatrudnitel'nyh i neochevidnyh sluchayah luchshe yavno ukazyvat' prioritety pri pomoshchi kruglyh skobok: x = (1 << 2) + 1 ; Eshche primer: uvelichivat' x na 40, esli ustanovlen flag, inache na 1: int bigFlag = 1, x = 2; x = x + bigFlag ? 40 : 1; printf( "%d\n", x ); otvetom budet 40, a ne 42, poskol'ku eto x = (x + bigFlag) ? 40 : 1; a ne x = x + (bigFlag ? 40 : 1); kotoroe my imeli v vidu. Poetomu vokrug uslovnogo vyrazheniya ?: obychno pishut kruglye skobki. Zametim, chto () ukazyvayut tol'ko prioritet, no ne poryadok vychislenij. Tak, kom- pilyator imeet polnoe pravo vychislit' long a = 50, x; int b = 4; x = (a * 100) / b; /* delenie celochislennoe s ostatkom ! */ i kak x = (a * 100)/b = 5000/4 = 1250 i kak x = (a/b) * 100 = 12*100 = 1200 nevziraya na nashi skobki, poskol'ku i * i / imeyut odinakovyj prioritet (hotya eto "pravo" eshche ne oznachaet, chto on obyazatel'no tak postupit). Takie operatory priho- ditsya razbivat' na dva, t.e. vvodit' promezhutochnuyu peremennuyu: { long a100 = a * 100; x = a100 / b; } 1.116. Sostav'te programmu vychisleniya trigonometricheskoj funkcii. Nazvanie funkcii i znachenie argumenta peredayutsya v kachestve parametrov funkcii main (sm. pro argv i argc v glave "Vzaimodejstvie s UNIX"): $ a.out sin 0.5 sin(0.5)=0.479426 (zdes' i dalee znachok $ oboznachaet priglashenie, vydannoe interpretatorom komand). Dlya preobrazovaniya stroki v znachenie tipa double vospol'zujtes' standartnoj funkciej atof(). char *str1, *str2, *str3; ... extern double atof(); double x = atof(str1); extern long atol(); long y = atol(str2); extern int atoi(); int i = atoi(str3); libo sscanf(str1, "%f", &x); sscanf(str2, "%ld", &y); sscanf(str3,"%d", &i); K slovu zametim, chto obratnoe preobrazovanie - chisla v tekst - udobnee vsego delaetsya pri pomoshchi funkcii sprintf(), kotoraya analogichna printf(), no sformirovannaya eyu stroka-soobshchenie ne vydaetsya na ekran, a zanositsya v massiv: A. Bogatyrev, 1992-95 - 58 - Si v UNIX char represent[ 40 ]; int i = ... ; sprintf( represent, "%d", i ); 1.117. Sostav'te programmu vychisleniya polinoma n-oj stepeni: n n-1 Y = A * X + A * X + ... + A0 n n-1 shema (Gornera): Y = A0 + X * ( A1 + X * ( A2 + ... + X * An )))...) Oformite algoritm kak funkciyu s peremennym chislom parametrov: poly( x, n, an, an-1, ... a0 ); O tom, kak eto sdelat' - chitajte razdel rukovodstva po UNIX man varargs. Otvet: #include <varargs.h> double poly(x, n, va_alist) double x; int n; va_dcl { va_list args; double sum = 0.0; va_start(args); /* inicializirovat' spisok arg-tov */ while( n-- >= 0 ){ sum *= x; sum += va_arg(args, double); /* izvlech' sled. argument tipa double */ } va_end(args); /* unichtozhit' spisok argumentov */ return sum; } main(){ /* y = 12*x*x + 3*x + 7 */ printf( "%g\n", poly(2.0, 2, 12.0, 3.0, 7.0)); } Prototip etoj funkcii: double poly(double x, int n, ... ); V etom primere ispol'zovany makrosy va_nechto. CHast' argumentov, kotoraya yavlyaetsya spiskom peremennoj dliny, oboznachaetsya v spiske parametrov kak va_alist, pri etom ona ob®yavlyaetsya kak va_dcl v spiske tipov parametrov. Zamet'te, chto tochka-s-zapyatoj posle va_dcl ne nuzhna! Opisanie va_list args; ob®yavlyaet special'nuyu "svyaznuyu" peremennuyu; smysl ee mashinno zavisim. va_start(args) inicializiruet etu peremennuyu spiskom fak- ticheskih argumentov, sootvetstvuyushchih va_alist-u. va_end(args) deinicializiruet etu peremennuyu (eto nado delat' obyazatel'no, poskol'ku inicializaciya mogla byt' svyazana s konstruirovaniem spiska argumentov pri pomoshchi vydeleniya dinamicheskoj pamyati; teper' my dolzhny unichtozhit' etot spisok i osvobodit' pamyat'). Ocherednoj argument tipa TYPE izvlekaetsya iz spiska pri pomoshchi TYPE x = va_arg(args, TYPE); Spisok argumentov prosmatrivaetsya sleva napravo v odnom napravlenii, vozvrat k A. Bogatyrev, 1992-95 - 59 - Si v UNIX predydushchemu argumentu nevozmozhen. Nel'zya ukazyvat' v kachestve tipov char, short, float: char ch = va_arg(args, char); poskol'ku v yazyke Si argumenty funkcii takih tipov avtomaticheski rasshiryayutsya v int, int, double sootvetstvenno. Korrektno budet tak: int ch = va_arg(args, int); 1.118. Eshche ob odnoj lovushke v yazyke Si na PDP-11 (i v kompilyatorah byvayut oshibki!): unsigned x = 2; printf( "%ld %ld", - (long) x, (long) -x ); |tot fragment napechataet chisla -2 i 65534. Vo vtorom sluchae pri privedenii k tipu long byl rasshiren znakovyj bit. Vstroennaya operaciya sizeof vydaet znachenie tipa unsigned. Podumajte, kakov budet effekt v sleduyushchem fragmente programmy? static struct point{ int x, y ;} p = { 33, 13 }; FILE *fp = fopen( "00", "w" ); /* vpered na dlinu odnoj struktury */ fseek( fp, (long) sizeof( struct point ), 0 ); /* nazad na dlinu odnoj struktury */ /*!*/ fseek( fp, (long) -sizeof( struct point ), 1 ); /* zapisyvaem v nachalo fajla odnu strukturu */ fwrite( &p, sizeof p, 1, fp ); /* zakryvaem fajl */ fclose( fp ); Gde dolzhen nahodit'sya minus vo vtorom vyzove fseek dlya polucheniya ozhidaemogo rezul'- tata? (Dannyj primer mozhet vesti sebya po-raznomu na raznyh mashinah, voprosy kasayutsya PDP-11). 1.119. Obratimsya k ukazatelyam na funkcii: void g(x){ printf("%d: here\n", x); } main(){ void (*f)() = g; /* Ukazatel' smotrit na funkciyu g() */ (*f)(1); /* Staraya forma vyzova funkcii po ukazatelyu */ f (2); /* Novaya forma vyzova */ /* V oboih sluchayah vyzyvaetsya g(x); */ } CHto pechataet programma? typedef void (*(*FUN))(); /* Popytka izobrazit' rekursivnyj tip typedef FUN (*FUN)(); */ FUN g(FUN f){ return f; } void main(){ FUN y = g(g(g(g(g)))); if(y == g) printf("OK\n"); A. Bogatyrev, 1992-95 - 60 - Si v UNIX } CHto pechataet programma? char *f(){ return "Hello, user!"; } g(func) char * (*func)(); { puts((*func)()); } main(){ g(f); } Pochemu bylo by neverno napisat' main(){ g(f()); } Eshche analogichnaya oshibka (posmotrite pro funkciyu signal v glave "Vzaimodejstvie s UNIX"): #include <signal.h> f(){ printf( "Good bye.\n" ); exit(0); } main(){ signal ( SIGINT, f() ); ... } Zapomnite, chto f() - eto ZNACHENIE funkcii f (t.e. ona vyzyvaetsya i nechto vozvrashchaet return-om; eto-to znachenie my i ispol'zuem), a f - eto ADRES funkcii f (ran'she eto tak i pisalos' &f), to est' metka nachala ee mashinnyh kodov ("tochka vhoda"). 1.120. CHto napechataet programma? (Primer posvyashchen ukazatelyam na funkcii i massivam funkcij): int f(n){ return n*2; } int g(n){ return n+4; } int h(n){ return n-1; } int (*arr[3])() = { f, g, h }; main(){ int i; for(i=0; i < 3; i++ ) printf( "%d\n", (*arr[i])(i+7) ); } 1.121. CHto napechataet programma? extern double sin(), cos(); main(){ double x; /* cc -lm */ for(x=0.0; x < 1.0; x += 0.2) printf("%6.4g %6.4g %6.4g\n", (x > 0.5 ? sin : cos)(x), sin(x), cos(x)); } to zhe v variante A. Bogatyrev, 1992-95 - 61 - Si v UNIX extern double sin(), cos(); main(){ double x; double (*f)(); for(x=0.0; x < 1.0; x += 0.2){ f = (x > 0.5 ? sin : cos); printf("%g\n", (*f)(x)); } } 1.122. Rassmotrite chetyre realizacii funkcii faktorial: n! = 1 * 2 * ... * n ili n! = n * (n-1)! gde 0! = 1 Vse oni illyustriruyut opredelennye podhody v programmirovanii: /* CIKL (ITERACIYA) */ int factorial1(n){ int res = 1; while(n > 0){ res *= n--; } return res; } /* PROSTAYA REKURSIYA */ int factorial2(n){ return (n==0 ? 1 : n * factorial2(n-1)); } /* Rekursiya, v kotoroj funkciya vyzyvaetsya rekursivno * edinstvennyj raz - v operatore return, nazyvaetsya * "hvostovoj rekursiej" (tail recursion) i * legko preobrazuetsya v cikl */ /* AVTOAPPLIKACIYA */ int fi(f, n) int (*f)(), n; { if(n == 0) return 1; else return n * (*f)(f, n-1); } int factorial3(n){ return fi(fi, n); } /* REKURSIYA S NELOKALXNYM PEREHODOM */ #include <setjmp.h> jmp_buf checkpoint; void fact(n, res) register int n, res; { if(n) fact(n - 1, res * n); else longjmp(checkpoint, res+1); } int factorial4(n){ int res; if(res = setjmp(checkpoint)) return (res - 1); else fact(n, 1); } 1.123. Napishite funkciyu, pechatayushchuyu celoe chislo v sisteme schisleniya s osnovaniem base. Otvet: A. Bogatyrev, 1992-95 - 62 - Si v UNIX printi( n, base ){ register int i; if( n < 0 ){ putchar( '-' ); n = -n; } if( i = n / base ) printi( i, base ); i = n % base ; putchar( i >= 10 ? 'A' + i - 10 : '0' + i ); } Poprobujte napisat' nerekursivnyj variant s nakopleniem otveta v stroke. Prive- dem rekursivnyj variant, nakaplivayushchij otvet v stroke s i pol'zuyushchijsya analogom funk- cii printi: funkciya prints - takaya zhe, kak printi, no vmesto vyzovov putchar(nechto); v nej napisany operatory *res++ = nechto; i rekursivno vyzyvaetsya konechno zhe prints. Itak: static char *res; ... tekst funkcii prints ... char *itos( n, base, s ) char *s; /* ukazyvaet na char[] massiv dlya otveta */ { res = s; prints(n, base); *res = '\0'; return s; } main(){ char buf[20]; printf( "%s\n", itos(19,2,buf); } 1.124. Napishite funkciyu dlya pobitnoj raspechatki celogo chisla. Imejte v vidu, chto chislo soderzhit 8 * sizeof(int) bit. Ukazanie: ispol'zujte operacii bitovogo sdviga i &. Otvet: printb(n){ register i; for(i = 8 * sizeof(int) - 1; i >= 0; --i) putchar(n & (1 << i) ? '1':'0'); } 1.125. Napishite funkciyu, sklonyayushchuyu sushchestvitel'nye russkogo yazyka v zavisimosti ot ih chisla. Naprimer: printf( "%d kirpich%s", n, grammar( n, "ej", "", "a" )); Otvet: char *grammar( i, s1, s2, s3 ) char *s1, /* prochee */ *s2, /* odin */ *s3; /* dva, tri, chetyre */ { i = i % 100; if( i > 10 && i <= 20 ) return s1; i = i % 10; if( i == 1 ) return s2; if( i == 2 || i == 3 || i == 4 ) return s3; return s1; } A. Bogatyrev, 1992-95 - 63 - Si v UNIX 1.126. Napishite operator printf, pechatayushchij chisla iz intervala 0..99 s dobavleniem nulya pered chislom, esli ono men'she 10 : 00 01 ... 09 10 11 ... Ispol'zujte uslovnoe vyrazhenie, format. Otvet: printf ("%s%d", n < 10 ? "0" : "", n); libo printf ("%02d", n ); libo printf ("%c%c", '0' + n/10, '0' + n%10 ); 1.127. Predosterezhem ot odnoj oshibki, chasto dopuskaemoj nachinayushchimi. putchar( "c" ); yavlyaetsya oshibkoj. putchar( 'c' ); verno. Delo v tom, chto putchar trebuet argument - simvol, togda kak "c" - STROKA iz odnogo simvola. Bol'shinstvo kompilyatorov (te, kotorye ne proveryayut prototipy vyzova stan- dartnyh funkcij) NE obnaruzhit zdes' nikakoj sintaksicheskoj oshibki (kstati, oshibka eta - semanticheskaya). Takzhe oshibochny operatory printf ( '\n' ); /* nuzhna stroka */ putchar( "\n" ); /* nuzhen simvol */ putchar( "ab" ); /* nuzhen simvol */ putchar( 'ab' ); /* oshibka v bukvennoj konstante */ char c; if((c = getchar()) == "q" ) ... ; /* nuzhno pisat' 'q' */ Otlichajte stroku iz odnogo simvola i simvol - eto raznye veshchi! (Podrobnee ob etom - v sleduyushchej glave). 1.128. Ves'ma chastoj yavlyaetsya oshibka "promah na edinicu", kotoraya vstrechaetsya v ochen' mnogih i raznoobraznyh sluchayah. Vot odna iz vozmozhnyh situacij: int m[20]; int i = 0; while( scanf( "%d", & m[i++] ) != EOF ); printf( "Vveli %d chisel\n", i ); V itoge i okazhetsya na 1 bol'she, chem ozhidalos'. Razberites' v chem delo. Otvet: argumenty funkcii vychislyayutsya do ee vyzova, poetomu kogda my dostigaem konca fajla i scanf vozvrashchaet EOF, i++ v vyzove scanf vse ravno delaetsya. Nado napi- sat' while( scanf( "%d", & m[i] ) != EOF ) i++; 1.129. Zamechanie po stilistike: pri vyvode soobshcheniya na ekran printf( "Hello \n" ); probely pered \n dostatochno bessmyslenny, poskol'ku na ekrane nikak ne otobrazyatsya. Nado pisat' (ekonomya pamyat') printf( "Hello\n" ); A. Bogatyrev, 1992-95 - 64 - Si v UNIX Edinstvennyj sluchaj, kogda takie probely znachimy - eto kogda vy vyvodite tekst inver- siej. Togda probely otobrazhayutsya kak svetlyj fon. Eshche nepriyatnee budet printf( "Hello\n " ); poskol'ku koncevye probely okazhutsya v nachale sleduyushchej stroki. 1.130. printf - interpretiruyushchaya funkciya, t.e. rabotaet ona dovol'no medlenno. Poe- tomu vmesto char s[20]; int i; ... printf( "%c", s[i] ); i printf( "\n" ); nado vsegda pisat' putchar( s[i] ); i putchar( '\n' ); poskol'ku printf v konce-koncov (sdelav vse preobrazovaniya po formatu) vnutri sebya vyzyvaet putchar. Tak sdelaem zhe eto srazu! 1.131. To, chto parametr "format" v funkcii printf mozhet byt' vyrazheniem, pozvolyaet delat' nekotorye udobnye veshchi. Naprimer: int x; ... printf( x ? "znachenie x=%d\n" : "x raven nulyu\n\n", x); Format zdes' - uslovnoe vyrazhenie. Esli x!=0, to budet napechatano znachenie x po for- matu %d. Esli zhe x==0, to budet napechatana stroka, ne soderzhashchaya ni odnogo %-ta. V rezul'tate argument x v spiske argumentov budet prosto proignorirovan. Odnako, nap- rimer int x = ... ; printf( x > 30000 ? "%f\n" : "%d\n", x); (chtoby bol'shie x pechatalis' v vide 31000.000000) nezakonno, poskol'ku celoe chislo nel'zya pechatat' po formatu %f ni v kakih sluchayah. Edinstvennym sposobom sdelat' eto yavlyaetsya yavnoe privedenie x k tipu double: printf("%f\n", (double) x); Budet li zakonen operator? printf( x > 30000 ? "%f\n" : "%d\n", x > 30000 ? (double) x : x ); Otvet: net. Uslovnoe vyrazhenie dlya argumenta budet imet' "starshij" tip - double. A znachenie tipa double nel'zya pechatat' po formatu %d. My dolzhny ispol'zovat' zdes' operator if: if( x > 30000 ) printf("%f\n", (double)x); else printf("%d\n", x); 1.132. Napishite funkciyu, pechatayushchuyu razmer fajla v udobnom vide: esli fajl men'she odnogo kilobajta - pechatat' ego razmer v bajtah, esli zhe bol'she - v kilobajtah (i megabajtah). #define KBYTE 1024L /* kilobajt */ #define THOUSAND 1024L /* kb. v megabajte */ A. Bogatyrev, 1992-95 - 65 - Si v UNIX void tellsize(unsigned long sz){ if(sz < KBYTE) printf("%lu bajt", sz); else{ unsigned long Kb = sz/KBYTE; unsigned long Mb = Kb/THOUSAND; unsigned long Dec = ((sz % KBYTE) * 10) / KBYTE; if( Mb ){ Kb %= THOUSAND; printf( Dec ? "%lu.%03lu.%01lu Mb." : "%lu.%lu Mb.", Mb, Kb, Dec ); } else printf( Dec ? "%lu.%01lu Kb.":"%lu Kb.", Kb, Dec); } putchar('\n'); } 1.133. Dlya pechati strok ispol'zujte printf("%s", string); /* A */ no ne printf(string); /* B */ Esli my ispol'zuem variant B, a v stroke vstretitsya simvol '%' char string[] = "abc%defg"; to %d budet vosprinyato kak format dlya vyvoda celogo chisla. Vo-pervyh, sama stroka %d ne budet napechatana; vo-vtoryh - chto zhe budet pechatat'sya po etomu formatu, kogda u nas est' lish' edinstvennyj argument - string?! Napechataetsya kakoj-to musor! 1.134. Pochemu operator char s[20]; scanf("%s", s); printf("%s\n", s); v otvet na vvod stroki Pushkin A.S. pechataet tol'ko "Pushkin"? Otvet: potomu, chto koncom teksta pri vvode po formatu %s schitaetsya libo \n, libo probel, libo tabulyaciya, a ne tol'ko \n; to est' format %s chitaet slovo iz teksta. CHtenie vseh simvolov do konca stroki, (vklyuchaya probely) dolzhno vyglyadet' tak: scanf("%[^\n]\n", s); %[^\n] - chitat' lyubye simvoly, krome \n (do \n) \n - propustit' \n na konce stroki %[abcdef] - chitat' slovo, sostoyashchee iz perechislennyh bukv. %[^abcde] - chitat' slovo iz lyubyh bukv, krome perechislennyh (prervat'sya po bukve iz spiska). Pust' teper' stroki vhodnoj informacii imeyut format: Frejd Zigmund 1856 1939 Pust' my hotim schityvat' v stroku s familiyu, v celoe y - god rozhdeniya, a prochie polya - ignorirovat'. Kak eto sdelat'? Nam pomozhet format "podavlenie prisvaivaniya" %*: scanf("%s%*s%d%*[^\n]\n", s, &y ); A. Bogatyrev, 1992-95 - 66 - Si v UNIX %* propuskaet pole po formatu, ukazannomu posle *, ne zanosya ego znachenie ni v kakuyu peremennuyu, a prosto "zabyvaya" ego. Tak format "%*[^\n]\n" ignoriruet "hvost" stroki, vklyuchaya simvol perevoda stroki. Simvoly " ", "\t", "\n" v formate vyzyvayut propusk vseh probelov, tabulyacij, perevodov strok vo vhodnom potoke, chto mozhno opisat' kak int c; while((c = getc(stdin))== ' ' || c == '\t' || c == '\n' ); libo kak format %*[ \t\n] Pered chislovymi formatami (%d, %o, %u, %ld, %x, %e, %f), a takzhe %s, propusk probelov delaetsya avtomaticheski. Poetomu scanf("%d%d", &x, &y); i scanf("%d %d", &x, &y); ravnopravny (probel pered vtorym %d prosto ne nuzhen). Neyavnyj propusk probelov ne delaetsya pered %c i %[... , poetomu v otvet na vvod stroki "12 5 x" primer main(){ int n, m; char c; scanf("%d%d%c", &n, &m, &c); printf("n=%d m=%d c='%c'\n", n, m, c); } napechataet "n=12 m=5 c=' '", to est' v c budet prochitan probel (predshestvovavshij x), a ne x. Avtomaticheskij propusk probelov pered %s ne pozvolyaet schityvat' po %s stroki, lidiruyushchie probely kotoryh dolzhny sohranyat'sya. CHtoby lidiruyushchie probely takzhe schity- valis', sleduet ispol'zovat' format scanf("%[^\n]%*1[\n]", s); v kotorom modifikator dliny 1 zastavlyaet ignorirovat' tol'ko odin simvol \n, a ne VSE probely i perevody strok, kak "\n". K sozhaleniyu (kak pokazal eksperiment) etot format ne v sostoyanii prochest' pustuyu stroku (sostoyashchuyu tol'ko iz \n). Poetomu mozhno sdelat' global'nyj vyvod: stroki nado schityvat' pri pomoshchi funkcij gets() i fgets()! 1.135. Eshche para slov pro scanf: scanf vozvrashchaet chislo uspeshno prochitannyh im dannyh (obrabotannyh %-ov) ili EOF v konce fajla. Neudacha mozhet nastupit', esli dannoe vo vhodnom potoke ne sootvetstvuet formatu, naprimer stroka 12 quack dlya int d1; double f; scanf("%d%lf", &d1, &f); V etom sluchae scanf prochtet 12 po formatu %d v peremennuyu d1, no slovo quack ne otve- chaet formatu %lf, poetomu scanf prervet svoyu rabotu i vydast znachenie 1 (uspeshno pro- chel odin format). Stroka quack ostanetsya nevostrebovannoj - ee prochitayut posleduyushchie vyzovy funkcij chteniya; a sejchas f ostanetsya neizmenennoj. 1.136. Si imeet kvalifikator const, ukazyvayushchij, chto znachenie yavlyaetsya ne peremen- noj, a konstantoj, i popytka izmenit' velichinu po etomu imeni yavlyaetsya oshibkoj. Vo mnogih sluchayah const mozhet zamenit' #define, pri etom eshche yavno ukazan tip konstanty, chto polezno dlya proverok kompilyatorom. A. Bogatyrev, 1992-95 - 67 - Si v UNIX const int x = 22; x = 33; /* oshibka: konstantu nel'zya menyat' */ Ispol'zovanie const s ukazatelem: Ukazuemyj ob®ekt - konstanta const char *pc = "abc"; pc[1] = 'x'; /* oshibka */ pc = "123"; /* OK */ Sam ukazatel' - konstanta char *const cp = "abc"; cp[1] = 'x'; /* OK */ cp = "123"; /* oshibka */ Ukazuemyj ob®ekt i sam ukazatel' - konstanty const char *const cpc = "abc"; cpc[1] = 'x'; /* oshibka */ cpc = "123"; /* oshibka */ Ukazatel' na konstantu neobhodimo ob®yavlyat' kak const TYPE* int a = 1; const int b = 2; const int *pca = &a; /* OK, prosto rassmatrivaem a kak konstantu */ const int *pcb = &b; /* OK */ int *pb = &b; /* oshibka, tak kak togda vozmozhno bylo by napisat' */ *pb = 3; /* izmenit' konstantu b */ 1.137. Standartnaya funkciya bystroj sortirovki qsort (algoritm quick sort) imeet takoj format: chtoby otsortirovat' massiv elementov tipa TYPE TYPE arr[N]; nado vyzyvat' qsort(arr,/* CHto sortirovat'? Ne s nachala: arr+m */ N, /* Skol'ko pervyh elementov massiva? */ /* mozhno sortirovat' tol'ko chast': n < N */ sizeof(TYPE),/* Ili sizeof arr[0] */ /* razmer odnogo elementa massiva*/ cmp); gde int cmp(TYPE *a1, TYPE *a2); funkciya sravneniya elementov *a1 i *a2. Ee argumenty - ADRESA dvuh kakih-to elementov sortiruemogo massiva. Funkciyu cmp my dolzhny napisat' sami - eto funkciya, zadayushchaya uporyadochenie elementov massiva. Dlya sortirovki po vozrastaniyu funkciya cmp() dolzhna vozvrashchat' celoe < 0, esli *a1 dolzhno idti ran'she *a2 < = 0, esli *a1 sovpadaet s *a2 == > 0, esli *a1 dolzhno idti posle *a2 > Dlya massiva strok elementy massiva imeyut tip (char *), poetomu argumenty funkcii imeyut tip (char **). Trebuemomu usloviyu udovletvoryaet takaya funkciya: A. Bogatyrev, 1992-95 - 68 - Si v UNIX char *arr[N]; ... cmps(s1, s2) char **s1, **s2; { return strcmp(*s1, *s2); } (Pro strcmp smotri razdel "Massivy i stroki"). Zametim, chto v nekotoryh sistemah programmirovaniya (naprimer v TurboC++ |-) vy dolzhny ispol'zovat' funkciyu sravneniya s prototipom int cmp (const void *a1, const void *a2); i vnutri nee yavno delat' privedenie tipa: cmps (const void *s1, const void *s2) { return strcmp(*(char **)s1, *(char **)s2); } ili mozhno postupit' sleduyushchim obrazom: int cmps(char **s1, char **s2){ return strcmp(*s1, *s2); } typedef int (*CMPS)(const void *, const void *); qsort((void *) array, ..., ..., (CMPS) cmps); Nakonec, vozmozhno i prosto ob®yavit' int cmps(const void *A, const void *B){ return strcmp(A, B); } Dlya massiva celyh goditsya takaya funkciya sravneniya: int arr[N]; ... cmpi(i1, i2) int *i1, *i2; { return *i1 - *i2; } Dlya massiva struktur, kotorye my sortiruem po celomu polyu key, goditsya struct XXX{ int key; ... } arr[N]; cmpXXX(st1, st2) struct XXX *st1, *st2; { return( st1->key - st2->key ); } Pust' u nas est' massiv long. Mozhno li ispol'zovat' long arr[N]; ... cmpl(L1, L2) long *L1, *L2; { return *L1 - *L2; } Otvet: okazyvaetsya, chto net. Funkciya cmpl dolzhna vozvrashchat' celoe, a raznost' dvuh long-ov imeet tip long. Poetomu kompilyator privodit etu raznost' k tipu int (kak pravilo obrubaniem starshih bitov). Pri etom (esli long-chisla byli veliki) rezul'tat mozhet izmenit' znak! Naprimer: main(){ int n; long a = 1L; long b = 777777777L; n = a - b; /* dolzhno by byt' otricatel'nym... */ printf( "%ld %ld %d\n", a, b, n ); } ____________________ |- TurboC - kompilyator Si v MS DOS, razrabotannyj firmoj Borland International. A. Bogatyrev, 1992-95 - 69 - Si v UNIX pechataet 1 777777777 3472. Funkciya sravneniya dolzhna vyglyadet' tak: cmpl(L1, L2) long *L1, *L2; { if( *L1 == *L2 ) return 0; if( *L1 < *L2 ) return (-1); return 1; } ili cmpl(L1, L2) long *L1, *L2; { return( *L1 == *L2 ? 0 : *L1 < *L2 ? -1 : 1 ); } poskol'ku vazhna ne velichina vozvrashchennogo znacheniya, a tol'ko ee znak. Uchtite, chto dlya ispol'zovaniya funkcii sravneniya vy dolzhny libo opredelit' funk- ciyu sravneniya do ee ispol'zovaniya v qsort(): int cmp(...){ ... } /* realizaciya */ ... qsort(..... , cmp); libo predvaritel'no ob®yavit' imya funkcii sravneniya, chtoby kompilyator ponimal, chto eto imenno funkciya: int cmp(); qsort(..... , cmp); ... int cmp(...){ ... } /* realizaciya */ 1.138. Pust' u nas est' dve programmy, pol'zuyushchiesya odnoj i toj zhe strukturoj dannyh W: a.c b.c -------------------------- ------------------------------ #include <fcntl.h> #include <fcntl.h> struct W{ int x,y; }a; struct W{ int x,y; }b; main(){ int fd; main(){ int fd; a.x = 12; a.y = 77; fd = open("f", O_RDONLY); fd = creat("f", 0644); read(fd, &b, sizeof b); write(fd, &a, sizeof a); close(fd); close(fd); printf("%d %d\n", b.x, b.y); } } CHto budet, esli my izmenim strukturu na struct W { long x,y; }; ili struct W { char c; int x,y; }; v fajle a.c i zabudem sdelat' eto v b.c? Budut li pravil'no rabotat' eti programmy? Iz nablyudaemogo mozhno sdelat' vyvod, chto esli dve ili neskol'ko programm (ili chastej odnoj programmy), razmeshchennye v raznyh fajlah, ispol'zuyut obshchie - tipy dannyh (typedef); - struktury i ob®edineniya; - konstanty (opredeleniya #define); - prototipy funkcij; to ih opredeleniya luchshe vynosit' v obshchij include-fajl (header-fajl), daby vse prog- rammy priderzhivalis' odnih i teh zhe obshchih soglashenij. Dazhe esli eti soglasheniya so A. Bogatyrev, 1992-95 - 70 - Si v UNIX vremenem izmenyatsya, to oni izmenyatsya vo vseh fajlah sinhronno i kak by sami soboj. V nashem sluchae ispravlyat' opredelenie struktury pridetsya tol'ko v include-fajle, a ne vyiskivat' vse mesta, gde ono napisano, ved' pri etom nemudreno kakoe-nibud' mesto i propustit'! W.h ----------------------- struct W{ long x, y; }; a.c b.c -------------------------- ------------------ #include <fcntl.h> #include <fcntl.h> #include "W.h" #include "W.h" struct W a; struct W b; main(){ ... main(){ ... printf("%ld... Krome togo, vynesenie obshchih fragmentov teksta programmy (opredelenij struktur, kons- tant, i.t.p.) v otdel'nyj fajl ekonomit nashi sily i vremya - vmesto togo, chtoby nabi- vat' odin i tot zhe tekst mnogo raz v raznyh fajlah, my teper' pishem v kazhdom fajle edinstvennuyu stroku - direktivu #include. Krome togo, ekonomitsya i mesto na diske, ved' programma stala koroche! Fajly vklyucheniya imeyut suffiks .h, chto oznachaet "header-file" (fajl-zagolovok). Sinhronnuyu perekompilyaciyu vseh programm v sluchae izmeneniya include-fajla mozhno zadat' v fajle Makefile - programme dlya koordinatora make|-: all: a b echo Zapusk a i b a ; b a: a.c W.h cc a.c -o a b: b.c W.h cc b.c -o b Pravila make imeyut vid cel': spisok_celej_ot_kotoryh_zavisit komanda komanda opisyvaet chto nuzhno sdelat', chtoby izgotovit' fajl cel' iz fajlov spisok_celej_ot_kotoryh_zavisit. Komanda vypolnyaetsya tol'ko esli fajl cel' eshche ne sushchestvuet, libo hot' odin iz fajlov sprava ot dvoetochiya yavlyaetsya bolee "molodym" (svezhim), chem celevoj fajl (smotri pole st_mtime i sisvyzov stat v glave pro UNIX). 1.139. Programma na Si mozhet byt' razmeshchena v neskol'kih fajlah. Kazhdyj fajl vystu- paet v roli "modulya", v kotorom sobrany shodnye po naznacheniyu funkcii i peremennye. Nekotorye peremennye i funkcii mozhno sdelat' nevidimymi dlya drugih modulej. Dlya etogo nado ob®yavit' ih static: - Ob®yavlenie peremennoj vnutri funkcii kak static delaet peremennuyu staticheskoj (t.e. ona budet sohranyat' svoe znachenie pri vyhode iz funkcii) i ogranichivaet ee vidimost' predelami dannoj funkcii. - Peremennye, opisannye vne funkcij, i tak yavlyayutsya staticheskimi (po klassu pamyati). Odnako slovo static i v etom sluchae pozvolyaet upravlyat' vidimost'yu etih peremennyh - oni budut vidimy tol'ko v predelah dannogo fajla. - Funkcii, ob®yavlennye kak static, takzhe vidimy tol'ko v predelah dannogo fajla. - Argumenty funkcii i lokal'nye (avtomaticheskie) peremennye funkcii i tak sushchest- vuyut tol'ko na vremya vyzova dannoj funkcii (pamyat' dlya nih vydelyaetsya v steke ____________________ |- Podrobnoe opisanie make smotri v dokumentacii po sisteme UNIX. A. Bogatyrev, 1992-95 - 71 - Si v UNIX pri vhode v funkciyu i unichtozhaetsya pri vyhode) i vidimy tol'ko vnutri ee tela. Argumenty funkcii nel'zya ob®yavlyat' static: f(x) static x; { x++; } nezakonno. Takim obrazom vse peremennye i funkcii v dannom fajle delyatsya na dve gruppy: - Vidimye tol'ko vnutri dannogo fajla (lokal'nye dlya modulya). Takie imena ob®yav- lyayutsya s ispol'zovaniem klyuchevogo slova static. V chastnosti est' eshche "bolee lokal'nye" peremennye - avtomaticheskie lokaly funkcij i ih formal'nye argumenty, kotorye vidimy tol'ko v predelah dannoj funkcii. Takzhe vidimy lish' v predelah odnoj funkcii staticheskie lokal'nye peremennye, ob®yavlennye v tele funkcii so slovom static. - Vidimye vo vseh fajlah (global'nye imena). Global'nye imena obrazuyut interfejs modulya i mogut byt' ispol'zovany v drugih modu- lyah. Lokal'nye imena izvne modulya nedostupny. Esli my ispol'zuem v fajle-module funkcii i peremennye, vhodyashchie v interfejs drugogo fajla-modulya, my dolzhny ob®yavit' ih kak extern ("vneshnie"). Dlya funkcij opi- sateli extern i int mozhno opuskat': // fajl A.c int x, y, z; // global'nye char ss[200]; // glob. static int v, w; // lokal'nye static char *s, p[20]; // lok. int f(){ ... } // glob. char *g(){ ... } // glob. static int h(){ ... } // lok. static char *sf(){ ... } // lok. int fi(){ ... } // glob. // fajl B.c extern int x, y; extern z; // int mozhno opustit' extern char ss[]; // razmer mozhno opustit' extern int f(); char *g(); // extern mozhno opustit' extern fi(); // int mozhno opustit' Horoshim tonom yavlyaetsya napisanie kommentariya - iz kakogo modulya ili biblioteki impor- tiruetsya peremennaya ili funkciya: extern int x, y; /* import from A.c */ char *tgetstr(); /* import from termlib */ Sleduyushchaya programma sobiraetsya iz fajlov A.c i B.c komandoj|= ____________________ |= Mozhno zadat' Makefile vida CFLAGS = -O AB: A.o B.o cc A.o B.o -o AB A.o: A.c cc -c $(CFLAGS) A.c B.o: B.c cc -c $(CFLAGS) B.c i sobirat' programmu prosto vyzyvaya komandu make. A. Bogatyrev, 1992-95 - 72 - Si v UNIX cc A.c B.c -o AB Pochemu kompilyator soobshchaet "x dvazhdy opredeleno"? fajl A.c fajl B.c ----------------------------------------- int x=12; int x=25; main(){ f(y) int *y; f(&x); { printf("%d\n", x); *y += x; } } Otvet: potomu, chto v kazhdom fajle opisana global'naya peremennaya x. Nado v odnom iz nih (ili v oboih srazu) sdelat' x lokal'nym imenem (isklyuchit' ego iz interfejsa modulya): static int x=...; Pochemu v sleduyushchem primere kompilyator soobshchaet "_f dvazhdy opredeleno"? fajl A.c fajl B.c ---------------------------------------------------- int x; extern int x; main(){ f(5); g(77); } g(n){ f(x+n); } f(n) { x=n; } f(m){ printf("%d\n", m); } Otvet: nado sdelat' v fajle B.c funkciyu f lokal'noj: static f(m)... Hot' v odnom fajle dolzhna byt' opredelena funkciya main, vyzyvaemaya sistemoj pri zapuske programmy. Esli takoj funkcii nigde net - kompilyator vydaet soobshchenie "_main neopredeleno". Funkciya main dolzhna byt' opredelena odin raz! V fajle ona mozhet naho- dit'sya v lyubom meste - ne trebuetsya, chtoby ona byla samoj pervoj (ili poslednej) funkciej fajla|=. 1.140. V chem oshibka? fajl A.c fajl B.c ---------------------------------------------------- extern int x; extern int x; main(){ x=2; f(){ f(); printf("%d\n", x); } } Otvet: peremennaya x v oboih fajlah ob®yavlena kak extern, v rezul'tate pamyat' dlya nee nigde ne vydelena, t.e. x ne opredelena ni v odnom fajle. Uberite odno iz slov extern! 1.141. V chem oshibka? fajl A.c fajl B.c ---------------------------------------------------- int x; extern double x; ... ... Tipy peremennyh ne sovpadayut. Bol'shinstvo kompilyatorov ne lovit takuyu oshibku, t.k. kazhdyj fajl kompiliruetsya otdel'no, nezavisimo ot ostal'nyh, a pri "sklejke" fajlov v ____________________ |= Esli vy pol'zuetes' "novym" stilem ob®yavleniya funkcij, no ne ispol'zuete proto- tipy, to sleduet opredelyat' kazhduyu funkciyu do pervogo mesta ee ispol'zovaniya, chtoby kompilyatoru v tochke vyzova byl izvesten ee zagolovok. |to privedet k tomu, chto main() okazhetsya poslednej funkciej v fajle - ee ne vyzyvaet nikto, zato ona vyzyvaet kogo-to eshche. A. Bogatyrev, 1992-95 - 73 - Si v UNIX obshchuyu vypolnyaemuyu programmu komponovshchik znaet lish' imena peremennyh i funkcij, no ne ih tipy i prototipy. V rezul'tate programma normal'no skompiliruetsya i soberetsya, no rezul'tat ee vypolneniya budet nepredskazuem! Poetomu ob®yavleniya extern tozhe polezno vynosit' v include-fajly: fajl proto.h ------------------ extern int x; fajl A.c fajl B.c ------------------ ------------------ #include "proto.h" #include "proto.h" int x; ... to, chto peremennaya x v A.c okazyvaetsya opisannoj i kak extern - vpolne dopustimo, t.k. v moment nastoyashchego ob®yavleniya etoj peremennoj eto slovo nachnet prosto ignoriro- vat'sya (lish' by tipy v ob®yavlenii s extern i bez nego sovpadali - inache oshibka!). 1.142. CHto pechataet programma i pochemu? int a = 1; /* primer Bjarne Stroustrup-a */ void f(){ int b = 1; static int c = 1; printf("a=%d b=%d c=%d\n", a++, b++, c++); } void main(){ while(a < 4) f(); } Otvet: a=1 b=1 c=1 a=2 b=1 c=2 a=3 b=1 c=3 1.143. Avtomaticheskaya peremennaya vidima tol'ko vnutri bloka, v kotorom ona opisana. CHto napechataet programma? /* fajl A.c */ int x=666; /*glob.*/ main(){ f(3); printf(" ::x = %d\n", x); g(2); g(5); printf(" ::x = %d\n", x); } g(n){ static int x=17; /*vidima tol'ko v g*/ printf("g::x = %2d g::n = %d\n", x++, n); if(n) g(n-1); else x = 0; } /* fajl B.c */ extern x; /*global*/ f(n){ /*lokal funkcii*/ x++; /*global*/ { int x; /*lokal bloka*/ x = n+1; /*lokal*/ A. Bogatyrev, 1992-95 - 74 - Si v UNIX n = 2*x; /*lokal*/ } x = n-1; /*global*/ } 1.144. Funkciya, kotoraya - ne soderzhit vnutri sebya staticheskih peremennyh, hranyashchih sostoyanie processa obrabotki dannyh (funkciya bez "pamyati"); - poluchaet znacheniya parametrov tol'ko cherez svoi argumenty (no ne cherez global'nye staticheskie peremennye); - vozvrashchaet znacheniya tol'ko cherez argumenty, libo kak znachenie funkcii (cherez return); nazyvaetsya reenterabel'noj (povtorno vhodimoj) ili chistoj (pure). Takaya funkciya mozhet parallel'no (ili psevdoparallel'no) ispol'zovat'sya neskol'kimi "potokami" obra- botki informacii v nashej programme, bez kakogo-libo nepredvidennogo vliyaniya etih "potokov obrabotki" drug na druga. Pervyj punkt trebovanij pozvolyaet funkcii ne zaviset' ni ot kakogo konkretnogo processa obrabotki dannyh, t.k. ona ne "pomnit" obrabotannyh eyu ranee dannyh i ne stroit svoe povedenie v zavisimosti ot nih. Vtorye dva punkta - eto trebovanie, chtoby vse bez isklyucheniya puti peredachi dannyh v funkciyu i iz nee (interfejs funkcii) byli perechisleny v ee zagolovke. |to lishaet funkciyu "pobochnyh effektov", ne predusmotrennyh programmistom pri ee vyzove (programmist obychno smotrit tol'ko na zagolovok funkcii, i ne vyiskivaet "tajnye" svyazi funkcii s programmoj cherez global'nye peremennye, esli tol'ko eto special'no ne ogovoreno). Vot primer ne reenterabel'noj funkcii: FILE *fp; ... /* global'nyj argument */ char delayedInput () { static char prevchar; /* pamyat' */ char c; c = prevchar; prevchar = getc (fp); return c; } A vot ee reenterabel'nyj ekvivalent: char delayedInput (char *prevchar, FILE *fp) { char c; c = *prevchar; *prevchar = getc (fp); return c; } /* vyzov: */ FILE *fp1, *fp2; char prev1, prev2, c1, c2; ... x1 = delayedInput (&prev1, fp1); x2 = delayedInput (&prev2, fp2); ... Kak vidim, vse "zapominayushchie" peremennye (t.e. prevchar) vyneseny iz samoj funkcii i podayutsya v nee v vide argumenta. Reenterabel'nye funkcii nezavisimy ot ostal'noj chasti programmy (ih mozhno skopi- rovat' v drugoj programmnyj proekt bez izmenenij), bolee ponyatny (poskol'ku vse zat- ragivaemye imi vneshnie peremennye perechisleny kak argumenty, ne nado vyiskivat' v tele funkcii global'nyh peremennyh, peredayushchih znachenie v/iz funkcii, t.e. eta funk- ciya ne imeet pobochnyh vliyanij), bolee nadezhny (hotya by potomu, chto kompilyator v sos- toyanii proverit' prototip takoj funkcii i predupredit' vas, esli vy zabyli zadat' kakoj-to argument; esli zhe argumenty peredayutsya cherez global'nye peremennye - vy mozhete zabyt' proinicializirovat' kakuyu-to iz nih). Starajtes' delat' funkcii reen- terabel'nymi! A. Bogatyrev, 1992-95 - 75 - Si v UNIX Vot eshche odin primer na etu temu. Ne-reenterabel'nyj variant: int x, y, result; int f (){ static int z = 4; y = x + z; z = y - 1; return x/2; } Vyzov: x=13; result = f(); printf("%d\n", y); A vot reenterabel'nyj ekvivalent: int y, result, zmem = 4; int f (/*IN*/ int x, /*OUT*/ int *ay, /*INOUT*/ int *az){ *az = (*ay = x + *az) - 1; return x/2; } Vyzov: result = f(13, &y, &zmem); printf("%d\n", y); 1.145. To, chto format zagolovka funkcii dolzhen byt' izvesten kompilyatoru do momenta ee ispol'zovaniya, pobuzhdaet nas pomeshchat' opredelenie funkcii do tochki ee vyzova. Tak, esli main vyzyvaet f, a f vyzyvaet g, to v fajle funkcii raspolozhatsya v poryadke g() { } f() { ... g(); ... } main(){ ... f(); ... } Programma obychno razrabatyvaetsya "sverhu-vniz" - ot main k detalyam. Si zhe vynuzhdaet nas razmeshchat' funkcii v programme v obratnom poryadke, i v itoge programma chitaetsya snizu-vverh - ot detalej k main, i chitat' ee sleduet ot konca fajla k nachalu! Tak my vynuzhdeny pisat', chtoby udovletvorit' Si-kompilyator: #include <stdio.h> unsigned long g(unsigned char *s){ const int BITS = (sizeof(long) * 8); unsigned long sum = 0; for(;*s; s++){ sum ^= *s; /* cyclic rotate left */ sum = (sum<<1)|(sum>>(BITS-1)); } return sum; } void f(char *s){ printf("%s %lu\n", s, g((unsigned char *)s)); } int main(int ac, char *av[]){ int i; for(i=1; i < ac; i++) f(av[i]); return 0; } A vot kak my razrabatyvaem programmu: A. Bogatyrev, 1992-95 - 76 - Si v UNIX #include <stdio.h> int main(int ac, char *av[]){ int i; for(i=1; i < ac; i++) f(av[i]); return 0; } void f(char *s){ printf("%s %lu\n", s, g((unsigned char *)s)); } unsigned long g(unsigned char *s){ const int BITS = (sizeof(long) * 8); unsigned long sum = 0; for(;*s; s++){ sum ^= *s; /* cyclic rotate left */ sum = (sum<<1)|(sum>>(BITS-1)); } return sum; } i vot kakuyu rugan' proizvodit Si-kompilyator v otvet na etu programmu: "0000.c", line 10: identifier redeclared: f current : function(pointer to char) returning void previous: function() returning int : "0000.c", line 7 "0000.c", line 13: identifier redeclared: g current : function(pointer to uchar) returning ulong previous: function() returning int : "0000.c", line 11 Resheniem problemy yavlyaetsya - zadat' prototipy (ob®yavleniya zagolovkov) vseh funkcij v nachale fajla (ili dazhe vynesti ih v header-fajl). #include <stdio.h> int main(int ac, char *av[]); void f(char *s); unsigned long g(unsigned char *s); ... Togda funkcii budet mozhno raspolagat' v tekste v lyubom poryadke. 1.146. Rassmotrim process sborki programmy iz neskol'kih fajlov na yazyke Si. Pust' my imeem fajly file1.c, file2.c, file3.c (odin iz nih dolzhen soderzhat' sredi drugih funkcij funkciyu main). Klyuch kompilyatora -o zastavlyaet sozdavat' vypolnyaemuyu prog- rammu s imenem, ukazannym posle etogo klyucha. Esli etot klyuch ne zadan - budet sozdan vypolnyaemyj fajl a.out cc file1.c file2.c file3.c -o file My poluchili vypolnyaemuyu programmu file. |to ekvivalentno 4-m komandam: cc -c file1.c poluchitsya file1.o cc -c file2.c file2.o cc -c file3.c file3.o cc file1.o file2.o file3.o -o file Klyuch -c zastavlyaet kompilyator prevratit' fajl na yazyke Si v "ob®ektnyj" fajl A. Bogatyrev, 1992-95 - 77 - Si v UNIX (soderzhashchij mashinnye komandy; ne budem vdavat'sya v podrobnosti). CHetvertaya komanda "skleivaet" ob®ektnye fajly v edinoe celoe - vypolnyaemuyu programmu|-. Pri etom, esli kakie-to funkcii, ispol'zuemye v nashej programme, ne byli opredeleny (t.e. sprogram- mirovany nami) ni v odnom iz nashih fajlov - budet prosmotrena biblioteka standartnyh funkcij. Esli zhe kakih-to funkcij ne okazhetsya i tam - budet vydano soobshchenie ob oshibke. Esli u nas uzhe est' kakie-to gotovye ob®ektnye fajly, my mozhem translirovat' tol'ko novye Si-fajly: cc -c file4.c cc file1.o file2.o file3.o file4.o -o file ili (chto to zhe samoe, no cc sam razberetsya, chto nado delat') cc file1.o file2.o file3.o file4.c -o file Sushchestvuyushchie u nas ob®ektnye fajly s otlazhennymi funkciyami udobno sobrat' v biblio- teku - fajl special'noj struktury, soderzhashchij vse ukazannye fajly (vse fajly skleeny v odin dlinnyj fajl, razdelyayas' special'nymi zagolovkami, sm. include-fajl <ar.h>): ar r file.a file1.o file2.o file3.o Budet sozdana biblioteka file.a, soderzhashchaya perechislennye .o fajly (imena bibliotek v UNIX imeyut suffiks .a - ot slova archive, arhiv). Posle etogo mozhno ispol'zovat' biblioteku: cc file4.o file5.o file.a -o file Mehanizm takov: esli v fajlah file4.o i file5.o ne opredelena kakaya-to funkciya (funk- cii), to prosmatrivaetsya biblioteka, i v spisok fajlov dlya "sklejki" dobavlyaetsya fajl iz biblioteki, soderzhashchij opredelenie etoj funkcii (iz biblioteki on ne udalyaetsya!). Tonkost': iz biblioteki berutsya ne VSE fajly, a lish' te, kotorye soderzhat opredeleniya nedostayushchih funkcij|=. Esli, v svoyu ochered', fajly, izvlekaemye iz biblioteki, budut soderzhat' neopredelennye funkcii - biblioteka (biblioteki) budut prosmotreny eshche raz i.t.d. (na samom dele dostatochno maksimum dvuh prohodov, tak kak pri pervom prosmotre biblioteki mozhno sostavit' ee katalog: gde kakie funkcii v nej soderzhatsya i kogo vyzyvayut). Mozhno ukazyvat' i neskol'ko bibliotek: cc file6.c file7.o \ file.a mylib.a /lib/libLIBR1.a -o file Takim obrazom, v komande cc mozhno smeshivat' imena fajlov: ishodnyh tekstov na Si .c, ob®ektnyh fajlov .o i fajlov-bibliotek .a. Prosmotr bibliotek, nahodyashchihsya v standartnyh mestah (katalogah /lib i /usr/lib), mozhno vklyuchit' i eshche odnim sposobom: ukazav klyuch -l. Esli biblioteka nazyvaetsya /lib/libLIBR1.a ili /usr/lib/libLIBR2.a to podklyuchenie delaetsya klyuchami -lLIBR1 i -lLIBR2 ____________________ |- Na samom dele, dlya "sklejki" ob®ektnyh fajlov v vypolnyaemuyu programmu, komanda /bin/cc vyzyvaet programmu /bin/ld - link editor, linker, redaktor svyazej, komponov- shchik. |= Poetomu biblioteka mozhet byt' ochen' bol'shoj, a k nashej programme "prikleitsya" lish' nebol'shoe chislo fajlov iz nee. V svyazi s etim stremyatsya delat' fajly, pomeshchaemye v biblioteku, kak mozhno men'she: 1 funkciya; libo "pachka" funkcij, vyzyvayushchih drug druga. A. Bogatyrev, 1992-95 - 78 - Si v UNIX sootvetstvenno. cc file1.c file2.c file3.o mylib.a -lLIBR1 -o file Spisok bibliotek i klyuchej -l dolzhen idti posle imen vseh ishodnyh .c i ob®ektnyh .o fajlov. Biblioteka standartnyh funkcij yazyka Si /lib/libc.a (klyuch -lc) podklyuchaetsya avtomaticheski ("podklyuchit'" biblioteku - znachit vynudit' kompilyator prosmatrivat' ee pri sborke, esli kakie-to funkcii, ispol'zovannye vami, ne byli vami opredeleny), to est' prosmatrivaetsya vsegda (imenno eta biblioteka soderzhit kody, naprimer, dlya printf, strcat, read). Mnogie prikladnye pakety funkcij postavlyayutsya imenno v vide bibliotek. Takie biblioteki sostoyat iz ryada .o fajlov, soderzhashchih ob®ektnye kody dlya razlichnyh funkcij (t.e. funkcii v skompilirovannom vide). Ishodnye teksty ot bol'shinstva bibliotek ne postavlyayutsya (tak kak yavlyayutsya kommercheskoj tajnoj). Tem ne menee, vy mozhete ispol'- zovat' eti funkcii, tak kak vam predostavlyayutsya razrabotchikom: - opisanie (dokumentaciya). - include-fajly, soderzhashchie formaty dannyh ispol'zuemye funkciyami biblioteki (imenno eti fajly vklyuchalis' #include v ishodnye teksty bibl. funkcij. Teper' uzhe vy dolzhny vklyuchat' ih v svoyu programmu). Takim obrazom vy znaete, kak nado vyzyvat' bibliotechnye funkcii i kakie struktury dannyh vy dolzhny ispol'zovat' v svoej programme dlya obrashcheniya k nim (hotya i ne imeete tekstov samih bibliotechnyh funkcij, t.e. ne znaete, kak oni ustroeny. Naprimer, vy chasto ispol'zuete printf(), no zadumyvaetes' li vy o ee vnutrennem ustrojstve?). Nekotorye bibliotechnye funkcii mogut byt' voobshche napisany ne na Si, a na assemblere ili drugom yazyke programmirovaniya|-|-. Eshche raz obrashchayu vashe vnimanie, chto biblioteka soderzhit ne ishodnye teksty funkcij, a skompilirovannye kody (i include-fajly soder- zhat (kak pravilo) ne teksty funkcij, a tol'ko opisanie formatov dannyh)! Biblioteka mozhet takzhe soderzhat' staticheskie dannye, vrode massivov strok-soobshchenij ob oshibkah. Posmotret' spisok fajlov, soderzhashchihsya v biblioteke, mozhno komandoj ar tv imyaFajlaBiblioteki a spisok imen funkcij - komandoj nm imyaFajlaBiblioteki Izvlech' fajl (fajly) iz arhiva (skopirovat' ego v tekushchij katalog), libo udalit' ego iz biblioteki mozhno komandami ar x imyaFajlaBiblioteki imyaFajla1 ... ar d imyaFajlaBiblioteki imyaFajla1 ... gde ... oznachaet spisok imen fajlov. "Licom" bibliotek sluzhat prilagaemye k nim include-fajly. Sistemnye include- fajly, soderzhashchie obshchie formaty dannyh dlya standartnyh bibliotechnyh funkcij, hranyatsya v kataloge /usr/include i podklyuchayutsya tak: dlya /usr/include/fajl.h nado #include <fajl.h> dlya /usr/include/sys/fajl.h #include <sys/fajl.h> ____________________ |-|- Obratite vnimanie, chto bibliotechnye funkcii ne yavlyayutsya chast'yu YAZYKA Si kak ta- kovogo. To, chto v drugih yazykah (PL/1, Algol-68, Pascal) yavlyaetsya chast'yu yazyka (vstroeno v yazyk)- v Si vyneseno na uroven' bibliotek. Naprimer, v Si net operatora vyvoda; funkciya vyvoda printf - eto bibliotechnaya funkciya (hotya i obshcheprinyataya). Ta- kim obrazom moshch' yazyka Si sostoit imenno v tom, chto on pozvolyaet ispol'zovat' funk- cii, napisannye drugimi programmistami i dazhe na drugih yazykah, t.e. yavlyaetsya funkci- onal'no rasshiryaemym. A. Bogatyrev, 1992-95 - 79 - Si v UNIX (sys - eto katalog, gde opisany formaty dannyh, ispol'zuemyh yadrom OS i sistemnymi vyzovami). Vashi sobstvennye include-fajly (posmotrite v predydushchij razdel!) ishchutsya v tekushchem kataloge i vklyuchayutsya pri pomoshchi #include "fajl.h" /* ./fajl.h */ #include "../h/fajl.h" /* ../h/fajl.h */ #include "/usr/my/fajl.h" /* /usr/my/fajl.h */ Nepremenno izuchite soderzhimoe standartnyh include-fajlov v svoej sisteme! V kachestve rezyume - shema, poyasnyayushchaya "prevrashcheniya" Si-programmy iz teksta na yazyke programmirovaniya v vypolnyaemyj kod: vse fajly .c mogut ispol'zovat' obshchie include-fajly; ih podstanovku v tekst, a takzhe obrabotku #define proizvedet prepro- cessor cpp file1.c file2.c file3.c | | | "preprocessor" | cpp | cpp | cpp | | | "kompilyaciya" | cc -c | cc -c | cc -c | | | file1.o file2.o file3.o | | | -----------*----------- | Neyavno dobavyatsya: ld |<----- /lib/libc.a (bibl. stand. funkcij) | /lib/crt0.o (starter) "svyazyvanie" | "komponovka" |<----- YAvno ukazannye biblioteki: | -lm /lib/libm.a V a.out 1.147. Naposledok - prostoj, no zhiznenno vazhnyj sovet. Esli vy pishete programmu, kotoruyu vstavite v sistemu dlya chastogo ispol'zovaniya, pomestite v ishodnyj tekst etoj programmy identifikacionnuyu stroku napodobie static char id[] = "This is /usr/abs/mybin/xprogram"; Togda v sluchae avarii v fajlovoj sisteme, esli vdrug vash fajl "poteryaetsya" (to est' u nego propadet imya - naprimer iz-za porchi kataloga), to on budet najden programmoj proverki fajlovoj sistemy - fsck - i pomeshchen v katalog /lost+found pod special'nym kodovym imenem, nichego obshchego ne imeyushchim so starym. CHtoby ponyat', chto eto byl za fajl i vo chto ego sleduet pereimenovat' (chtoby vosstanovit' pravil'noe imya), my pri- menim komandu strings imya_fajla |ta komanda pokazhet vse dlinnye stroki iz pechatnyh simvolov, soderzhashchiesya v dannom fajle, v chastnosti i nashu stroku id[]. Uvidev ee, my srazu pojmem, chto fajl nado pereimenovat' tak: mv imya_fajla /usr/abs/mybin/xprogram 1.148. Gde razmeshchat' include-fajly i kak programma uznaet, gde zhe oni lezhat? Stan- dartnye sistemnye include-fajly razmeshcheny v /usr/include i podkatalogah. Esli my pishem nekuyu svoyu programmu (proekt) i ispol'zuem direktivy #include "imyaFajla.h" A. Bogatyrev, 1992-95 - 80 - Si v UNIX to obychno include-fajly imyaFajla.h lezhat v tekushchem kataloge (tam zhe, gde i fajly s programmoj na Si). Odnako my mozhem pomeshchat' VSE nashi include-fajly v odno mesto (skazhem, izvestnoe gruppe programmistov, rabotayushchih nad odnim i tem zhe proektom). Horoshee mesto dlya vseh vashih lichnyh include-fajlov - katalog (vami sozdannyj) $HOME/include gde $HOME - vash domashnij katalog. Horoshee mesto dlya obshchih include-fajlov - katalog /usr/local/include Kak skazat' kompilyatoru, chto #include "" fajly nado brat' iz opredelennogo mesta, a ne iz tekushchego kataloga? |to delaet klyuch kompilyatora cc -Iimya_kataloga ... Naprimer: /* Fajl x.c */ #include "x.h" int main(int ac, char *av[]){ .... return 0; } I fajl x.h nahoditsya v kataloge /home/abs/include/x.h (/home/abs - moj domashnij kata- log). Zapusk programmy na kompilyaciyu vyglyadit tak: cc -I/home/abs/include -O x.c -o x ili cc -I$HOME/include -O x.c -o x Ili, esli moya programma x.c nahoditsya v /home/abs/progs cc -I../include -O x.c -o x Klyuch -O zadaet vyzov kompilyatora s optimizaciej. Klyuch -I okazyvaet vliyanie i na #include <> direktivy tozhe. Dlya OS Solaris na mashinah Sun programmy dlya okonnoj sistemy X Window System soderzhat stroki vrode #include <X11/Xlib.h> #include <X11/Xutil.h> Na Sun eti fajly nahodyatsya ne v /usr/include/X11, a v /usr/openwin/include/X11. Poe- tomu zapusk na kompilyaciyu okonnyh programm na Sun vyglyadit tak: cc -O -I/usr/openwin/include xprogram.c \ -o xprogram -L/usr/openwin/lib -lX11 gde -lX11 zadaet podklyuchenie graficheskoj okonnoj biblioteki Xlib. Esli include-fajly nahodyatsya vo mnogih katalogah, to mozhno zadat' poisk v nes- kol'kih katalogah, k primeru: cc -I/usr/openwin/include -I/usr/local/include -I$HOME/include ... A. Bogatyrev, 1992-95 - 81 - Si v UNIX Massiv predstavlyaet soboj agregat iz neskol'kih peremennyh odnogo i togo zhe tipa. Massiv s imenem a iz LENGTH elementov tipa TYPE ob®yavlyaetsya tak: TYPE a[LENGTH]; |to sootvetstvuet tomu, chto ob®yavlyayutsya peremennye tipa TYPE so special'nymi imenami a[0], a[1], ..., a[LENGTH-1]. Kazhdyj element massiva imeet svoj nomer - indeks. Dostup k x-omu elementu massiva osushchestvlyaetsya pri pomoshchi operacii indeksacii: int x = ... ; /* celochislennyj indeks */ TYPE value = a[x]; /* chtenie x-ogo elementa */ a[x] = value; /* zapis' v x-tyj element */ V kachestve indeksa mozhet ispol'zovat'sya lyuboe vyrazhenie, vydayushchee znachenie celogo tipa: char, short, int, long. Indeksy elementov massiva v Si nachinayutsya s 0 (a ne s 1), i indeks poslednego elementa massiva iz LENGTH elementov - eto LENGTH-1 (a ne LENGTH). Poetomu cikl po vsem elementam massiva - eto TYPE a[LENGTH]; int indx; for(indx=0; indx < LENGTH; indx++) ...a[indx]...; indx < LENGTH ravnoznachno indx <= LENGTH-1. Vyhod za granicy massiva (popytka chteniya/zapisi nesushchestvuyushchego elementa) mozhet privesti k nepredskazuemym rezul'tatam i povedeniyu programmy. Otmetim, chto eto odna iz samyh rasprostranennyh oshibok. Staticheskie massivy mozhno ob®yavlyat' s inicializaciej, perechislyaya znacheniya ih elementov v {} cherez zapyatuyu. Esli zadano men'she elementov, chem dlina massiva - ostal'nye elementy schitayutsya nulyami: int a10[10] = { 1, 2, 3, 4 }; /* i 6 nulej */ Esli pri opisanii massiva s inicializaciej ne ukazat' ego razmer, on budet podschitan kompilyatorom: int a3[] = { 1, 2, 3 }; /* kak by a3[3] */ V bol'shinstve sovremennyh komp'yuterov (s fon-Nejmanovskoj arhitekturoj) pamyat' predstavlyaet soboj massiv bajt. Kogda my opisyvaem nekotoruyu peremennuyu ili massiv, v pamyati vydelyaetsya nepreryvnaya oblast' dlya hraneniya etoj peremennoj. Vse bajty pamyati komp'yutera pronumerovany. Nomer bajta, s kotorogo nachinaetsya v pamyati nasha peremennaya, nazyvaetsya adresom etoj peremennoj (adres mozhet imet' i bolee slozhnuyu strukturu, chem prosto celoe chislo - naprimer sostoyat' iz nomera segmenta pamyati i nomera bajta v etom segmente). V Si adres peremennoj mozhno poluchit' s pomoshch'yu opera- cii vzyatiya adresa &. Pust' u nas est' peremennaya var, togda &var - ee adres. Adres nel'zya prisvaivat' celoj peremennoj; dlya hraneniya adresov ispol'zuyutsya ukazateli (smotri nizhe). Dannoe mozhet zanimat' neskol'ko podryad idushchih bajt. Razmer v bajtah uchastka pamyati, trebuemogo dlya hraneniya znacheniya tipa TYPE, mozhno uznat' pri pomoshchi operacii sizeof(TYPE), a razmer peremennoj - pri pomoshchi sizeof(var). Vsegda vypolnyaetsya sizeof(char)==1. V nekotoryh mashinah adresa peremennyh (a takzhe agregatov dannyh - massivov i struktur) kratny sizeof(int) ili sizeof(double) - eto tak nazyvaemoe "vyravnivanie (alignment) dannyh na granicu tipa int". |to pozvolyaet delat' dostup k dannym bolee bystrym (apparatura rabotaet effektivnee). YAzyk Si predostavlyaet nam sredstvo dlya raboty s adresami dannyh - ukazateli (pointer)|-. Ukazatel' fizicheski - eto adres nekotoroj peremennoj ("ukazuemoj" pere- mennoj). Otlichie ukazatelej ot mashinnyh adresov sostoit v tom, chto ukazatel' mozhet soderzhat' adresa dannyh tol'ko opredelennogo tipa. Ukazatel' ptr, kotoryj mozhet uka- zyvat' na dannye tipa TYPE, opisyvaetsya tak: TYPE var; /* peremennaya */ TYPE *ptr; /* ob®yavlenie uk-lya */ ptr = & var; A. Bogatyrev, 1992-95 - 82 - Si v UNIX V dannom sluchae my zanesli v ukazatel'nuyu peremennuyu ptr adres peremennoj var. Budem govorit', chto ukazatel' ptr ukazyvaet na peremennuyu var (ili, chto ptr ustanovlen na var). Pust' TYPE ravno int, i u nas est' massiv i ukazateli: int array[LENGTH], value; int *ptr, *ptr1; Ustanovim ukazatel' na x-yj element massiva ptr = & array[x]; Ukazatelyu mozhno prisvoit' znachenie drugogo ukazatelya na takoj zhe tip. V rezul'tate oba ukazatelya budut ukazyvat' na odno i to zhe mesto v pamyati: ptr1 = ptr; My mozhem izmenyat' ukazuemuyu peremennuyu pri pomoshchi operacii * *ptr = 128; /* zanesti 128 v ukazuemuyu perem. */ value = *ptr; /* prochest' ukazuemuyu peremennuyu */ V dannom sluchae my zanosim i zatem chitaem znachenie peremennoj array[x], na kotoruyu postavlen ukazatel', to est' *ptr oznachaet sejchas array[x] Takim obrazom, operaciya * (znachenie po adresu) okazyvaetsya obratnoj k operacii & (vzyatie adresa): & (*ptr) == ptr i * (&value) == value Operaciya * ob®yasnyaet smysl opisaniya TYPE *ptr; ono oznachaet, chto znachenie vyrazheniya *ptr budet imet' tip TYPE. Nazvanie zhe tipa samogo ukazatelya - eto (TYPE *). V chast- nosti, TYPE mozhet sam byt' ukazatel'nym tipom - mozhno ob®yavit' ukazatel' na ukaza- tel', vrode char **ptrptr; Imya massiva - eto konstanta, predstavlyayushchaya soboj ukazatel' na 0-oj element mas- siva. |tot ukazatel' otlichaetsya ot obychnyh tem, chto ego nel'zya izmenit' (ustanovit' na druguyu peremennuyu), poskol'ku on sam hranitsya ne v peremennoj, a yavlyaetsya prosto nekotorym postoyannym adresom. massiv ukazatel' ____________ _____ array: | array[0] | ptr:| * | | array[1] | | | array[2] |<--------- sejchas raven &array[2] | ... | Sledstviem takoj interpretacii imen massivov yavlyaetsya to, chto dlya togo chtoby posta- vit' ukazatel' na nachalo massiva, nado pisat' ptr = array; ili ptr = &array[0]; no ne ptr = &array; Operaciya & pered odinokim imenem massiva ne nuzhna i nedopustima! Takoe rodstvo ukazatelej i massivov pozvolyaet nam primenyat' operaciyu * k imeni massiva: value = *array; oznachaet to zhe samoe, chto i value = array[0]; Ukazateli - ne celye chisla! Hotya fizicheski eto i nomera bajtov, adresnaya arif- metika otlichaetsya ot obychnoj. Tak, esli dan ukazatel' TYPE *ptr; i nomer bajta (adres), na kotoryj ukazyvaet ptr, raven byteaddr, to ptr = ptr + n; /* n - celoe, mozhet byt' i < 0 */ zastavit ptr ukazyvat' ne na bajt nomer byteaddr + n, a na bajt nomer A. Bogatyrev, 1992-95 - 83 - Si v UNIX byteaddr + (n * sizeof(TYPE)) to est' pribavlenie edinicy k ukazatelyu prodvigaet adres ne na 1 bajt, a na razmer ukazyvaemogo ukazatelem tipa dannyh! Pust' ukazatel' ptr ukazyvaet na x-yj element massiva array. Togda posle TYPE *ptr2 = array + L; /* L - celoe */ TYPE *ptr1 = ptr + N; /* N - celoe */ ptr += M; /* M - celoe */ ukazateli ukazyvayut na ptr1 == &array[x+N] i ptr == &array[x+M] ptr2 == &array[L] Esli my teper' rassmotrim cepochku ravenstv *ptr2 = *(array + L) = *(&array[L]) = array[L] to poluchim OSNOVNOE PRAVILO: pust' ptr - ukazatel' ili imya massiva. Togda operacii indeksacii, vzyatiya znacheniya po adresu, vzyatiya adresa i pribavleniya celogo k ukazatelyu svyazany sootnosheniyami: ptr[x] tozhdestvenno *(ptr+x) &ptr[x] tozhdestvenno ptr+x (tozhdestva verny v obe storony), v tom chisle pri x==0 i x < 0. Tak chto, naprimer, ptr[-1] oznachaet *(ptr-1) ptr[0] oznachaet *ptr Ukazateli mozhno indeksirovat' podobno massivam. Rassmotrim primer: /* indeks: 0 1 2 3 4 */ double numbers[5] = { 0.0, 1.0, 2.0, 3.0, 4.0 }; double *dptr = &numbers[2]; double number = dptr[2]; /* ravno 4.0 */ numbers: [0] [1] [2] [3] [4] | [-2] [-1] [0] [1] [2] dptr poskol'ku esli dptr = &numbers[x] = numbers + x to dptr[i] = *(dptr + i) = = *(numbers + x + i) = numbers[x + i] Ukazatel' na odin tip mozhno preobrazovat' v ukazatel' na drugoj tip: takoe pre- obrazovanie ne vyzyvaet generacii kakih-libo mashinnyh komand, no zastavlyaet kompilya- tor izmenit' parametry adresnoj arifmetiki, a takzhe operacii vyborki dannogo po uka- zatelyu (sobstvenno, raznica v ukazatelyah na dannye raznyh tipov sostoit tol'ko v raz- merah ukazuemyh tipov; a takzhe v generacii komand `->' dlya vyborki polej struktur, esli ukazatel' - na strukturnyj tip). Celye (int ili long) chisla inogda mozhno preobrazovyvat' v ukazateli. |tim pol'- zuyutsya pri napisanii drajverov ustrojstv dlya dostupa k registram po fizicheskim adre- sam, naprimer: A. Bogatyrev, 1992-95 - 84 - Si v UNIX unsigned short *KISA5 = (unsigned short *) 0172352; Zdes' voznikayut dva tonkih momenta: 1. Kak uzhe bylo skazano, adresa dannyh chasto vyravnivayutsya na granicu nekotorogo tipa. My zhe mozhem zadat' nevyrovnennoe celoe znachenie. Takoj adres budet nekorrekten. 2. Struktura adresa, podderzhivaemaya processorom, mozhet ne sootvetstvovat' formatu celyh (ili dlinnyh celyh) chisel. Tak obstoit delo s IBM PC 8086/80286, gde adres sostoit iz pary short int chisel, hranyashchihsya v pamyati podryad. Odnako ves' adres (esli rassmatrivat' eti dva chisla kak odno dlinnoe celoe) ne yavlyaetsya obychnym long-chislom, a vychislyaetsya bolee slozhnym sposobom: adresnaya para SEGMENT:OFFSET preobrazuetsya tak unsigned short SEGMENT, OFFSET; /*16 bit: [0..65535]*/ unsigned long ADDRESS = (SEGMENT << 4) + OFFSET; poluchaetsya 20-i bitnyj fizicheskij adres ADDRESS Bolee togo, na mashinah s dispetcherom pamyati, adres, hranimyj v ukazatele, yavlya- etsya "virtual'nym" (t.e. voobrazhaemym, nenastoyashchim) i mozhet ne sovpadat' s fizi- cheskim adresom, po kotoromu dannye hranyatsya v pamyati komp'yutera. V pamyati mozhet odnovremenno nahodit'sya neskol'ko programm, v kazhdoj iz nih budet svoya sistema adresacii ("adresnoe prostranstvo"), otschityvayushchaya virtual'nye adresa s nulya ot nachala oblasti pamyati, vydelennoj dannoj programme. Preobrazovanie virtual'nyh adresov v fizicheskie vypolnyaetsya apparatno. V Si prinyato soglashenie, chto ukazatel' (TYPE *)0 oznachaet "ukazatel' ni na chto". On yavlyaetsya prosto priznakom, ispol'zuemym dlya oboznacheniya nesushchestvuyushchego adresa ili konca cepochki ukazatelej, i imeet special'noe oboznachenie NULL. Obrashchenie (vyborka ili zapis' dannyh) po etomu ukazatelyu schitaetsya nekorrektnym (krome sluchaya, kogda vy pishete mashinno-zavisimuyu programmu i rabotaete s fizicheskimi adresami). Otmetim, chto ukazatel' mozhno napravit' v nepravil'noe mesto - na uchastok pamyati, soderzhashchij dannye ne togo tipa, kotoryj zadan v opisanii ukazatelya; libo voobshche soderzhashchij neizvestno chto: int i = 2, *iptr = &i; double x = 12.76; iptr += 7; /* kuda zhe on ukazal ?! */ iptr = (int *) &x; i = *iptr; Samo prisvaivanie ukazatelyu nekorrektnogo znacheniya eshche ne yavlyaetsya oshibkoj. Oshibka vozniknet lish' pri obrashchenii k dannym po etomu ukazatelyu (takie oshibki dovol'no tyazhelo iskat'!). Pri peredache imeni massiva v kachestve parametra funkcii, kak argument peredaetsya ne kopiya SAMOGO MASSIVA (eto zanyalo by slishkom mnogo mesta), a kopiya ADRESA 0-ogo elementa etogo massiva (t.e. ukazatel' na nachalo massiva). f(int x ){ x++; } g(int xa[]){ xa[0]++; } int a[2] = { 1, 1 }; /* ob®yavlenie s inicializaciej */ main(){ f(a[0]); printf("%d\n",a[0]); /* a[0] ostalos' ravno 1*/ g(a ); printf("%d\n",a[0]); /* a[0] stalo ravno 2 */ } V f() v kachestve argumenta peredaetsya kopiya elementa a[0] (i izmenenie etoj kopii ne privodit k izmeneniyu samogo massiva - argument x yavlyaetsya lokal'noj peremennoj v f()), a v g() takim lokalom yavlyaetsya ADRES massiva a - no ne sam massiv, poetomu xa[0]++ izmenyaet sam massiv a (zato, naprimer, xa++ vnutri g() izmenilo by lish' lokal'nuyu ukazatel'nuyu peremennuyu xa, no ne adres massiva a). Zamet'te, chto poskol'ku massiv peredaetsya kak ukazatel' na ego nachalo, to razmer massiva v ob®yavlenii argumenta mozhno ne ukazyvat'. |to pozvolyaet odnoj funkciej A. Bogatyrev, 1992-95 - 85 - Si v UNIX obrabatyvat' massivy raznoj dliny: vmesto Fun(int xa[5]) { ... } mozhno Fun(int xa[] ) { ... } ili dazhe Fun(int *xa ) { ... } Esli funkciya dolzhna znat' dlinu massiva - peredavajte ee kak dopolnitel'nyj argument: int sum( int a[], int len ){ int s=0, i; for(i=0; i < len; i++) s += a[i]; return( s ); } ... int arr[10] = { ... }; ... int sum10 = sum(arr, 10); ... Kolichestvo elementov v massive TYPE arr[N]; mozhno vychislit' special'nym obrazom, kak #define LENGTH (sizeof(arr) / sizeof(arr[0])) ili #define LENGTH (sizeof(arr) / sizeof(TYPE)) Oba sposoba vydadut chislo, ravnoe N. |ti konstrukcii obychno upotreblyayutsya dlya vychis- leniya dliny massivov, zadavaemyh v vide TYPE arr[] = { ....... }; bez yavnogo ukazaniya razmera. sizeof(arr) vydaet razmer vsego massiva v bajtah. sizeof(arr[0]) vydaet razmer odnogo elementa. I vse eto ne zavisit ot tipa elementa (prosto potomu, chto vse elementy massivov imeyut odinakovyj razmer). Stroka v Si - eto posledovatel'nost' bajt (bukv, simvolov, liter, character), zavershayushchayasya v konce special'nym priznakom - bajtom '\0'. |tot priznak dobavlyaetsya kompilyatorom avtomaticheski, kogda my zadaem stroku v vide "stroka". Dlina stroki (t.e. chislo liter, predshestvuyushchih '\0') nigde yavno ne hranitsya. Dlina stroki ograni- chena lish' razmerom massiva, v kotorom sohranena stroka, i mozhet izmenyat'sya v processe raboty programmy v predelah ot 0 do dliny massiva-1. Pri peredache stroki v kachestve argumenta v funkciyu, funkcii ne trebuetsya znat' dlinu stroki, t.k. peredaetsya ukaza- tel' na nachalo massiva, a nalichie ogranichitelya '\0' pozvolyaet obnaruzhit' konec stroki pri ee prosmotre. S massivami bajt mozhno ispol'zovat' sleduyushchuyu konstrukciyu, zadayushchuyu massivy (stroki) odinakovogo razmera: char stringA [ITSSIZE]; char stringB [sizeof stringA]; V dannom razdele my v osnovnom budem rassmatrivat' stroki i ukazateli na simvoly. 2.1. Operacii vzyatiya adresa ob®ekta i razymenovaniya ukazatelya - vzaimno obratny. TYPE objx; TYPE *ptrx = &objx; /* inicializiruem adresom objx */ *(&objx) = objx; &(*ptrx) = ptrx; Vot primer togo, kak mozhno zamenit' uslovnyj operator uslovnym vyrazheniem (eto udastsya ne vsegda): if(c) a = 1; else b = 1; A. Bogatyrev, 1992-95 - 86 - Si v UNIX Preduprezhdenie: takoj stil' ne sposobstvuet ponyatnosti programmy i dazhe kompaktnosti ee koda. #include <stdio.h> int main(int ac, char *av[]){ int a, b, c; a = b = c = 0; if(av[1]) c = atoi(av[1]); *(c ? &a : &b) = 1; /* !!! */ printf("cond=%d a=%d b=%d\n", c, a, b); return 0; } 2.2. Kakim obrazom inicializiruyutsya po umolchaniyu vneshnie i staticheskie massivy? Ini- cializiruyutsya li po umolchaniyu avtomaticheskie massivy? Kakim obrazom mozhno prisvai- vat' znacheniya elementam massiva, otnosyashchegosya k lyubomu klassu pamyati? 2.3. Pust' zadan massiv int arr[10]; chto togda oznachayut vyrazheniya: arr[0] *arr *arr + 2 arr[2] *(arr + 2) arr &arr[2] arr+2 2.4. Pravil'no li napisano uvelichenie velichiny, na kotoruyu ukazyvaet ukazatel' a, na edinicu? *a++; Otvet: net, nado: (*a)++; ili *a += 1; 2.5. Dan fragment teksta: char a[] = "xyz"; char *b = a + 1; CHemu ravny b[-1] b[2] "abcd"[3] (Otvet: 'x', '\0', 'd' ) Mozhno li napisat' a++ ? To zhe pro b++ ? Mozhno li napisat' b=a ? a=b ? (net, da, da, net) 2.6. Nizhe privedena programma, vychislyayushchaya srednee znachenie elementov massiva int arr [] = {1, 7, 4, 45, 31, 20, 57, 11}; main () { int i; long sum; for ( i = 0, sum = 0L; i < (sizeof(arr)/sizeof(int)); i++ ) sum += arr[i]; printf ("Srednee znachenie = %ld\n", sum/8) A. Bogatyrev, 1992-95 - 87 - Si v UNIX } Perepishite ukazannuyu programmu s primeneniem ukazatelej. 2.7. CHto napechataetsya v rezul'tate raboty programmy? char arr[] = {'S', 'L', 'A', 'V', 'A'}; main () { char *pt; int i; pt = arr + sizeof(arr) - 1; for( i = 0; i < 5; i++, pt-- ) printf("%c %c\n", arr[i], *pt); } Pochemu massiv arr[] opisan vne funkcii main()? Kak vnesti ego v funkciyu main() ? Otvet: napisat' vnutri main static char arr[]=... 2.8. Mozhno li pisat' na Si tak: f( n, m ){ int x[n]; int y[n*2]; int z[n * m]; ... } Otvet: k sozhaleniyu nel'zya (Si - eto ne Algol). Pri otvedenii pamyati dlya massiva v kachestve razmera dolzhna byt' ukazana konstanta ili vyrazhenie, kotoroe mozhet byt' eshche vo vremya kompilyacii vychisleno do celochislennoj konstanty, t.e. massivy imeyut fiksiro- vannuyu dlinu. 2.9. Predpolozhim, chto u nas est' opisanie massiva static int mas[30][100]; a) vyrazite adres mas[22][56] inache b) vyrazite adres mas[22][0] dvumya sposobami c) vyrazite adres mas[0][0] tremya sposobami 2.10. Sostav'te programmu inicializacii dvumernogo massiva a[10][10], vyborki ele- mentov s a[5][5] do a[9][9] i ih raspechatki. Ispol'zujte dostup k elementam po uka- zatelyu. 2.11. Sostav'te funkciyu vychisleniya skalyarnogo proizvedeniya dvuh vektorov. Dlina vektorov zadaetsya v kachestve odnogo iz argumentov. 2.12. Sostav'te funkciyu umnozheniya dvumernyh matric a[][] * b[][]. 2.13. Sostav'te funkciyu umnozheniya trehmernyh matric a[][][] * b[][][]. 2.14. Dlya teh, kto programmiroval na yazyke Pascal: kakaya dopushchena oshibka? char a[10][20]; char c; int x,y; ... c = a[x,y]; Otvet: mnogomernye massivy v Si nado indeksirovat' tak: A. Bogatyrev, 1992-95 - 88 - Si v UNIX c = a[x][y]; V napisannom zhe primere my imeem v kachestve indeksa vyrazhenie x,y (operator "zapya- taya") so znacheniem y, t.e. c = a[y]; Sintaksicheskoj oshibki net, no smysl sovershenno izmenilsya! 2.15. Dvumernye massivy v pamyati predstavlyayutsya kak odnomernye. Naprimer, esli int a[N][M]; to konstrukciya a[y][x] prevrashchaetsya pri kompilyacii v odnomernuyu konstrukciyu, podobnuyu takoj: int a[N * M]; /* massiv razvernut postrochno */ #define a_yx(y, x) a[(x) + (y) * M] to est' a[y][x] est' *(&a[0][0] + y * M + x) Sledstviem etogo yavlyaetsya to, chto kompilyator dlya generacii indeksacii dvumernyh (i bolee) massovov dolzhen znat' M - razmer massiva po 2-omu izmereniyu (a takzhe 3-emu, 4-omu, i.t.d.). V chastnosti, pri peredache mnogomernogo massiva v funkciyu f(arr) int arr[N][M]; { ... } /* goditsya */ f(arr) int arr[] [M]; { ... } /* goditsya */ f(arr) int arr[] []; { ... } /* ne goditsya */ f(arr) int (*arr)[M]; { ... } /* goditsya */ f(arr) int *arr [M]; { ... } /* ne goditsya: eto uzhe ne dvumernyj massiv, a odnomernyj massiv ukazatelej */ A takzhe pri opisanii vneshnih massivov: extern int a[N][M]; /* goditsya */ extern int a[ ][M]; /* goditsya */ extern int a[ ][ ]; /* ne goditsya: kompilyator ne smozhet sgenerit' operaciyu indeksacii */ Vot kak, k primeru, dolzhna vyglyadet' rabota s dvumernym massivom arr[ROWS][COLS], otvedennym pri pomoshchi malloc(); void f(int array[][COLS]){ int x, y; for(y=0; y < ROWS; y++) for(x=0; x < COLS; x++) array[y][x] = 1; } void main(){ int *ptr = (int *) malloc(sizeof(int) * ROWS * COLS); f( (int (*) [COLS]) ptr); } 2.16. Kak opisyvat' ssylki (ukazateli) na dvumernye massivy? Rassmotrim takuyu prog- rammu: A. Bogatyrev, 1992-95 - 89 - Si v UNIX #include <stdio.h> #define First 3 #define Second 5 char arr[First][Second] = { "ABC.", { 'D', 'E', 'F', '?', '\0' }, { 'G', 'H', 'Z', '!', '\0' } }; char (*ptr)[Second]; main(){ int i; ptr = arr; /* arr i ptr teper' vzaimozamenimy */ for(i=0; i < First; i++) printf("%s\t%s\t%c\n", arr[i], ptr[i], ptr[i][2]); } Ukazatelem zdes' yavlyaetsya ptr. Otmetim, chto u nego zadana razmernost' po vtoromu izmereniyu: Second, imenno dlya togo, chtoby kompilyator mog pravil'no vychislit' dvumer- nye indeksy. Poprobujte sami ob®yavit' char (*ptr)[4]; char (*ptr)[6]; char **ptr; i uvidet', k kakim neveselym effektam eto privedet (kompilyator, kstati, budet rugat'sya; no est' veroyatnost', chto on vse zhe stransliruet eto dlya vas. No rabotat' ono budet plachevno). Poprobujte takzhe ispol'zovat' ptr[x][y]. Obratite takzhe vnimanie na inicializaciyu strok v nashem primere. Stroka "ABC." ravnosil'na ob®yavleniyu { 'A', 'B', 'C', '.', '\0' }, 2.17. Massiv s modeliruet dvumernyj massiv char s[H][W]; Perepishite primer pri pomoshchi ukazatelej, izbav'tes' ot operacii umnozheniya. Pryamougol'nik (x0,y0,width,height) lezhit celikom vnutri (0,0,W,H). char s[W*H]; int x,y; int x0,y0,width,height; for(x=0; x < W*H; x++) s[x] = '.'; ... for(y=y0; y < y0+height; y++) for(x=x0; x < x0+width; x++) s[x + W*y] = '*'; Otvet: char s[W*H]; int i,j; int x0,y0,width,height; char *curs; ... for(curs = s + x0 + W*y0, i=0; i < height; i++, curs += W-width) for(j=0; j < width; j++) *curs++ = '*'; Takaya optimizaciya vozmozhna v nekotoryh funkciyah iz glavy "Rabota s videopamyat'yu". A. Bogatyrev, 1992-95 - 90 - Si v UNIX 2.18. CHto oznachayut opisaniya? int i; // celoe. int *pi; // ukazatel' na celoe. int *api[3]; // massiv iz 3h uk-lej na celye. int (*pai)[3]; // ukazatel' na massiv iz 3h celyh. // mozhno opisat' kak int **pai; int fi(); // funkciya, vozvrashchayushchaya celoe. int *fpi(); // f-ciya, vozvr. uk-l' na celoe. int (*pfi)(); // uk-l' na f-ciyu, vozvrashchayushchuyu celoe. int *(*pfpi)(); // uk-l' na f-ciyu, vozvr. uk-l' na int. int (*pfpfi())(); // f-ciya, vozvrashchayushchaya ukazatel' na // "funkciyu, vozvrashchayushchuyu celoe". int (*fai())[3]; // f-ciya, vozvr. uk-l' na massiv // iz 3h celyh. inache ee // mozhno opisat' kak int **fai(); int (*apfi[3])(); // massiv iz 3h uk-lej na funkcii, // vozvrashchayushchie celye. Peremennye v Si opisyvayutsya v formate ih ispol'zovaniya. Tak opisanie int (*f)(); oznachaet, chto f mozhno ispol'zovat' v vide int value; value = (*f)(1, 2, 3 /* spisok argumentov */); Odnako iz takogo sposoba opisaniya tip samoj opisyvaemoj peremennoj i ego smysl dovol'no neochevidny. Privedem priem (pozaimstvovannyj iz zhurnala "Communications of the ACM"), pozvolyayushchij proyasnit' smysl opisaniya. Opisanie na Si perevoditsya v opisa- nie v stile yazyka Algol-68. Dalee ref TIP oznachaet "ukazatel' na TIP" proc() TIP "funkciya, vozvrashchayushchaya TIP" array of TIP "massiv iz elementov TIPa" x: TIP "x imeet tip TIP" Privedem neskol'ko primerov, iz kotoryh yasen i sposob preobrazovaniya: int (*f())(); oznachaet (*f())() : int *f() : proc() int f() : ref proc() int f : proc() ref proc() int to est' f - funkciya, vozvrashchayushchaya ukazatel' na funkciyu, vozvrashchayushchuyu celoe. int (*f[3])(); oznachaet (*f[])() : int *f[] : proc() int f[] : ref proc() int f : array of ref proc() int f - massiv ukazatelej na funkcii, vozvrashchayushchie celye. Obratno: opishem g kak ukaza- tel' na funkciyu, vozvrashchayushchuyu ukazatel' na massiv iz 5i ukazatelej na funkcii, vozv- rashchayushchie ukazateli na celye. A. Bogatyrev, 1992-95 - 91 - Si v UNIX g : ref p() ref array of ref p() ref int *g : p() ref array of ref p() ref int (*g)() : ref array of ref p() ref int *(*g)() : array of ref p() ref int (*(*g)())[5] : ref p() ref int *(*(*g)())[5] : p() ref int (*(*(*g)())[5])(): ref int *(*(*(*g)())[5])(): int int *(*(*(*g)())[5])(); V Si nevozmozhny funkcii, vozvrashchayushchie massiv: proc() array of ... a tol'ko proc() ref array of ... Samo nazvanie tipa (naprimer, dlya ispol'zovaniya v operacii privedeniya tipa) polucha- etsya vycherkivaniem imeni peremennoj (a takzhe mozhno opustit' razmer massiva): g = ( int *(*(*(*)())[])() ) 0; 2.19. Napishite funkciyu strcat(d,s), pripisyvayushchuyu stroku s k koncu stroki d. Otvet: char *strcat(d,s) register char *d, *s; { while( *d ) d++; /* ishchem konec stroki d */ while( *d++ = *s++ ); /* strcpy(d, s) */ return (d-1); /* konec stroki */ } Cikl, pomechennyj "strcpy" - eto naibolee kratkaya zapis' operatorov do{ char c; c = (*d = *s); s++; d++; } while(c != '\0'); Na samom dele strcat dolzhen po standartu vozvrashchat' svoj pervyj argument, kak i funk- ciya strcpy: char *strcat(d,s) register char *d, *s; { char *p = d; while( *d ) d++; strcpy(d, s); return p; } |ti dva varianta demonstriruyut, chto funkciya mozhet byt' realizovana raznymi sposobami. Krome togo vidno, chto vmesto standartnoj bibliotechnoj funkcii my mozhem opredelit' svoyu odnoimennuyu funkciyu, neskol'ko otlichayushchuyusya povedeniem ot standartnoj (kak vozv- rashchaemoe znachenie v 1-om variante). 2.20. Napishite programmu, kotoraya ob®edinyaet i raspechatyvaet dve stroki, vvedennye s terminala. Dlya vvoda strok ispol'zujte funkciyu gets(), a dlya ih ob®edineniya - strcat(). V drugom variante ispol'zujte sprintf(result,"%s%s",s1,s2); 2.21. Modificirujte predydushchuyu programmu takim obrazom, chtoby ona vydavala dlinu (chislo simvolov) ob®edinennoj stroki. Ispol'zujte funkciyu strlen(). Privedem nes- kol'ko versij realizacii strlen: /* Pri pomoshchi indeksacii massiva */ A. Bogatyrev, 1992-95 - 92 - Si v UNIX int strlen(s) char s[]; { int length = 0; for(; s[length] != '\0'; length++); return (length); } /* Pri pomoshchi prodvizheniya ukazatelya */ int strlen(s) char *s; { int length; for(length=0; *s; length++, s++); return length; } /* Pri pomoshchi raznosti ukazatelej */ int strlen(register char *s) { register char *p = s; while(*p) p++; /* ishchet konec stroki */ return (p - s); } Raznost' dvuh ukazatelej na odin i tot zhe tip - celoe chislo: esli TYPE *p1, *p2; to p2 - p1 = celoe chislo shtuk TYPE lezhashchih mezhdu p2 i p1 esli p2 = p1 + n to p2 - p1 = n |ta raznost' mozhet byt' i otricatel'noj esli p2 < p1, to est' p2 ukazyvaet na bolee levyj element massiva. 2.22. Napishite operator Si, kotoryj obrubaet stroku s do dliny n bukv. Otvet: if( strlen(s) > n ) s[n] = '\0'; Pervoe sravnenie voobshche govorya izlishne. Ono napisano lish' na tot sluchaj, esli stroka s koroche, chem n bukv i hranitsya v massive, kotoryj takzhe koroche n, t.e. ne imeet n- ogo elementa (poetomu v nego nel'zya proizvodit' zapis' priznaka konca). 2.23. Napishite funkcii preobrazovaniya stroki, soderzhashchej izobrazhenie celogo chisla, v samo eto chislo. V dvuh raznyh variantah argument-adres dolzhen ukazyvat' na pervyj bajt stroki; na poslednij bajt. Otvet: #define isdigit(c) ('0' <= (c) && (c) <= '9') int atoi(s) register char *s; { register int res=0, neg=0; for(;;s++){ switch(*s){ case ' ': case '\t': continue; case '-': neg++; case '+': s++; } break; } while(isdigit(*s)) res = res * 10 + *s++ - '0'; return( neg ? -res : res ); } int backatoi(s) register char *s; { int res=0, pow=1; while(isdigit(*s)){ A. Bogatyrev, 1992-95 - 93 - Si v UNIX res += (*s-- - '0') * pow; pow *= 10; } if(*s == '-') res = -res; return res; } 2.24. Mozhno li dlya zaneseniya v massiv s stroki "hello" napisat' char s[6]; s = "hello"; ili char s[6], d[] = "hello"; s = d; Otvet: net. Massivy v Si nel'zya prisvaivat' celikom. Dlya peresylki massiva bajt nado ispol'zovat' funkciyu strcpy(s,d). Zdes' zhe my pytaemsya izmenit' adres s (imya massiva - eto adres nachala pamyati, vydelennoj dlya hraneniya massiva), sdelav ego ravnym adresu bezymyannoj stroki "hello" (ili massiva d vo vtorom sluchae). |tot adres yavlyaetsya konstantoj i ne mozhet byt' izmenen! Zametim odnako, chto opisanie massiva s inicializaciej vpolne dopustimo: char s[6] = "hello"; ili char s[6] = { 'h', 'e', 'l', 'l', 'o', '\0' }; ili char s[] = "hello"; ili char s[] = { "hello" }; V etom sluchae kompilyator rezerviruet pamyat' dlya hraneniya massiva i raspisyvaet ee bajtami nachal'nogo znacheniya. Obratite vnimanie, chto stroka v dvojnyh kavychkah (esli ee rassmatrivat' kak massiv bukv) imeet dlinu na edinicu bol'she, chem napisano bukv v stroke, poskol'ku v konce massiva nahoditsya simvol '\0' - priznak konca, dobavlennyj kompilyatorom. Esli by my napisali char s[5] = "hello"; to kompilyator soobshchil by ob oshibke, poskol'ku dliny massiva (5) nedostatochno, chtoby razmestit' 6 bajt. V tret'ej stroke primera napisano s[], chtoby kompilyator sam pos- chital neobhodimuyu dlinu massiva. Nakonec, vozmozhna situaciya, kogda massiv bol'she, chem hranyashchayasya v nem stroka. Togda "lishnee" mesto soderzhit kakoj-to musor (v static-pamyati iznachal'no - bajty \0). char s[12] = "hello"; soderzhit: h e l l o \0 ? ? ? ? ? ? V programmah tekstovoj obrabotki pod "dlinoj stroki" obychno ponimayut kolichestvo bukv v stroke NE schitaya zakryvayushchij bajt '\0'. Imenno takuyu dlinu schitaet standartnaya funkciya strlen(s). Poetomu sleduet razlichat' takie ponyatiya kak "(tekushchaya) dlina stroki" i "dlina massiva, v kotorom hranitsya stroka": sizeof(s). Dlya napisannogo vyshe primera eti znacheniya ravny sootvetstvenno 5 i 12. Sleduet takzhe otlichat' massivy ot ukazatelej: char *sp = "bye bye"; sp = "hello"; budet vpolne zakonno, poskol'ku v dannom sluchae sp - ne imya massiva (t.e. konstanta, ravnaya adresu nachala massiva), a ukazatel' (peremennaya, hranyashchaya adres nekotoroj oblasti pamyati). Poskol'ku ukazatel' - eto peremennaya, to ee znachenie izmenyat' mozhno: v dannom sluchae sp snachala soderzhala adres bezymyannogo massiva, v kotorom nahoditsya "bye bye"; zatem my zanesli v sp adres bezymyannogo massiva, hranyashchego A. Bogatyrev, 1992-95 - 94 - Si v UNIX stroku "hello". Zdes' ne proishodit kopirovaniya massiva, a proishodit prosto prisva- ivanie peremennoj sp novogo znacheniya adresa. Predosterezhem ot vozmozhnoj nepriyatnosti: char d[5]; char s[] = "abcdefgh"; strcpy(d, s); Dliny massiva d prosto ne hvatit dlya hraneniya takoj dlinnoj stroki. Poskol'ku eto nichem ne kontroliruetsya (ni kompilyatorom, ni samoj strcpy, ni vami yavnym obrazom), to pri kopirovanii stroki "izbytochnye" bajty zapishutsya posle massiva d poverh drugih dannyh, kotorye budut isporcheny. |to privedet k nepredskazuemym effektam. Nekotorye vozmozhnosti dlya kontrolya za dlinoj strok-argumentov vam dayut funkcii strncpy(d,s,len); strncat(d,s,len); strncmp(s1,s2,len). Oni peresylayut (sravnivayut) ne bolee, chem len pervyh simvolov stroki s (strok s1, s2). Posmotrite v dokumenta- ciyu! Napishite funkciyu strncmp (sravnenie strok po pervym len simvolam), posmotrev na funkciyu strncpy: char *strncpy(dst, src, n) register char *dst, *src; register int n; { char *save; for(save=dst; --n >= 0; ) if( !(*dst++ = *src++)){ while(--n >= 0) *dst++ = '\0'; return save; } return save; } Otmet'te, chto strncpy obladaet odnim nepriyatnym svojstvom: esli n <= strlen(src), to stroka dst ne budet imet' na konce simvola '\0', to est' budet nahodit'sya v nekor- rektnom (ne kanonicheskom) sostoyanii. Otvet: int strncmp(register char *s1, register char *s2, register int n) { if(s1 == s2) return(0); while(--n >= 0 && *s1 == *s2++) if(*s1++ == '\0') return(0); return((n < 0)? 0: (*s1 - *--s2)); } 2.25. V chem oshibka? #include <stdio.h> /* dlya putchar */ char s[] = "We don't need no education"; main(){ while(*s) putchar(*s++); } Otvet: zdes' s - konstanta, k nej neprimenima operaciya ++. Nado napisat' char *s = "We don't need no education"; sdelav s ukazatelem na bezymyannyj macciv. Ukazatel' uzhe mozhno izmenyat'. 2.26. Kakie iz privedennyh konstrukcij oboznachayut odno i to zhe? A. Bogatyrev, 1992-95 - 95 - Si v UNIX char a[] = ""; /* pustaya stroka */ char b[] = "\0"; char c = '\0'; char z[] = "ab"; char aa[] = { '\0' }; char bb[] = { '\0', '\0' }; char xx[] = { 'a', 'b' }; char zz[] = { 'a', 'b', '\0' }; char *ptr = "ab"; 2.27. Najdite oshibki v opisanii simvol'noj stroki: main() { char mas[] = {'s', 'o', 'r', 't'}; /* "sort" ? */ printf("%s\n", mas); } Otvet: stroka dolzhna konchat'sya '\0' (v nashem sluchae printf ne obnaruzhiv simvola konca stroki budet vydavat' i bajty, nahodyashchiesya v pamyati posle massiva mas, t.e. musor); inicializirovannyj massiv ne mozhet byt' avtomaticheskim - trebuetsya static: main() { static char mas[] = {'s', 'o', 'r', 't', '\0'}; } Zametim, chto main(){ char *mas = "sort"; } zakonno, t.k. sama stroka zdes' hranitsya v staticheskoj pamyati, a inicializiruetsya lish' ukazatel' na etot massiv bajt. 2.28. V chem oshibka? Programma sobiraetsya iz dvuh fajlov: a.c i b.c komandoj cc a.c b.c -o ab a.c b.c --------------------------------------------------- int n = 2; extern int n; char s[] = "012345678"; extern char *s; main(){ f(){ f(); s[n] = '+'; printf("%s\n", s ); } } Otvet: delo v tom, chto tipy (char *) - ukazatel', i char[] - massiv, oznachayut odno i to zhe tol'ko pri ob®yavlenii formal'nogo parametra funkcii: f(char *arg){...} f(char arg[]){...} eto budet lokal'naya peremennaya, soderzhashchaya ukazatel' na char (t.e. adres nekotorogo bajta v pamyati). Vnutri funkcii my mozhem izmenyat' etu peremennuyu, naprimer arg++. Dalee, i (char *) i char[] odinakovo ispol'zuyutsya, naprimer, oba eti tipa mozhno indeksirovat': arg[i]. No vne funkcij oni ob®yavlyayut raznye ob®ekty! Tak char *p; eto skalyarnaya peremennaya, hranyashchaya adres (ukazatel'): -------- ------- p:| *--|----->| '0' | char -------- | '1' | char ... A. Bogatyrev, 1992-95 - 96 - Si v UNIX togda kak char a[20]; eto adres nachala massiva (a vovse ne peremennaya): ------- a:| '0' | char | '1' | char ... V nashem primere v fajle b.c my ob®yavili vneshnij massiv s kak peremennuyu. V rezul'- tate kompilyator budet interpretirovat' nachalo massiva s kak peremennuyu, soderzhashchuyu ukazatel' na char. ------- s:| '0' | \ eto budet vosprinyato kak | '1' | / adres drugih dannyh. | '2' | ... I indeksirovat'sya budet uzhe |TOT adres! Rezul'tat - obrashchenie po nesushchestvuyushchemu adresu. To, chto napisano u nas, ekvivalentno char s[] = "012345678"; char **ss = s; /* s - kak by "massiv ukazatelej" */ /* pervye bajty s interpretiruyutsya kak ukazatel': */ char *p = ss[0]; p[2] = '+'; My zhe dolzhny byli ob®yavit' v b.c extern char s[]; /* razmer ukazyvat' ne trebuetsya */ Vot eshche odin analogichnyj primer, kotoryj poyasnit vam, chto proishodit (a zaodno poka- zhet poryadok bajtov v long). Primer vypolnyalsya na IBM PC 80386, na kotoroj sizeof(char *) = sizeof(long) = 4 a.c b.c --------------------------------------------------- char s[20] = {1,2,3,4}; extern char *s; main(){ f(){ /*pechat' ukazatelya kak long */ f(); printf( "%08lX\n", s ); } } pechataetsya 04030201. 2.29. CHto napechataet programma? static char str1[ ] = "abc"; static char str2[4]; strcpy( str2, str1 ); /* mozhno li napisat' str2 = str1; ? */ printf( str1 == str2 ? "ravno":"ne ravno" ); Kak nado pravil'no sravnivat' stroki? CHto na samom dele sravnivaetsya v dannom pri- mere? Otvet: sravnivayutsya adresa massivov, hranyashchih stroki. Tak A. Bogatyrev, 1992-95 - 97 - Si v UNIX char str1[2]; char str2[2]; main(){ printf( str1 < str2 ? "<":">"); } pechataet <, a esli napisat' char str2[2]; char str1[2]; to napechataetsya >. 2.30. Napishite programmu, sprashivayushchuyu vashe imya do teh por, poka vy ego pravil'no ne vvedete. Dlya sravneniya strok ispol'zujte funkciyu strcmp() (ee realizaciya est' v glave "Mobil'nost'"). 2.31. Kakie znacheniya vozvrashchaet funkciya strcmp() v sleduyushchej programme? #include <stdio.h> main() { printf("%d\n", strcmp("abc", "abc")); /* 0 */ printf("%d\n", strcmp("ab" , "abc")); /* -99 */ printf("%d\n", strcmp("abd", "abc")); /* 1 */ printf("%d\n", strcmp("abc", "abd")); /* -1 */ printf("%d\n", strcmp("abc", "abe")); /* -2 */ } 2.32. V kachestve itoga predydushchih zadach: pomnite, chto v Si stroki (a ne adresa) nado sravnivat' kak if( strcmp("abc", "bcd") < 0) ... ; if( strcmp("abc", "bcd") == 0) ... ; vmesto if( "abc" < "bcd" ) ... ; if( "abc" == "bcd" ) ... ; i prisvaivat' kak char d[80], s[80]; strcpy( d, s ); vmesto d = s; 2.33. Napishite programmu, kotoraya sortiruet po alfavitu i pechataet sleduyushchie klyuche- vye slova yazyka Si: int char double long for while if 2.34. Vopros ne sovsem pro stroki, skoree pro cikl: chem ploha konstrukciya? char s[] = "You're a smart boy, now shut up."; int i, len; for(i=0; i < strlen(s); i++) putchar(s[i]); Otvet: v sootvetstvii s semantikoj Si cikl razvernetsya primerno v A. Bogatyrev, 1992-95 - 98 - Si v UNIX i=0; LOOP: if( !(i < strlen(s))) goto ENDLOOP; putchar(s[i]); i++; goto LOOP; ENDLOOP: ; Zamet'te, chto hotya dlina stroki s ne menyaetsya, strlen(s) vychislyaetsya na KAZHDOJ itera- cii cikla, sovershaya lishnyuyu rabotu! Bor'ba s etim takova: for(i=0, len=strlen(s); i < len; i++ ) putchar(s[i]); ili for(i=0, len=strlen(s); len > 0; i++, --len ) putchar(s[i]); Analogichno, v cikle while( i < strlen(s))...; funkciya tozhe budet vychislyat'sya pri kazhdoj proverke usloviya! |to, konechno, otnositsya k lyuboj funkcii, ispol'zuemoj v uslovii, a ne tol'ko k strlen. (No, razumeetsya, sluchaj kogda funkciya vozvrashchaet priznak "nado li prodolzhat' cikl" - sovsem drugoe delo: takaya funkciya obyazana vychislyat'sya kazhdyj raz). 2.35. CHto napechataet sleduyushchaya programma? #include <stdio.h> main(){ static char str[] = "Do vstrechi v bufete"; char *pt; pt = str; puts(pt); puts(++pt); str[7] = '\0'; puts(str); puts(pt); puts(++pt); } 2.36. CHto napechataet sleduyushchaya programma? main() { static char name[] = "Konstantin"; char *pt; pt = name + strlen(name); while(--pt >= name) puts(pt); } 2.37. CHto napechataet sleduyushchaya programma? char str1[] = "abcdef"; char str2[] = "xyz"; main(){ register char *a, *b; a = str1; b = str2; while( *b ) *a++ = *b++; printf( "str=%s a=%s\n", str1, a ); a = str1; b = str2; A. Bogatyrev, 1992-95 - 99 - Si v UNIX while( *b ) *++a = *b++; printf( "str=%s a=%s\n", str1, a ); } Otvet: str=xyzdef a=def str=xxyzef a=zef 2.38. CHto pechataet programma? char *s; for(s = "Sitroen"; *s; s+= 2){ putchar(s[0]); if(!s[1]) break; } putchar('\n'); 2.39. CHto napechataet programma? Rassmotrite prodvizhenie ukazatelya s, ukazatelej - elementov massiva strs[]. Razberites' s poryadkom vypolneniya operacij. V kakih sluchayah ++ izmenyaet ukazatel', a v kakih - bukvu v stroke? Narisujte sebe kartinku, izobrazha- yushchuyu sostoyanie ukazatelej - ona pomozhet vam rasputat' eti spagetti. Udelite razboru etogo primera dostatochnoe vremya! #include <stdio.h> /* opredelenie NULL */ /* Latinskij alfavit: abcdefghijklmnopqrstuvwxyz */ char *strs[] = { "abcd","ABCD","0fpx","159", "hello","-gop","A1479",NULL }; main(){ char c, **s = strs, *p; c = *++*s; printf("#1 %d %c %s\n", s-strs, c, *s); c = **++s; printf("#2 %d %c %s\n", s-strs, c, *s); c = **s++; printf("#3 %d %c %s\n", s-strs, c, *s); c = ++**s; printf("#4 %d %c %s\n", s-strs, c, *s); c = (**s)++; printf("#5 %d %c %s\n", s-strs, c, *s); c = ++*++*s; printf("#6 %d %c %s\n", s-strs, c, *s); c = *++*s++; printf("#7 %d %c %s %s\n", s-strs, c, *s, strs[2]); c = ++*++*s++; printf("#8 %d %c %s %s\n", s-strs, c, *s, strs[3]); c = ++*++*++s; printf("#9 %d %c %s\n", s-strs,c,*s); c = ++**s++; printf("#10 %d %c %s\n",s-strs,c,*s); p = *s; c = ++*(*s)++; printf("#11 %d %c %s %s %s\n",s-strs,c,*s,strs[6],p); c = ++*((*s)++); printf("#12 %d %c %s %s\n", s-strs, c, *s, strs[6]); c = (*++(*s))++; printf("#13 %d %c %s %s\n", s-strs, c, *s, strs[6]); for(s=strs; *s; s++) printf("strs[%d]=\"%s\"\n", s-strs, *s); putchar('\n'); } Pechataetsya: A. Bogatyrev, 1992-95 - 100 - Si v UNIX #1 0 b bcd strs[0]="bcd" #2 1 A ABCD strs[1]="ABCD" #3 2 A 0fpx strs[2]="px" #4 2 1 1fpx strs[3]="69" #5 2 1 2fpx strs[4]="hello" #6 2 g gpx strs[5]="iop" #7 3 p 159 px strs[6]="89" #8 4 6 hello 69 #9 5 h hop #10 6 i A1479 #11 6 B 1479 1479 B1479 #12 6 2 479 479 #13 6 7 89 89 Uchtite, chto konstrukciya char *strs[1] = { "hello" }; oznachaet, chto v strs[0] soderzhitsya ukazatel' na nachal'nyj bajt bezymyannogo massiva, soderzhashchego stroku "hello". |tot ukazatel' mozhno izmenyat'! Poprobujte sostavit' eshche podobnye primery iz *, ++, (). 2.40. CHto pechataet programma? char str[25] = "Hi, "; char *f(char **s){ int cnt; for(cnt=0; **s != '\0'; (*s)++, ++cnt); return("ny" + (cnt && (*s)[-1] == ' ') + (!cnt)); } void main(void){ char *s = str; if( *f(&s) == 'y') strcat(s, "dude"); else strcat(s, " dude"); printf("%s\n", str); } CHto ona napechataet, esli zadat' char str[25]="Hi,"; ili char str[25]=""; 2.41. V chem sostoit oshibka? (Lyubimaya oshibka nachinayushchih) main(){ char *buf; /* ili char buf[]; */ gets( buf ); printf( "%s\n", buf ); } Otvet: pamyat' pod stroku buf ne vydelena, ukazatel' buf ne proinicializirovan i smot- rit neizvestno kuda. Nado bylo pisat' naprimer tak: char buf[80]; ili char mem[80], *buf = mem; Obratite na etot primer osoboe vnimanie, poskol'ku, opisav ukazatel' (no nikuda ego ne napraviv), novichki uspokaivayutsya, ne zabotyas' o vydelenii pamyati dlya hraneniya dan- nyh. Ukazatel' dolzhen ukazyvat' na CHTO-TO, v chem mozhno hranit' dannye, a ne "viset'", ukazyvaya "pal'cem v nebo"! Zapis' informacii po "visyachemu" ukazatelyu razrushaet pamyat' programmy i privodit k skoromu (no chasto ne nemedlennomu i potomu tainstvennomu) krahu. A. Bogatyrev, 1992-95 - 101 - Si v UNIX Vot programma, kotoraya takzhe ispol'zuet neinicializirovannyj ukazatel'. Na mashine SPARCstation 20 eta programma ubivaetsya operacionnoj sistemoj s diagnostikoj "Segmentation fault" (SIGSEGV). |to kak raz i znachit obrashchenie po ukazatelyu, ukazy- vayushchemu "pal'cem v nebo". main(){ int *iptr; int ival = *iptr; printf("%d\n", ival); } 2.42. Dlya polucheniya stroki "Life is life" napisana programma: main(){ char buf[ 60 ]; strcat( buf, "Life " ); strcat( buf, "is " ); strcat( buf, "life" ); printf( "%s\n", buf ); } CHto okazhetsya v massive buf? Otvet: v nachale massiva okazhetsya musor, poskol'ku avtomaticheskij massiv ne iniciali- ziruetsya bajtami '\0', a funkciya strcat() pripisyvaet stroki k koncu stroki. Dlya isp- ravleniya mozhno napisat' *buf = '\0'; pered pervym strcat()-om, libo vmesto pervogo strcat()-a napisat' strcpy( buf, "Life " ); 2.43. Sostav'te makroopredelenie copystr(s1, s2) dlya kopirovaniya stroki s2 v stroku s1. 2.44. Sostav'te makroopredelenie lenstr(s) dlya vychisleniya dliny stroki. Mnogie sovremennye kompilyatory sami obrashchayutsya s podobnymi korotkimi (1-3 opera- tora) standartnymi funkciyami kak s makrosami, to est' pri obrashchenii k nim generyat ne vyzov funkcii, a podstavlyayut tekst ee tela v mesto obrashcheniya. |to delaet ob®ektnyj kod neskol'ko "tolshche", no zato bystree. V rasshirennyh dialektah Si i v Si++ kompilya- toru mozhno predlozhit' obrashchat'sya tak i s vashej funkciej - dlya etogo funkciyu sleduet ob®yavit' kak inline (takie funkcii nazyvayutsya eshche "intrinsic"). 2.45. Sostav'te rekursivnuyu i nerekursivnuyu versii programmy invertirovaniya (zer- kal'nogo otobrazheniya) stroki: abcdef --> fedcba. 2.46. Sostav'te funkciyu index(s, t), vozvrashchayushchuyu nomer pervogo vhozhdeniya simvola t v stroku s; esli simvol t v stroku ne vhodit, funkciya vozvrashchaet -1. Perepishite etu funkciyu s ukazatelyami, chtoby ona vozvrashchala ukazatel' na pervoe vhozhdenie simvola. Esli simvol v stroke otsutstvuet - vydavat' NULL. V UNIX System-V takaya funkciya nazyvaetsya strchr. Vot vozmozhnyj otvet: char *strchr(s, c) register char *s, c; { while(*s && *s != c) s++; return *s == c ? s : NULL; } A. Bogatyrev, 1992-95 - 102 - Si v UNIX Zamet'te, chto p=strchr(s,'\0'); vydaet ukazatel' na konec stroki. Vot primer ispol'- zovaniya: extern char *strchr(); char *s = "abcd/efgh/ijklm"; char *p = strchr(s, '/'); printf("%s\n", p==NULL ? "bukvy / net" : p); if(p) printf("Indeks vhozhdeniya = s[%d]\n", p - s ); 2.47. Napishite funkciyu strrchr(), ukazyvayushchuyu na poslednee vhozhdenie simvola. Otvet: char *strrchr(s, c) register char *s, c; { char *last = NULL; do if(*s == c) last = s; while(*s++); return last; } Vot primer ee ispol'zovaniya: extern char *strrchr(); char p[] = "wsh"; /* etalon */ main(argc, argv) char *argv[];{ char *s = argv[1]; /* proveryaemoe imya */ /* poprobujte vyzyvat' * a.out csh * a.out /bin/csh * a.out wsh * a.out /usr/local/bin/wsh */ char *base = (base = strrchr(s, '/')) ? base+1 : s; if( !strcmp(p, base)) printf("Da, eto %s\n" , p); else printf("Net, eto %s\n", base); /* eshche bolee izoshchrennyj variant: */ if( !strcmp(p,(base=strrchr(s,'/')) ? ++base : (base=s)) ) printf("Yes %s\n", p); else printf("No %s\n", base); } 2.48. Napishite makros substr(to,from,n,len) kotoryj zapisyvaet v to kusok stroki from nachinaya s n-oj pozicii i dlinoj len. Ispol'zujte standartnuyu funkciyu strncpy. Otvet: #define substr(to, from, n, len) strncpy(to, from+n, len) ili bolee korrektnaya funkciya: A. Bogatyrev, 1992-95 - 103 - Si v UNIX char *substr(to, from, n, len) char *to, *from; { int lfrom = strlen(from); if(n < 0 ){ len += n; n = 0; } if(n >= lfrom || len <= 0) *to = '\0'; /* pustaya stroka */ else{ /* dlina ostatka stroki: */ if(len > lfrom-n) len = lfrom - n; strncpy(to, from+n, len); to[len] = '\0'; } return to; } 2.49. Napishite funkciyu, proveryayushchuyu, okanchivaetsya li stroka na ".abc", i esli net - pripisyvayushchuyu ".abc" k koncu. Esli zhe stroka uzhe imeet takoe okonchanie - nichego ne delat'. |ta funkciya polezna dlya generacii imen fajlov s zadannym rasshireniem. Sde- lajte rasshirenie argumentom funkcii. Dlya sravneniya konca stroki s so strokoj p sleduet ispol'zovat': int ls = strlen(s), lp = strlen(p); if(ls >= lp && !strcmp(s+ls-lp, p)) ...sovpali...; 2.50. Napishite funkcii vstavki simvola c v ukazannuyu poziciyu stroki (s razdvizhkoj stroki) i udaleniya simvola v zadannoj pozicii (so sdvizhkoj stroki). Stroka dolzhna izmenyat'sya "na meste", t.e. nikuda ne kopiruyas'. Otvet: /* udalenie */ char delete(s, at) register char *s; { char c; s += at; if((c = *s) == '\0') return c; while( s[0] = s[1] ) s++; return c; } /* libo prosto strcpy(s+at, s+at+1); */ /* vstavka */ insert(s, at, c) char s[], c; { register char *p; s += at; p = s; while(*p) p++; /* na konec stroki */ p[1] = '\0'; /* zakryt' stroku */ for( ; p != s; p-- ) p[0] = p[-1]; *s = c; } 2.51. Sostav'te programmu udaleniya simvola c iz stroki s v kazhdom sluchae, kogda on vstrechaetsya. Otvet: A. Bogatyrev, 1992-95 - 104 - Si v UNIX delc(s, c) register char *s; char c; { register char *p = s; while( *s ) if( *s != c ) *p++ = *s++; else s++; *p = '\0'; /* ne zabyvajte zakryvat' stroku ! */ } 2.52. Sostav'te programmu udaleniya iz stroki S1 kazhdogo simvola, sovpadayushchego s kakim-libo simvolom stroki S2. 2.53. Sostav'te funkciyu scopy(s,t), kotoraya kopiruet stroku s v t, pri etom simvoly tabulyacii i perevoda stroki dolzhny zamenyat'sya na special'nye dvuhsimvol'nye posledo- vatel'nosti "\n" i "\t". Ispol'zujte switch. 2.54. Sostav'te funkciyu, kotoraya "ukorachivaet" stroku, zamenyaya izobrazheniya specsim- volov (vrode "\n") na sami eti simvoly ('\n'). Otvet: extern char *strchr(); void unquote(s) char *s; { static char from[] = "nrtfbae", to [] = "\n\r\t\f\b\7\33"; char c, *p, *d; for(d=s; c = *s; s++) if( c == '\\'){ if( !(c = *++s)) break; p = strchr(from, c); *d++ = p ? to[p - from] : c; }else *d++ = c; *d = '\0'; } 2.55. Napishite programmu, zamenyayushchuyu v stroke S vse vhozhdeniya podstroki P na stroku Q, naprimer: P = "ura"; Q = "oj"; S = "ura-ura-ura!"; Rezul'tat: "oj-oj-oj!" 2.56. Krome funkcij raboty so strokami (gde predpolagaetsya, chto massiv bajt zaversha- etsya priznakom konca '\0'), v Si predusmotreny takzhe funkcii dlya raboty s massivami bajt bez ogranichitelya. Dlya takih funkcij neobhodimo yavno ukazyvat' dlinu obrabatyvae- mogo massiva. Napishite funkcii: peresylki massiva dlinoj n bajt memcpy(dst,src,n); zapolneniya massiva simvolom c memset(s,c,n); poiska vhozhdeniya simvola v massiv memchr(s,c,n); sravneniya dvuh massivov memcmp(s1,s2,n); Otvet: #define REG register char *memset(s, c, n) REG char *s, c; { REG char *p = s; while( --n >= 0 ) *p++ = c; return s; } char *memcpy(dst, src, n) REG char *dst, *src; REG int n; { REG char *d = dst; A. Bogatyrev, 1992-95 - 105 - Si v UNIX while( n-- > 0 ) *d++ = *src++; return dst; } char *memchr(s, c, n) REG char *s, c; { while(n-- && *s++ != c); return( n < 0 ? NULL : s-1 ); } int memcmp(s1, s2, n) REG char *s1, *s2; REG n; { while(n-- > 0 && *s1 == *s2) s1++, s2++; return( n < 0 ? 0 : *s1 - *s2 ); } Est' takie standartnye funkcii. 2.57. Pochemu luchshe pol'zovat'sya standartnymi funkciyami raboty so strokami i pamyat'yu (strcpy, strlen, strchr, memcpy, ...)? Otvet: potomu, chto oni obychno realizovany postavshchikami sistemy |FFEKTIVNO, to est' napisany ne na Si, a na assemblere s ispol'zovaniem specializirovannyh mashinnyh komand i registrov. |to delaet ih bolee bystrymi. Napisannyj Vami ekvivalent na Si mozhet ispol'zovat'sya dlya povysheniya mobil'nosti programmy, libo dlya vneseniya popravok v standartnye funkcii. 2.58. Rassmotrim programmu, kopiruyushchuyu stroku samu v sebya: #include <stdio.h> #include <string.h> char string[] = "abcdefghijklmn"; void main(void){ memcpy(string+2, string, 5); printf("%s\n", string); exit(0); Ona pechataet abababahijklmn. My mogli by ozhidat', chto kusok dliny 5 simvolov "abcde" budet skopirovan kak est': ab[abcde]hijklmn, a poluchili ab[ababa]hijklmn - cikliches- koe povtorenie pervyh dvuh simvolov stroki... V chem delo? Delo v tom, chto kogda oblasti istochnika (src) i poluchatelya (dst) perekryvayutsya, to v nekij moment *src beretsya iz UZHE perezapisannoj ranee oblasti, to est' isporchennoj! Vot programma, illyustriruyushchaya etu problemu: A. Bogatyrev, 1992-95 - 106 - Si v UNIX #include <stdio.h> #include <string.h> #include <ctype.h> char string[] = "abcdefghijklmn"; char *src = &string[0]; char *dst = &string[2]; int n = 5; void show(int niter, char *msg){ register length, i; printf("#%02d %s\n", niter, msg); length = src-string; putchar('\t'); for(i=0; i < length+3; i++) putchar(' '); putchar('S'); putchar('\n'); printf("\t...%s...\n", string); length = dst-string; putchar('\t'); for(i=0; i < length+3; i++) putchar(' '); putchar('D'); putchar('\n'); } void main(void){ int iter = 0; while(n-- > 0){ show(iter, "pered"); *dst++ = toupper(*src++); show(iter++, "posle"); } exit(0); } Ona pechataet: A. Bogatyrev, 1992-95 - 107 - Si v UNIX #00 pered S ...abcdefghijklmn... D #00 posle S ...abAdefghijklmn... D #01 pered S ...abAdefghijklmn... D #01 posle S ...abABefghijklmn... D #02 pered S ...abABefghijklmn... D #02 posle S ...abABAfghijklmn... D #03 pered S ...abABAfghijklmn... D #03 posle S ...abABABghijklmn... D #04 pered S ...abABABghijklmn... D #04 posle S ...abABABAhijklmn... D Otrezki NE perekryvayutsya, esli odin iz nih lezhit libo celikom levee, libo celikom pravee drugogo (n - dlina oboih otrezkov). dst src src dst ######## @@@@@@@@ @@@@@@@@ ######## dst+n <= src ili src+n <= dst dst <= src-n ili dst >= src+n Otrezki perekryvayutsya v sluchae ! (dst <= src - n || dst >= src + n) = (dst > src - n && dst < src + n) Pri etom opasen tol'ko sluchaj dst > src. Takim obrazom opasnaya situaciya opisyvaetsya usloviem src < dst && dst < src + n (esli dst==src, to voobshche nichego ne nado delat'). Resheniem yavlyaetsya kopirovanie "ot A. Bogatyrev, 1992-95 - 108 - Si v UNIX hvosta k golove": void bcopy(register char *src, register char *dst, register int n){ if(dst >= src){ dst += n-1; src += n-1; while(--n >= 0) *dst-- = *src--; }else{ while(n-- > 0) *dst++ = *src++; } } Ili, ogranichivayas' tol'ko opasnym sluchaem: void bcopy(register char *src, register char *dst, register int n){ if(dst==src || n <= 0) return; if(src < dst && dst < src + n) { dst += n-1; src += n-1; while(--n >= 0) *dst-- = *src--; }else memcpy(dst, src, n); } Programma #include <stdio.h> #include <string.h> #include <ctype.h> char string[] = "abcdefghijklmn"; char *src = &string[0]; char *dst = &string[2]; int n = 5; void show(int niter, char *msg){ register length, i; printf("#%02d %s\n", niter, msg); length = src-string; putchar('\t'); for(i=0; i < length+3; i++) putchar(' '); putchar('S'); putchar('\n'); printf("\t...%s...\n", string); length = dst-string; putchar('\t'); for(i=0; i < length+3; i++) putchar(' '); putchar('D'); putchar('\n'); } A. Bogatyrev, 1992-95 - 109 - Si v UNIX void main(void){ int iter = 0; if(dst==src || n <= 0){ printf("Nichego ne nado delat'\n"); return; } if(src < dst && dst < src + n) { dst += n-1; src += n-1; while(--n >= 0){ show(iter, "pered"); *dst-- = toupper(*src--); show(iter++, "posle"); } }else while(n-- > 0){ show(iter, "pered"); *dst++ = toupper(*src++); show(iter++, "posle"); } exit(0); } Pechataet A. Bogatyrev, 1992-95 - 110 - Si v UNIX #00 pered S ...abcdefghijklmn... D #00 posle S ...abcdefEhijklmn... D #01 pered S ...abcdefEhijklmn... D #01 posle S ...abcdeDEhijklmn... D #02 pered S ...abcdeDEhijklmn... D #02 posle S ...abcdCDEhijklmn... D #03 pered S ...abcdCDEhijklmn... D #03 posle S ...abcBCDEhijklmn... D #04 pered S ...abcBCDEhijklmn... D #04 posle S ...abABCDEhijklmn... D Teper' bcopy() - udobnaya funkciya dlya kopirovaniya i sdviga massivov, v chastnosti mas- sivov ukazatelej. Pust' u nas est' massiv strok (vydelennyh malloc-om): char *lines[NLINES]; Togda ciklicheskaya perestanovka strok vyglyadit tak: A. Bogatyrev, 1992-95 - 111 - Si v UNIX void scrollUp(){ char *save = lines[0]; bcopy((char *) lines+1, /* from */ (char *) lines, /* to */ sizeof(char *) * (NLINES-1)); lines[NLINES-1] = save; } void scrollDown(){ char *save = lines[NLINES-1]; bcopy((char *) &lines[0], /* from */ (char *) &lines[1], /* to */ sizeof(char *) * (NLINES-1)); lines[0] = save; } Vozmozhno, chto napisanie po analogii funkcii dlya kopirovaniya massivov elementov tipa (void *) - obobshchennyh ukazatelej - mozhet okazat'sya eshche ponyatnee i effektivnee. Takaya funkciya - memmove - standartno sushchestvuet v UNIX SVR4. Zamet'te, chto poryadok argu- mentov v nej obratnyj po otnosheniyu k bcopy. Sleduet otmetit', chto v SVR4 vse funkcii mem... imeyut ukazateli tipa (void *) i schetchik tipa size_t - tip dlya kolichestva bajt (vmesto unsigned long); v chastnosti dlina fajla imeet imenno etot tip (smotri sistem- nye vyzovy lseek i stat). #include <sys/types.h> void memmove(void *Dst, const void *Src, register size_t n){ register caddr_t src = (caddr_t) Src, dst = (caddr_t) Dst; if(dst==src || n <= 0) return; if(src < dst && dst < src + n) { dst += n-1; src += n-1; while(--n >= 0) *dst-- = *src--; }else memcpy(dst, src, n); } caddr_t - eto tip dlya ukazatelej na BAJT, fakticheski eto (unsigned char *). Zachem voobshche ponadobilos' ispol'zovat' caddr_t? Zatem, chto dlya void *pointer; int n; znachenie pointer + n ne opredeleno i nevychislimo, ibo sizeof(void) ne imeet smysla - eto ne 0, a prosto oshibka, diagnostiruemaya kompilyatorom! 2.59. Eshche ob opechatkah: vot chto byvaet, kogda vmesto znaka `=' pechataetsya `-' (na klaviature oni nahodyatsya ryadom...). A. Bogatyrev, 1992-95 - 112 - Si v UNIX #include <stdio.h> #include <strings.h> char *strdup(const char *s){ extern void *malloc(); return strcpy((char *)malloc(strlen(s)+1), s); } char *ptr; void main(int ac, char *av[]){ ptr - strdup("hello"); /* podrazumevalos' ptr = ... */ *ptr = 'H'; printf("%s\n", ptr); free(ptr); exit(0); } Delo v tom, chto zapis' (a chasto i chtenie) po *pointer, gde pointer==NULL, privodit k avarijnomu prekrashcheniyu programmy. V nashej programme ptr ostalos' ravnym NULL - ukaza- telem v nikuda. V operacionnoj sisteme UNIX na mashinah s apparatnoj zashchitoj pamyati, stranica pamyati, soderzhashchaya adres NULL (0) byvaet zakryta na zapis', poetomu lyuboe obrashchenie po zapisi v etu stranicu vyzyvaet preryvanie ot dispetchera pamyati i avarij- noe prekrashchenie processa. Sistema sama pomogaet lovit' vashi oshibki (no uzhe vo vremya vypolneniya programmy). |to OCHENX chastaya oshibka - zapis' po adresu NULL. MS DOS v takih sluchayah predpochitaet prosto zavisnut', i vy byvaete vynuzhdeny igrat' akkord iz treh klavish - Ctrl/Alt/Del, tak i ne ponyav v chem delo. 2.60. Raz uzh rech' zashla o funkcii strdup (kstati, eto standartnaya funkciya), privedem eshche odnu funkciyu dlya sohraneniya strok. char *savefromto(register char *from, char *upto) { char *ptr, *s; if((ptr = (char *) malloc(upto - from + 1)) == NULL) return NULL; for(s = ptr; from < upto; from++) *s++ = *from; *s = '\0'; return ptr; } Sam simvol (*upto) ne sohranyaetsya, a zamenyaetsya na '\0'. 2.61. Uproshchennyj analog funkcii printf. A. Bogatyrev, 1992-95 - 113 - Si v UNIX /* * Mashinno - nezavisimyj printf() (uproshchennyj variant). * printf - Formatnyj Vyvod. */ #include <stdio.h> #include <ctype.h> #include <varargs.h> #include <errno.h> #include <string.h> extern int errno; /* kod sistemnoj oshibki, format %m */ /* chtenie znacheniya chisla */ #define GETN(n,fmt) \ n = 0; \ while(isdigit(*fmt)){ \ n = n*10 + (*fmt - '0'); \ fmt++; \ } void myprintf(fmt, va_alist) register char *fmt; va_dcl { va_list ap; char c, *s; int i; int width, /* minimal'naya shirina polya */ prec, /* maks. dlina dannogo */ sign, /* vyravnivanie: 1 - vpravo, -1 - vlevo */ zero, /* shirina polya nachinaetsya s 0 */ glong; /* trebuetsya dlinnoe celoe */ va_start(ap); for(;;){ while((c = *fmt++) != '%'){ if( c == '\0' ) goto out; putchar(c); } sign = 1; zero = 0; glong = 0; if(*fmt == '-'){ sign = (-1); fmt++; } if(*fmt == '0'){ zero = 1; fmt++; } if(*fmt == '*'){ width = va_arg(ap, int); if(width < 0){ width = -width; sign = -sign; } fmt++; }else{ GETN(width, fmt); } width *= sign; if(*fmt == '.'){ if(*++fmt == '*'){ prec = va_arg(ap, int); fmt++; }else{ GETN(prec, fmt); } }else prec = (-1); /* proizvol'no */ if( *fmt == 'l' ){ glong = 1; fmt++; } A. Bogatyrev, 1992-95 - 114 - Si v UNIX switch(c = *fmt++){ case 'c': putchar(va_arg(ap, int)); break; case 's': prStr(width, prec, va_arg(ap, char *)); break; case 'm': prStr(width, prec, strerror(errno)); break; /* strerror preobrazuet kod oshibki v stroku-rasshifrovku */ case 'u': prUnsigned(width, glong ? va_arg(ap, unsigned long) : (unsigned long) va_arg(ap, unsigned int), 10 /* base */, zero); break; case 'd': prInteger(width, glong ? va_arg(ap, long) : (long) va_arg(ap, int), 10 /* base */, zero); break; case 'o': prUnsigned(width, glong ? va_arg(ap, unsigned long) : (unsigned long) va_arg(ap, unsigned int), 8 /* base */, zero); break; case 'x': prUnsigned(width, glong ? va_arg(ap, unsigned long) : (unsigned long) va_arg(ap, unsigned int), 16 /* base */, zero); break; case 'X': prUnsigned(width, glong ? va_arg(ap, unsigned long) : (unsigned long) va_arg(ap, unsigned int), -16 /* base */, zero); break; case 'b': prUnsigned(width, glong ? va_arg(ap, unsigned long) : (unsigned long) va_arg(ap, unsigned int), 2 /* base */, zero); break; case 'a': /* address */ prUnsigned(width, (long) (char *) va_arg(ap, char *), 16 /* base */, zero); break; case 'A': /* address */ prUnsigned(width, (long) (char *) va_arg(ap, char *), -16 /* base */, zero); break; case 'r': prRoman(width, prec, va_arg(ap, int)); break; case '%': putchar('%'); break; default: putchar(c); break; } } out: va_end(ap); } A. Bogatyrev, 1992-95 - 115 - Si v UNIX /* --------------------------------------------------------- */ int strnlen(s, maxlen) char *s; { register n; for( n=0; *s && n < maxlen; n++, s++ ); return n; } /* Pechat' stroki */ static prStr(width, prec, s) char *s; { int ln; /* skol'ko simvolov vyvodit' */ int toLeft = 0; /* k kakomu krayu prizhimat' */ if(s == NULL){ pr( "(NULL)", 6); return; } /* Izmerit' dlinu i obrubit' dlinnuyu stroku. * Delo v tom, chto stroka mozhet ne imet' \0 na konce, togda * strlen(s) mozhet privesti k obrashcheniyu v zapreshchennye adresa */ ln = (prec > 0 ? strnlen(s, prec) : strlen(s)); /* shirina polya */ if( ! width ) width = (prec > 0 ? prec : ln); if( width < 0){ width = -width; toLeft = 1; } if( width > ln){ /* dopolnit' pole probelami */ if(toLeft){ pr(s, ln); prSpace(width - ln, ' '); } else { prSpace(width - ln, ' '); pr(s, ln); } } else { pr(s, ln); } } /* Pechat' stroki dlinoj l */ static pr(s, ln) register char *s; register ln; { for( ; ln > 0 ; ln-- ) putchar( *s++ ); } /* Pechat' n simvolov c */ static prSpace(n, c) register n; char c;{ for( ; n > 0 ; n-- ) putchar( c ); } /* --------------------------------------------------------- */ static char *ds; /* Rimskie cifry */ static prRoman(w,p,n){ char bd[60]; ds = bd; if( n < 0 ){ n = -n; *ds++ = '-'; } prRdig(n,6); *ds = '\0'; prStr(w, p, bd); } A. Bogatyrev, 1992-95 - 116 - Si v UNIX static prRdig(n, d){ if( !n ) return; if( d ) prRdig( n/10, d - 2); tack(n%10, d); } static tack(n, d){ static char im[] = " MDCLXVI"; /* ..1000 500 100 50 10 5 1 */ if( !n ) return; if( 1 <= n && n <= 3 ){ repeat(n, im[d+2]); return; } if( n == 4 ) *ds++ = im[d+2]; if( n == 4 || n == 5 ){ *ds++ = im[d+1]; return; } if( 6 <= n && n <= 8 ){ *ds++ = im[d+1]; repeat(n - 5, im[d+2] ); return; } /* n == 9 */ *ds++ = im[d+2]; *ds++ = im[d]; } static repeat(n, c) char c; { while( n-- > 0 ) *ds++ = c; } /* --------------------------------------------------------- */ static char aChar = 'A'; static prInteger(w, n, base, zero) long n; { /* preobrazuem chislo v stroku */ char bd[128]; int neg = 0; /* < 0 */ if( n < 0 ){ neg = 1; n = -n; } if( base < 0 ){ base = -base; aChar = 'A'; } else { aChar = 'a'; } ds = bd; prUDig( n, base ); *ds = '\0'; /* Teper' pechataem stroku */ prIntStr( bd, w, zero, neg ); } A. Bogatyrev, 1992-95 - 117 - Si v UNIX static prUnsigned(w, n, base, zero) unsigned long n; { char bd[128]; if( base < 0 ){ base = -base; aChar = 'A'; } else { aChar = 'a'; } ds = bd; prUDig( n, base ); *ds = '\0'; /* Teper' pechataem stroku */ prIntStr( bd, w, zero, 0 ); } static prUDig( n, base ) unsigned long n; { unsigned long aSign; if((aSign = n/base ) > 0 ) prUDig( aSign, base ); aSign = n % base; *ds++ = (aSign < 10 ? '0' + aSign : aChar + (aSign - 10)); } static prIntStr( s, width, zero, neg ) char *s; { int ln; /* skol'ko simvolov vyvodit' */ int toLeft = 0; /* k kakomu krayu prizhimat' */ ln = strlen(s); /* dlina stroki s */ /* SHirina polya: vychislit', esli ne ukazano yavno */ if( ! width ){ width = ln; /* shirina polya */ if( neg ) width++; /* 1 simvol dlya minusa */ } if( width < 0 ){ width = -width; toLeft = 1; } if( ! neg ){ /* Polozhitel'noe chislo */ if(width > ln){ if(toLeft){ pr(s, ln); prSpace(width - ln, ' '); } else { prSpace(width - ln, zero ? '0' : ' '); pr(s, ln); } } else { pr(s, ln); } }else{ /* Otricatel'noe chislo */ if(width > ln){ /* Nado zapolnyat' ostavshuyusya chast' polya */ width -- ; /* width soderzhit odnu poziciyu dlya minusa */ if(toLeft){ putchar('-'); pr(s, ln); prSpace(width - ln, ' '); } else{ if( ! zero ){ prSpace(width - ln, ' '); putchar('-'); pr(s,ln); } else { putchar('-'); prSpace(width - ln, '0'); pr(s, ln); } } } else { putchar('-'); pr(s, ln); } } } A. Bogatyrev, 1992-95 - 118 - Si v UNIX /* --------------------------------------------------------- */ main(){ int i, n; static char s[] = "Hello, world!\n"; static char p[] = "Hello, world"; long t = 7654321L; myprintf( "%%abc%Y\n"); myprintf( "%s\n", "abs" ); myprintf( "%5s|\n", "abs" ); myprintf( "%-5s|\n", "abs" ); myprintf( "%5s|\n", "xyzXYZ" ); myprintf( "%-5s|\n", "xyzXYZ" ); myprintf( "%5.5s|\n", "xyzXYZ" ); myprintf( "%-5.5s|\n", "xyzXYZ" ); myprintf( "%r\n", 444 ); myprintf( "%r\n", 999 ); myprintf( "%r\n", 16 ); myprintf( "%r\n", 18 ); myprintf( "%r\n", 479 ); myprintf( "%d\n", 1234 ); myprintf( "%d\n", -1234 ); myprintf( "%ld\n", 97487483 ); myprintf( "%2d|%2d|\n", 1, -3 ); myprintf( "%-2d|%-2d|\n", 1, -3 ); myprintf( "%02d|%2d|\n", 1, -3 ); myprintf( "%-02d|%-2d|\n", 1, -3 ); myprintf( "%5d|\n", -12 ); myprintf( "%05d|\n", -12 ); myprintf( "%-5d|\n", -12 ); myprintf( "%-05d|\n", -12 ); for( i = -6; i < 6; i++ ) myprintf( "width=%2d|%0*d|%0*d|%*d|%*d|\n", i, i, 123, i, -123, i, 123, i, -123); myprintf( "%s at location %a\n", s, s ); myprintf( "%ld\n", t ); n = 1; t = 1L; for( i=0; i < 34; i++ ){ myprintf( "for %2d |%016b|%d|%u|\n\t |%032lb|%ld|%lu|\n", i, n, n, n, t, t, t ); n *= 2; t *= 2; } myprintf( "%8x %8X\n", 7777, 7777 ); myprintf( "|%s|\n", p ); myprintf( "|%10s|\n", p ); myprintf( "|%-10s|\n", p ); myprintf( "|%20s|\n", p ); myprintf( "|%-20s|\n", p ); myprintf( "|%20.10s|\n", p ); myprintf( "|%-20.10s|\n", p ); myprintf( "|%.10s|\n", p ); } A. Bogatyrev, 1992-95 - 119 - Si v UNIX Vydacha etoj programmy: %abcY abs abs| abs | xyzXYZ| xyzXYZ| xyzXY| xyzXY| CDXLIV CMXCIX XVI XVIII CDLXXIX 1234 -1234 97487483 1|-3| 1 |-3| 01|-3| 1 |-3| -12| -0012| -12 | -12 | width=-6|123 |-123 |123 |-123 | width=-5|123 |-123 |123 |-123 | width=-4|123 |-123|123 |-123| width=-3|123|-123|123|-123| width=-2|123|-123|123|-123| width=-1|123|-123|123|-123| width= 0|123|-123|123|-123| width= 1|123|-123|123|-123| width= 2|123|-123|123|-123| width= 3|123|-123|123|-123| width= 4|0123|-123| 123|-123| width= 5|00123|-0123| 123| -123| Hello, world! at location 400980 7654321 for 0 |0000000000000001|1|1| |00000000000000000000000000000001|1|1| for 1 |0000000000000010|2|2| |00000000000000000000000000000010|2|2| for 2 |0000000000000100|4|4| |00000000000000000000000000000100|4|4| for 3 |0000000000001000|8|8| |00000000000000000000000000001000|8|8| for 4 |0000000000010000|16|16| |00000000000000000000000000010000|16|16| for 5 |0000000000100000|32|32| |00000000000000000000000000100000|32|32| for 6 |0000000001000000|64|64| |00000000000000000000000001000000|64|64| for 7 |0000000010000000|128|128| |00000000000000000000000010000000|128|128| for 8 |0000000100000000|256|256| |00000000000000000000000100000000|256|256| for 9 |0000001000000000|512|512| |00000000000000000000001000000000|512|512| for 10 |0000010000000000|1024|1024| A. Bogatyrev, 1992-95 - 120 - Si v UNIX |00000000000000000000010000000000|1024|1024| for 11 |0000100000000000|2048|2048| |00000000000000000000100000000000|2048|2048| for 12 |0001000000000000|4096|4096| |00000000000000000001000000000000|4096|4096| for 13 |0010000000000000|8192|8192| |00000000000000000010000000000000|8192|8192| for 14 |0100000000000000|16384|16384| |00000000000000000100000000000000|16384|16384| for 15 |1000000000000000|32768|32768| |00000000000000001000000000000000|32768|32768| for 16 |10000000000000000|65536|65536| |00000000000000010000000000000000|65536|65536| for 17 |100000000000000000|131072|131072| |00000000000000100000000000000000|131072|131072| for 18 |1000000000000000000|262144|262144| |00000000000001000000000000000000|262144|262144| for 19 |10000000000000000000|524288|524288| |00000000000010000000000000000000|524288|524288| for 20 |100000000000000000000|1048576|1048576| |00000000000100000000000000000000|1048576|1048576| for 21 |1000000000000000000000|2097152|2097152| |00000000001000000000000000000000|2097152|2097152| for 22 |10000000000000000000000|4194304|4194304| |00000000010000000000000000000000|4194304|4194304| for 23 |100000000000000000000000|8388608|8388608| |00000000100000000000000000000000|8388608|8388608| for 24 |1000000000000000000000000|16777216|16777216| |00000001000000000000000000000000|16777216|16777216| for 25 |10000000000000000000000000|33554432|33554432| |00000010000000000000000000000000|33554432|33554432| for 26 |100000000000000000000000000|67108864|67108864| |00000100000000000000000000000000|67108864|67108864| for 27 |1000000000000000000000000000|134217728|134217728| |00001000000000000000000000000000|134217728|134217728| for 28 |10000000000000000000000000000|268435456|268435456| |00010000000000000000000000000000|268435456|268435456| for 29 |100000000000000000000000000000|536870912|536870912| |00100000000000000000000000000000|536870912|536870912| for 30 |1000000000000000000000000000000|1073741824|1073741824| |01000000000000000000000000000000|1073741824|1073741824| for 31 |10000000000000000000000000000000|-2147483648|2147483648| |10000000000000000000000000000000|-2147483648|2147483648| for 32 |0000000000000000|0|0| |00000000000000000000000000000000|0|0| for 33 |0000000000000000|0|0| |00000000000000000000000000000000|0|0| 1e61 1E61 |Hello, world| |Hello, world| |Hello, world| | Hello, world| |Hello, world | | Hello, wor| |Hello, wor | |Hello, wor| 2.62. Rassmotrim programmu summirovaniya vektorov: A. Bogatyrev, 1992-95 - 121 - Si v UNIX int A[1024], B[1024], C[1024]; ... for(i=0; i < 1024; i++) C[i] = A[i] + B[i]; A pochemu by ne for(i=1024-1; i >=0 ; --i) ...; A pochemu by ne v proizvol'nom poryadke? foreach i in (0..1023) ...; Dannyj primer pokazyvaet, chto nekotorye operacii obladayut vrozhdennym paralellizmom, ved' vse 1024 slozhenij mozhno bylo by vypolnyat' parallel'no! Odnako tupoj kompilyator budet skladyvat' ih imenno v tom poryadke, v kotorom vy emu veleli. Tol'ko samye sov- remennye kompilyatory na mnogoprocessornyh sistemah umeyut avtomaticheski rasparalleli- vat' takie cikly. Sam yazyk Si ne soderzhit sredstv ukazaniya parallel'nosti (razve chto snova - biblioteki i sistemnye vyzovy dlya etogo). A. Bogatyrev, 1992-95 - 122 - Si v UNIX Programma schitaetsya mobil'noj, esli ona bez kakih-libo izmenenij ee ishodnogo teksta (libo posle nastrojki nekotoryh konstant pri pomoshchi #define i #ifdef) transli- ruetsya i rabotaet na raznyh tipah mashin (s raznoj razryadnost'yu, sistemoj komand, arhitekturoj, periferiej) pod upravleniem operacionnyh sistem odnogo semejstva. Zame- tim, chto mobil'nymi mogut byt' tol'ko ishodnye teksty programm, ob®ektnye moduli dlya raznyh processorov, estestvenno, nesovmestimy! 3.1. Napishite programmu, pechatayushchuyu razmer tipov dannyh char, short, int, long, float, double, (char *) v bajtah. Ispol'zujte dlya etogo vstroennuyu operaciyu sizeof. 3.2. Sostav'te mobil'nuyu programmu, vyyasnyayushchuyu znacheniya sleduyushchih velichin dlya lyuboj mashiny, na kotoroj rabotaet programma: 1) Naibol'shee dopustimoe znakovoe celoe. 2) Naibol'shee bezznakovoe celoe. 3) Naibol'shee po absolyutnoj velichine otricatel'noe celoe. 4) Tochnost' znacheniya |x|, otlichayushchegosya ot 0, gde x - veshchestvennoe chislo. 5) Naimen'shee znachenie e, takoe chto mashina razlichaet chisla 1 i 1+e (dlya veshchestven- nyh chisel). 3.3. Sostav'te mobil'nuyu programmu, vyyasnyayushchuyu dlinu mashinnogo slova |VM (chislo bitov v peremennoj tipa int). Ukazanie: dlya etogo mozhno ispol'zovat' bitovye sdvigi. 3.4. Nado li pisat' v svoih programmah opredeleniya #define EOF (-1) #define NULL ((char *) 0) /* ili ((void *)0) */ Otvet: NET. Vo-pervyh, eti konstanty uzhe opredeleny v include-fajle, podklyuchaemom po direktive #include <stdio.h> poetomu pravil'nee napisat' imenno etu direktivu. Vo-vtoryh, eto bylo by prosto nep- ravil'no: konkretnye znacheniya etih konstant na dannoj mashine (v dannoj realizacii sistemy) mogut byt' drugimi! CHtoby priderzhivat'sya teh soglashenij, kotoryh priderzhiva- yutsya vse standartnye funkcii dannoj realizacii, vy DOLZHNY brat' eti konstanty iz <stdio.h>. Po toj zhe prichine sleduet pisat' #include <fcntl.h> int fd = open( imyaFajla, O_RDONLY); /* O_WRONLY, O_RDWR */ vmesto int fd = open( imyaFajla, 0); /* 1, 2 */ 3.5. Pochemu mozhet zavershat'sya po zashchite pamyati sleduyushchaya programma? #include <sys/types.h> #include <stdio.h> time_t t; extern time_t time(); ... t = time(0); /* uznat' tekushchee vremya v sekundah s 1 YAnv. 1970 g.*/ Otvet: delo v tom, chto prototip sistemnogo vyzova time() eto: time_t time( time_t *t ); to est' argument dolzhen byt' ukazatelem. My zhe vmesto ukazatelya napisali v kachestve A. Bogatyrev, 1992-95 - 123 - Si v UNIX argumenta 0 (tipa int). Na mashine IBM PC AT 286 ukazatel' - eto 2 slova, a celoe - odno. Nedostayushchee slovo budet vzyato iz steka proizvol'no. V rezul'tate time() polu- chaet v kachestve argumenta ne nulevoj ukazatel', a musor. Pravil'no budet napisat': t = time(NULL); libo (po opredeleniyu time()) time( &t ); a eshche bolee korrektno tak: t = time((time_t *)NULL); Moral': vezde, gde trebuetsya nulevoj ukazatel', sleduet pisat' NULL (ili yavnoe prive- denie nulya k tipu ukazatelya), a ne prosto 0. 3.6. Najdite oshibku: void f(x, s) long x; char *s; { printf( "%ld %s\n", x, s ); } void main(){ f( 12, "hello" ); } |ta programma rabotaet na IBM PC 386, no ne rabotaet na IBM PC 286. Otvet. Zdes' voznikaet ta zhe problema, chto i v primere pro sin(12). Delo v tom, chto f trebuet pervyj argument tipa long (4 bajta na IBM PC 286), my zhe peredaem ej int (2 bajta). V itoge v x popadaet nevernoe znachenie; no bolee togo, nedostayushchie bajty otbirayutsya u sleduyushchego argumenta - s. V itoge i adres stroki stanovitsya nepra- vil'nym, programma obrashchaetsya po nesushchestvuyushchemu adresu i padaet. Na IBM PC 386 i int i long imeyut dlinu 4 bajta, poetomu tam eta oshibka ne proyavlyaetsya! Opyat'-taki, eto povod dlya ispol'zovaniya prototipov funkcij (kogda vy prochitaete pro nih - vernites' k etomu primeru!). Napishite prototip void f(long x, char *s); i oshibki ne budet. V dannom primere my ispol'zovali tip void, kotorogo ne sushestvovalo v rannih versiyah yazyka Si. |tot tip oznachaet, chto funkciya ne vozvrashchaet znacheniya (to est' yavlyaetsya "proceduroj" v smysle yazykov Pascal ili Algol). Esli my ne napishem slovo void pered f, to kompilyator budet schitat' funkciyu f vozvrashchayushchej celoe (int), hotya eta funkciya nichego ne vozvrashchaet (v nej net operatora return). V bol'shinstve sluchaev eto ne prineset vreda i programma budet rabotat'. No zato esli my napishem int x = f((long) 666, "good bye" ); to x poluchit nepredskazuemoe znachenie. Esli zhe f opisana kak void, to napisannyj ope- rator zastavit kompilyator soobshchit' ob oshibke. Tip (void *) oznachaet ukazatel' na chto ugodno (ponyatno, chto k takomu ukazatelyu operacii [], *, -> neprimenimy: snachala sleduet yavno privesti ukazatel' k soderzha- tel'nomu tipu "ukazatel' na tip"). V chastnosti, sejchas stalo prinyato schitat', chto funkciya dinamicheskogo vydeleniya pamyati (memory allocation) malloc() (kotoraya otvodit v kuche|= oblast' pamyati zakazannogo razmera i vydaet ukazatel' na nee) imeet prototip: ____________________ |- V dannoj knige slova "ukazatel'" i "ssylka" upotreblyayutsya v odnom i tom zhe smysle. Esli vy obratites' k yazyku Si++, to obnaruzhite, chto tam eti dva termina (pointer i reference) oznachayut raznye ponyatiya (hotya i shodnye). ____________________ A. Bogatyrev, 1992-95 - 124 - Si v UNIX void *malloc(unsigned size); /* size bajt */ char *s = (char *) malloc( strlen(buf)+1 ); struct ST *p = (struct ST *) malloc( sizeof(struct ST)); /* ili sizeof(*p) */ hotya ran'she prinyato bylo char *malloc(); 3.7. Pogovorim pro operator sizeof. Otmetim rasprostranennuyu oshibku, kogda sizeof prinimayut za funkciyu. |to ne tak! sizeof vychislyaetsya kompilyatorom pri translyacii programmy, a ne programmoj vo vremya vypolneniya. Pust' char a[] = "abcdefg"; char *b = "hijklmn"; Togda sizeof(a) est' 8 (bajt \0 na konce - schitaetsya) sizeof(b) est' 2 na PDP-11 (razmer ukazatelya) strlen(a) est' 7 strlen(b) est' 7 Esli my sdelaem b = "This ia a new line"; strcpy(a, "abc"); to vse ravno sizeof(b) ostanetsya ravno 2 sizeof(a) 8 Takim obrazom sizeof vydaet kolichestvo zarezervirovannoj dlya peremennoj pamyati (v bajtah), nezavisimo ot tekushchego ee soderzhimogo. Operaciya sizeof primenima dazhe k vyrazheniyam. V etom sluchae ona soobshchaet nam, kakov budet razmer u rezul'tata etogo vyrazheniya. Samo vyrazhenie pri etom ne vychislya- etsya, tak v double f(){ printf( "Hi!\n"); return 12.34; } main(){ int x = 2; long y = 4; printf( "%u\n", sizeof(x + y + f())); } budet napechatano znachenie, sovpadayushchee s sizeof(double), a fraza "Hi!" ne budet nape- chatana. Kogda operator sizeof primenyaetsya k peremennoj (a ne k imeni tipa), mozhno ne pisat' kruglye skobki: sizeof(char *); no sizeof x; 3.8. Napishite ob®edinenie, v kotorom mozhet hranit'sya libo ukazatel', libo celoe, libo dejstvitel'noe chislo. Otvet: union all{ char *s; int i; double f; ____________________ |= "Kucha" (heap, pool) - oblast' staticheskoj pamyati, uvelichivayushchayasya po mere nadob- nosti, i prednaznachennaya kak raz dlya hraneniya dinamicheski otvedennyh dannyh. A. Bogatyrev, 1992-95 - 125 - Si v UNIX } x; x.i = 12 ; printf("%d\n", x.i); x.f = 3.14; printf("%f\n", x.f); x.s = "Hi, there"; printf("%s\n", x.s); printf("int=%d double=%d (char *)=%d all=%d\n", sizeof(int), sizeof(double), sizeof(char *), sizeof x); V dannom primere vy obnaruzhite, chto razmer peremennoj x raven maksimal'nomu iz razme- rov tipov int, double, char *. Esli vy hotite ispol'zovat' odnu i tu zhe peremennuyu dlya hraneniya dannyh raznyh tipov, to dlya polucheniya mobil'noj programmy vy dolzhny pol'zovat'sya tol'ko ob®edineni- yami i nikogda ne privyazyvat'sya k dline slova i predstavleniyu etih tipov dannyh na konkretnoj ZVM! Ran'she, kogda programmisty ne dumali o mobil'nosti, oni pisali prog- rammy, gde v odnoj peremenoj tipa int hranili v zavisimosti ot nuzhdy to celye znache- niya, to ukazateli (eto bylo na mashinah PDP i VAX). Uvy, takie programmy okazalis' neperenosimy na mashiny, na kotoryh sizeof(int) != sizeof(char *), bolee togo, oni okazalis' ves'ma tumanny dlya ponimaniya ih drugimi lyud'mi. Ne sledujte etomu stilyu (takoj stil' amerikancy nazyvayut "poor style"), bolee togo, vsemi silami izbegajte ego! Sravnite dva primera, ispol'zuyushchie dva stilya programmirovaniya. Pervyj stil' ne tak ploh, kak tol'ko chto opisannyj, no vse zhe my rekomenduem ispol'zovat' tol'ko vto- roj: /* STILX PERVYJ: YAVNYE PREOBRAZOVANIYA TIPOV */ typedef void *PTR; /* universal'nyj ukazatel' */ struct a { int x, y; PTR pa; } A; struct b { double u, v; PTR pb; } B; #define Aptr(p) ((struct a *)(p)) #define Bptr(p) ((struct b *)(p)) PTR ptr1, ptr2; main(){ ptr1 = &A; ptr2 = &B; Bptr(ptr2)->u = Aptr(ptr1)->x = 77; printf("%f %d\n", B.u, A.x); } /* STILX VTOROJ: OB'EDINENIE */ /* predvaritel'noe ob®yavlenie: */ extern struct a; extern struct b; /* universal'nyj tip dannyh: */ typedef union everything { int i; double d; char *s; struct a *ap; struct b *bp; } ALL; struct a { int x, y; ALL pa; } A; struct b { double u, v; ALL pb; } B; ALL ptr1, ptr2, zz; main(){ ptr1.ap = &A; ptr2.bp = &B; zz.i = 77; ptr2.bp->u = ptr1.ap->x = zz.i; printf("%f %d\n", B.u, A.x); } 3.9. Dlya vydeleniya klassov simvolov (naprimer cifr), sleduet pol'zovat'sya makrosami iz include-fajla <ctype.h> Tak vmesto if( '0' <= c && c <= '9' ) ... A. Bogatyrev, 1992-95 - 126 - Si v UNIX sleduet ispol'zovat' #include <ctype.h> ..... if(isdigit(c)) ... i vmesto if((c >='a' && c <= 'z') || (c >= 'A' && c <= 'Z')) ... nado if(isalpha(c)) ... Delo v tom, chto sravneniya < i > zavisyat ot raspolozheniya bukv v ispol'zuemoj kodi- rovke. No naprimer, v kodirovke KOI-8 russkie bukvy raspolozheny NE v alfavitnom poryadke. Vsledstvie etogo, esli dlya char c1, c2; c1 < c2 to eto eshche ne znachit, chto bukva c1 predshestvuet bukve c2 v alfavite! Leksikografiches- koe sravnenie trebuet special'noj perekodirovki bukv k "uporyadochennoj" kodirovke. Analogichno, sravnenie if( c >= 'a' && c <= 'ya' ) skoree vsego ne dast ozhidaemogo rezul'tata. Makroopredeleniya zhe v <ctype.h> ispol'- zuyut massiv flagov dlya kazhdoj bukvy kodirovki, i potomu ne zavisyat ot poryadka bukv (i rabotayut bystree). Ideya realizacii takova: extern unsigned char _ctype[]; /*massiv flagov*/ #define US(c) (sizeof(c)==sizeof(char)?((c)&0xFF):(c)) /* podavlenie rasshireniya znakovogo bita */ /* F L A G I */ #define _U 01 /* uppercase: bol'shaya bukva */ #define _L 02 /* lowercase: malaya bukva */ #define _N 04 /* number: cifra */ #define _S 010 /* space: probel */ /* ... est' i drugie flagi ... */ #define isalpha(c) ((_ctype+1)[US(c)] & (_U|_L) ) #define isupper(c) ((_ctype+1)[US(c)] & _U ) #define islower(c) ((_ctype+1)[US(c)] & _L ) #define isdigit(c) ((_ctype+1)[US(c)] & _N ) #define isalnum(c) ((_ctype+1)[US(c)] & (_U|_L|_N)) #define tolower(c) ((c) + 'a' - 'A' ) #define toupper(c) ((c) + 'A' - 'a' ) gde massiv _ctype[] zapolnen zaranee (eto proinicializirovannye staticheskie dannye) i hranitsya v standartnoj biblioteke Si. Vot ego fragment: unsigned char _ctype[256 /* razmer alfavita */ + 1] = { /* EOF kod (-1) */ 0, ... /* '1' kod 061 0x31 */ _N, ... /* 'A' kod 0101 0x41 */ _U, ... /* 'a' kod 0141 0x61 */ _L, ... }; A. Bogatyrev, 1992-95 - 127 - Si v UNIX Vyigrysh v skorosti poluchaetsya vot pochemu: esli my opredelim|- #define isalpha(c) (((c) >= 'a' && (c) <= 'z') || \ ((c) >= 'A' && (c) <= 'Z')) to etot operator sostoit iz 7 operacij. Esli zhe my ispol'zuem isalpha iz <ctype.h> (kak opredeleno vyshe) - my ispol'zuem tol'ko dve operacii: indeksaciyu i proverku bitovoj maski &. Operacii _ctype+1 i _U|_L vychislyayutsya do konstant eshche pri kompilya- cii, i poetomu ne vyzyvayut generacii mashinnyh komand. Opredelennye vyshe toupper i tolower rabotayut verno lish' v kodirovke ASCII|=, v kotoroj vse latinskie bukvy raspolozheny podryad i po alfavitu. Obratite vnimanie, chto tolower imeet smysl primenyat' tol'ko k bol'shim bukvam, a toupper - tol'ko k malen'- kim: if( isupper(c) ) c = tolower(c); Sushchestvuet eshche cherezvychajno poleznyj makros isspace(c), kotoryj mozhno bylo by oprede- lit' kak #define isspace(c) (c==' ' ||c=='\t'||c=='\f'|| \ c=='\n'||c=='\r') ili #define isspace(c) (strchr(" \t\f\n\r",(c)) != NULL) Na samom dele on, konechno, realizovan cherez flagi v _ctype[]. On ispol'zuetsya dlya opredeleniya simvolov-probelov, sluzhashchih zapolnitelyami promezhutkov mezhdu slovami teksta. Est' eshche dva neredko ispol'zuemyh makrosa: isprint(c), proveryayushchij, yavlyaetsya li c PECHATNYM simvolom, t.e. imeyushchim izobrazhenie na ekrane; i iscntrl(c), oznachayushchij, chto simvol c yavlyaetsya upravlyayushchim, t.e. pri ego vyvode na terminal nichego ne izobra- zitsya, no terminal proizvedet nekotoroe dejstvie, vrode ochistki ekrana ili peremeshche- niya kursora v kakom-to napravlenii. Oni nuzhny, kak pravilo, dlya otobrazheniya upravlya- yushchih ("kontrolovskih") simvolov v special'nom pechatnom vide, vrode ^A dlya koda '\01'. Zadanie: issledujte kodirovku i <ctype.h> na vashej mashine. Napishite funkciyu leksikograficheskogo sravneniya bukv i strok. Ukazanie: pust' bukvy imeyut takie kody (eto ne sootvetstvuet real'nosti!): bukva: a b v g d e kod: 1 4 2 5 3 0 nuzhno: 0 1 2 3 4 5 Togda ideya funkcii Ctou perekodirovki k uporyadochennomu alfavitu takova: unsigned char UU[] = { 5, 0, 2, 4, 1, 3 }; /* v dejstvitel'nosti - 256 elementov: UU[256] */ Ctou(c) unsigned char c; { return UU[c]; } int strcmp(s1, s2) char *s1, *s2; { /* Proignorirovat' sovpadayushchie nachala strok */ while(*s1 && *s1 == *s2) s1++, s2++; /* Vernut' raznost' [ne]sovpavshih simvolov */ return Ctou(*s1) - Ctou(*s2); ____________________ |- Obratite vnimanie, chto simvol \ v konce stroki makroopredeleniya pozvolyaet pro- dolzhit' makros na sleduyushchej stroke, poetomu makros mozhet sostoyat' iz mnogih strok. |= ASCII - American Standard Code for Information Interchange - naibolee rasprost- ranennaya v mire kodirovka (Amerikanskij standart). A. Bogatyrev, 1992-95 - 128 - Si v UNIX } Razberites' s principom formirovaniya massiva UU. 3.10. V sovremennyh UNIX-ah s podderzhkoj razlichnyh yazykov tablica ctype zagruzhaetsya iz nekotoryh sistemnyh fajlov - dlya kazhdogo yazyka svoya. Dlya kakogo yazyka - vybira- etsya po soderzhimomu peremennoj okruzheniya LANG. Esli peremennaya ne zadana - ispol'zu- etsya znachenie "C", anglijskij yazyk. Zagruzka tablic dolzhna proishodit' yavno, vyzovom ... #include <locale.h> ... main(){ setlocale(LC_ALL, ""); ... vse ostal'noe ... } 3.11. Vernemsya k nashej lyubimoj probleme so znakovym bitom u tipa char. #include <stdio.h> #include <locale.h> #include <ctype.h> int main(int ac, char *av[]){ char c; char *string = "abvgdezhziklmnop"; setlocale(LC_ALL, ""); for(;c = *string;string++){ #ifdef DEBUG printf("%c %d %d\n", *string, *string, c); #endif if(isprint(c)) printf("%c - pechatnyj simvol\n", c); } return 0; } |ta programma neozhidanno pechataet % a.out v - pechatnyj simvol z - pechatnyj simvol I vse. V chem delo??? Rassmotrim k primeru simvol 'g'. Ego kod '\307'. V operatore c = *string; Simvol c poluchaet znachenie -57 (desyatichnoe), kotoroe OTRICATELXNO. V sistemnom fajle /usr/include/ctype.h makros isprint opredelen tak: #define isprint(c) ((_ctype + 1)[c] & (_P|_U|_L|_N|_B)) I znachenie c ispol'zuetsya v nashem sluchae kak otricatel'nyj indeks v massive, ibo indeks privoditsya k tipu int (signed). Otkuda teper' izvlekaetsya znachenie flagov - nam neizvestno; mozhno tol'ko s uverennost'yu skazat', chto NE iz massiva _ctype. A. Bogatyrev, 1992-95 - 129 - Si v UNIX Problemu reshaet libo ispol'zovanie isprint(c & 0xFF) libo isprint((unsigned char) c) libo ob®yavlenie v nashem primere unsigned char c; V pervom sluchae my yavno privodim signed k unsigned bitovoj operaciej, obnulyaya lishnie bity. Vo vtorom i tret'em - unsigned char rasshiryaetsya v unsigned int, kotoryj osta- netsya polozhitel'nym. Veroyatno, vtoroj put' predpochtitel'nee. 3.12. Itak, snova napomnim, chto russkie bukvy char, a ne unsigned char dayut otrica- tel'nye indeksy v massive. char c = 'g'; int x[256]; ...x[c]... /* indeks < 0 */ ...x['g']... Poetomu bajtovye indeksy dolzhny byt' libo unsigned char, libo & 0xFF. Kak v sleduyu- shchem primere: /* Programma preobrazovaniya simvolov v fajle: transliteraciya tr abcd prst zamenyaet stroki xxxxdbcaxxxx -> xxxxtrspxxxx Po motivam knigi M.Dansmura i G.Dejvisa. */ #include <stdio.h> #define ASCII 256 /* chislo bukv v alfavite ASCII */ /* BUFSIZ opredeleno v stdio.h */ char mt[ ASCII ]; /* tablica perekodirovki */ /* nachal'naya razmetka tablicy */ void mtinit(){ register int i; for( i=0; i < ASCII; i++ ) mt[i] = (char) i; } A. Bogatyrev, 1992-95 - 130 - Si v UNIX int main(int argc, char *argv[]) { register char *tin, *tout; /* unsigned char */ char buffer[ BUFSIZ ]; if( argc != 3 ){ fprintf( stderr, "Vyzov: %s chto naCHto\n", argv[0] ); return(1); } tin = argv[1]; tout = argv[2]; if( strlen(tin) != strlen(tout)){ fprintf( stderr, "stroki raznoj dliny\n" ); return(2); } mtinit(); do{ mt[ (*tin++) & 0xFF ] = *tout++; /* *tin - imeet tip char. * & 0xFF podavlyaet rasshirenie znaka */ } while( *tin ); tout = mt; while( fgets( buffer, BUFSIZ, stdin ) != NULL ){ for( tin = buffer; *tin; tin++ ) *tin = tout[ *tin & 0xFF ]; fputs( buffer, stdout ); } return(0); } 3.13. int main(int ac, char *av[]){ char c = 'g'; if('a' <= c && c < 256) printf("|to odna bukva.\n"); return 0; } Uvy, eta programma ne pechataet NICHEGO. Prosto potomu, chto signed char v sravnenii (v operatore if) privoditsya k tipu int. A kak celoe chislo - russkaya bukva otricatel'na. Snova resheniem yavlyaetsya libo ispol'zovanie vezde (c & 0xFF), libo ob®yavlenie unsigned char c. V chastnosti, etot primer pokazyvaet, chto NELXZYA prosto tak sravni- vat' dve peremennye tipa char. Nuzhno prinimat' predohranitel'nye mery po podavleniyu rasshireniya znaka: if((ch1 & 0xFF) < (ch2 & 0xFF))...; Dlya unsigned char takoj problemy ne budet. 3.14. Pochemu neverno: A. Bogatyrev, 1992-95 - 131 - Si v UNIX #include <stdio.h> main(){ char c; while((c = getchar()) != EOF) putchar(c); } Potomu chto c opisano kak char, v to vremya kak EOF - znachenie tipa int ravnoe (-1). Russkaya bukva "Bol'shoj tverdyj znak" v kodirovke KOI-8 imeet kod '\377' (0xFF). Esli my podadim na vhod etoj programme etu bukvu, to v sravnenii signed char so zna- cheniem znakovogo celogo EOF, c budet privedeno tozhe k znakovomu celomu - rasshireniem znaka. 0xFF prevratitsya v (-1), chto oznachaet, chto postupil simvol EOF. Syurpriz!!! Posemu dannaya programma budet delat' vid, chto v lyubom fajle s bol'shim russkim tverdym znakom posle etogo znaka (i vklyuchaya ego) dal'she nichego net. CHto est' dosadnoe zabluzh- denie. Resheniem sluzhit PRAVILXNOE ob®yavlenie int c. 3.15. Izuchite povedenie programmy #define TYPE char void f(TYPE c){ if(c == 'j') printf("|to bukva j\n"); printf("c=%c c=\\%03o c=%03d c=0x%0X\n", c, c, c, c); } int main(){ f('g'); f('j'); f('z'); f('Z'); return 0; } kogda TYPE opredeleno kak char, unsigned char, int. Ob®yasnite povedenie. Vydachi v etih treh sluchayah takovy (int == 32 bita): c=g c=\37777777707 c=-57 c=0xFFFFFFC7 |to bukva j c=j c=\37777777712 c=-54 c=0xFFFFFFCA c=z c=\172 c=122 c=0x7A c=Z c=\132 c=090 c=0x5A c=g c=\307 c=199 c=0xC7 c=j c=\312 c=202 c=0xCA c=z c=\172 c=122 c=0x7A c=Z c=\132 c=090 c=0x5A i snova kak 1 sluchaj. Rassmotrite al'ternativu if(c == (unsigned char) 'j') printf("|to bukva j\n"); gde predpolagaetsya, chto znak u russkih bukv i u c NE rasshiryaetsya. V dannom sluchae fraza '|to bukva j' ne pechataetsya ni s tipom char, ni s tipom int, poskol'ku v srav- nenii c privoditsya k tipu signed int rasshireniem znakovogo bita (kotoryj raven 1). Sleva poluchaetsya otricatel'noe chislo! V takih sluchayah vnov' sleduet pisat' if((unsigned char)c == (unsigned char)'j') printf("|to bukva j\n"); A. Bogatyrev, 1992-95 - 132 - Si v UNIX 3.16. Obychno voznikayut problemy pri napisanii funkcij s peremennym chislom argumen- tov. V yazyke Si eta problema reshaetsya ispol'zovaniem makrosov va_args, ne zavisyashchih ot soglashenij o vyzovah funkcij na dannoj mashine, i ispol'zuyushchih eti makrosy speci- al'nyh funkcij. Est' dva stilya oformleniya takih programm: s ispol'zovaniem <varargs.h> i <stdarg.h>. Pervyj byl prodemonstrirovan v pervoj glave na primere funkcii poly(). Dlya illyustracii vtorogo privedem primer funkcii trassirovki, zapisy- vayushchej sobshchenie v fajl: #include <stdio.h> #include <stdarg.h> void trace(char *fmt, ...) { va_list args; static FILE *fp = NULL; if(fp == NULL){ if((fp = fopen("TRACE", "w")) == NULL) return; } va_start(args, fmt); /* vtoroj argument: arg-t posle kotorogo * v zagolovke funkcii idet ... */ vfprintf(fp, fmt, args); /* bibliotechnaya f-ciya */ fflush(fp); /* vytolknut' soobshchenie v fajl */ va_end(args); } main(){ trace( "%s\n", "Go home."); trace( "%d %d\n", 12, 34); } Simvol `...' (troetochie) v zagolovke funkcii oboznachaet peremennyj (vozmozhno pustoj) spisok argumentov. On dolzhen byt' samym poslednim, sleduya za vsemi obyazatel'nymi argumentami funkcii. Makros va_arg(args,type), izvlekayushchij iz peremennogo spiska argumentov `...' ocherednoe znachenie tipa type, odinakov v oboeh modelyah. Funkciya vfprintf mozhet byt' napisana cherez funkciyu vsprintf (v dejstvitel'nosti obe funkcii - standartnye): int vfprintf(FILE *fp, const char *fmt, va_list args){ /*static*/ char buffer[1024]; int res; res = vsprintf(buffer, fmt, args); fputs(buffer, fp); return res; } Funkciya vsprintf(str,fmt,args); analogichna funkcii sprintf(str,fmt,...) - zapisyvaet preobrazovannuyu po formatu stroku v bajtovyj massiv str, no ispol'zuetsya v kontekste, podobnom privedennomu. V konec sformirovannoj stroki sprintf zapisyvaet '\0'. 3.17. Napishite funkciyu printf, ponimayushchuyu formaty %c (bukva), %d (celoe), %o (vos'- merichnoe), %x (shestnadcaterichnoe), %b (dvoichnoe), %r (rimskoe), %s (stroka), %ld (dlinnoe celoe). Otvet smotri v prilozhenii. 3.18. Dlya togo, chtoby odin i tot zhe ishodnyj tekst programmy translirovalsya na raz- nyh mashinah (v raznyh sistemah), prihoditsya vydelyat' v programme sistemno-zavisimye chasti. Takie chasti dolzhny po-raznomu vyglyadet' na raznyh mashinah, poetomu ih oform- lyayut v vide tak nazyvaemyh "uslovno kompiliruemyh" chastej: #ifdef XX ... variant1 #else ... variant2 #endif A. Bogatyrev, 1992-95 - 133 - Si v UNIX |ta direktiva preprocessora vedet sebya sleduyushchim obrazom: esli makros s imenem XX byl opredelen #define XX to v programmu podstavlyaetsya variant1, esli zhe net - variant2. Operator #else ne obya- zatelen - pri ego otsutstvii variant2 pust. Sushchestvuet takzhe operator #ifndef, koto- ryj podstavlyaet variant1 esli makros XX ne opredelen. Est' eshche i operator #elif - else if: #ifdef makro1 ... #elif makro2 ... #else ... #endif Opredelit' makros mozhno ne tol'ko pri pomoshchi #define, no i pri pomoshchi klyucha kompilya- tora, tak cc -DXX file.c ... sootvetstvuet vklyucheniyu v nachalo fajla file.c direktivy #define XX A dlya programmy main(){ #ifdef XX printf( "XX = %d\n", XX); #else printf( "XX undefined\n"); #endif } klyuch cc -D"XX=2" file.c ... ekvivalenten zadaniyu direktivy #define XX 2 CHto budet, esli sovsem ne zadat' klyuch -D v dannom primere? |tot priem ispol'zuetsya v chastnosti v teh sluchayah, kogda kakie-to standartnye tipy ili funkcii v dannoj sisteme nosyat drugie nazvaniya: cc -Dvoid=int ... cc -Dstrchr=index ... V nekotoryh sistemah kompilyator avtomaticheski opredelyaet special'nye makrosy: tak kompilyatory v UNIX neyavno podstavlyayut odin iz klyuchej (ili neskol'ko srazu): -DM_UNIX -DM_XENIX -Dunix -DM_SYSV -D__SVR4 -DUSG ... byvayut i drugie A. Bogatyrev, 1992-95 - 134 - Si v UNIX |to pozvolyaet programme "uznat'", chto ee kompiliruyut dlya sistemy UNIX. Bolee pod- robno pro eto napisano v dokumentacii po komande cc. 3.19. Operator #ifdef primenyaetsya v include-fajlah, chtoby isklyuchit' povtornoe vklyu- chenie odnogo i togo zhe fajla. Pust' fajly aa.h i bb.h soderzhat aa.h bb.h #include "cc.h" #include "cc.h" typedef unsigned long ulong; typedef int cnt_t; A fajly cc.h i 00.c soderzhat cc.h 00.c ... #include "aa.h" struct II { int x, y; }; #include "bb.h" ... main(){ ... } V etom sluchae tekst fajla cc.h budet vstavlen v 00.c dvazhdy: iz aa.h i iz bb.h. Pri kompilyacii 00.c kompilyator soobshchit "Pereopredelenie struktury II". CHtoby include- fajl ne podstavlyalsya eshche raz, esli on uzhe odnazhdy byl vklyuchen, priduman sleduyushchij priem - sleduet oformlyat' fajly vklyuchenij tak: /* fajl cc.h */ #ifndef _CC_H # define _CC_H /* opredelyaetsya pri pervom vklyuchenii */ ... struct II { int x, y; }; ... #endif /* _CC_H */ Vtoroe i posleduyushchie vklyucheniya takogo fajla budut podstavlyat' pustoe mesto, chto i trebuetsya. Dlya fajla <sys/types.h> bylo by ispol'zovano makroopredelenie _SYS_TYPES_H. 3.20. Lyuboj makros mozhno otmenit', napisav direktivu #undef imyaMakro Primer: #include <stdio.h> #undef M_UNIX #undef M_SYSV main() { putchar('!'); #undef putchar #define putchar(c) printf( "Bukva '%c'\n", c); putchar('?'); #if defined(M_UNIX) || defined(M_SYSV) /* ili prosto #if M_UNIX */ printf("|to UNIX\n"); #else printf("|to ne UNIX\n"); #endif /* UNIX */ } Obychno #undef ispol'zuetsya imenno dlya pereopredeleniya makrosa, kak putchar v etom primere (delo v tom, chto putchar - eto makros iz <stdio.h>). Direktiva #if, ispol'zovannaya nami, yavlyaetsya rasshireniem operatora #ifdef i podstavlyaet tekst esli vypolneno ukazannoe uslovie: A. Bogatyrev, 1992-95 - 135 - Si v UNIX #if defined(MACRO) /* ravno #ifdef(MACRO) */ #if !defined(MACRO) /* ravno #ifndef(MACRO) */ #if VALUE > 15 /* esli celaya konstanta #define VALUE 25 bol'she 15 (==, !=, <=, ...) */ #if COND1 || COND2 /* esli verno lyuboe iz uslovij */ #if COND1 && COND2 /* esli verny oba usloviya */ Direktiva #if dopuskaet ispol'zovanie v kachestve argumenta dovol'no slozhnyh vyrazhe- nij, vrode #if !defined(M1) && (defined(M2) || defined(M3)) 3.21. Uslovnaya kompilyaciya mozhet ispol'zovat'sya dlya trassirovki programm: #ifdef DEBUG # define DEBUGF(body) \ { \ body; \ } #else # define DEBUGF(body) #endif int f(int x){ return x*x; } int main(int ac, char *av[]){ int x = 21; DEBUGF(x = f(x); printf("%s equals to %d\n", "x", x)); printf("x=%d\n", x); } Pri kompilyacii cc -DDEBUG file.c v vyhodnom potoke programmy budet prisutstvovat' otladochnaya vydacha. Pri kompilyacii bez -DDEBUG etoj vydachi ne budet. 3.22. V yazyke C++ (razvitie yazyka Si) slova class, delete, friend, new, operator, overload, template, public, private, protected, this, virtual yavlyayutsya zarezerviro- vannymi (klyuchevymi). |to mozhet vyzvat' nebol'shuyu problemu pri perenose teksta prog- rammy na Si v sistemu programmirovaniya C++, naprimer: #include <termio.h> ... int fd_tty = 2; /* stderr */ struct termio old, new; ioctl (fd_tty, TCGETA, &old); new = old; new.c_lflag |= ECHO | ICANON; ioctl (fd_tty, TCSETAW, &new); ... Stroki, soderzhashchie imya peremennoj (ili funkcii) new, okazhutsya nepravil'nymi v C++. Proshche vsego eta problema reshaetsya pereimenovaniem peremennoj (ili funkcii). CHtoby ne proizvodit' pravki vo vsem tekste, dostatochno pereopredelit' imya pri pomoshchi direktivy define: A. Bogatyrev, 1992-95 - 136 - Si v UNIX #define new new_modes ... staryj tekst ... #undef new Pri perenose programmy na Si v C++ sleduet takzhe uchest', chto v C++ dlya kazhdoj funkcii dolzhen byt' zadan prototip, prezhde chem eta funkciya budet ispol'zovana (Si pozvolyaet opuskat' prototipy dlya mnogih funkcij, osobenno vozvrashchayushchih znacheniya tipov int ili void). A. Bogatyrev, 1992-95 - 137 - Si v UNIX Fajly predstavlyayut soboj oblasti pamyati na vneshnem nositele (kak pravilo magnit- nom diske), prednaznachennye dlya: - hraneniya dannyh, prevoshodyashchih po ob®emu pamyat' komp'yutera (men'she, razumeetsya, tozhe mozhno); - dolgovremennogo hraneniya informacii (ona sohranyaetsya pri vyklyuchenii mashiny). V UNIX i v MS DOS fajly ne imeyut predopredelennoj struktury i predstavlyayut soboj prosto linejnye massivy bajt. Esli vy hotite zadat' nekotoruyu strukturu hranimoj informacii - vy dolzhny pozabotit'sya ob etom v svoej programme sami. Fajly otlichayutsya ot obychnyh massivov tem, chto - oni mogut izmenyat' svoj razmer; - obrashchenie k elementam etih massivov proizvoditsya ne pri pomoshchi operacii indeksa- cii [], a pri pomoshchi special'nyh sistemnyh vyzovov i funkcij; - dostup k elementam fajla proishodit v tak nazyvaemoj "pozicii chteniya/zapisi", kotoraya avtomaticheski prodvigaetsya pri operaciyah chteniya/zapisi, t.e. fajl pros- matrivaetsya posledovatel'no. Est', pravda, funkcii dlya proizvol'nogo izmeneniya etoj pozicii. Fajly imeyut imena i organizovany v ierarhicheskuyu drevovidnuyu strukturu iz katalogov i prostyh fajlov. Ob etom i o sisteme imenovaniya fajlov prochitajte v dokumentacii po UNIX. 4.1. Dlya raboty s kakim-libo fajlom nasha programma dolzhna otkryt' etot fajl - usta- novit' svyaz' mezhdu imenem fajla i nekotoroj peremennoj v programme. Pri otkrytii fajla v yadre operacionnoj sistemy vydelyaetsya "svyazuyushchaya" struktura file "otkrytyj fajl", soderzhashchaya: f_offset: ukazatel' pozicii chteniya/zapisi, kotoryj v dal'nejshem my budem oboznachat' kak RWptr. |to long-chislo, ravnoe rasstoyaniyu v bajtah ot nachala fajla do pozicii chteniya/zapisi; f_flag: rezhimy otkrytiya fajla: chtenie, zapis', chtenie i zapis', nekotorye dopolnitel'nye flagi; f_inode: raspolozhenie fajla na diske (v UNIX - v vide ssylki na I-uzel fajla|-); i koe-chto eshche. U kazhdogo processa imeetsya tablica otkrytyh im fajlov - eto massiv ssylok na upomyanutye "svyazuyushchie" struktury|=. Pri otkrytii fajla v etoj tablice ishchetsya ____________________ |- I-uzel (I-node, indeksnyj uzel) - svoeobraznyj "pasport", kotoryj est' u kazhdogo fajla (v tom chisle i kataloga). V nem soderzhatsya: - dlina fajla long di_size; - nomer vladel'ca fajla int di_uid; - kody dostupa i tip fajla ushort di_mode; - vremya sozdaniya i poslednej modifikacii time_t di_ctime, di_mtime; - nachalo tablicy blokov fajla char di_addr[...]; - kolichestvo imen fajla short di_nlink; i.t.p. Soderzhimoe nekotoryh polej etogo pasporta mozhno uznat' vyzovom stat(). Vse I-uzly sobrany v edinuyu oblast' v nachale fajlovoj sistemy - tak nazyvaemyj I-fajl. Vse I- uzly pronumerovany, nachinaya s nomera 1. Kornevoj katalog (fajl s imenem "/") kak pravilo imeet I-uzel nomer 2. |= U kazhdogo processa v UNIX takzhe est' svoj "pasport". CHast' etogo pasporta naho- ditsya v tablice processov v yadre OS, a chast' - "prikleena" k samomu processu, odnako ne dostupna iz programmy neposredstvenno. |ta vtoraya chast' pasporta nosit nazvanie "u-area" ili struktura user. V nee, v chastnosti, vhodyat tablica otkrytyh processom fajlov A. Bogatyrev, 1992-95 - 138 - Si v UNIX svobodnaya yachejka, v nee zanositsya ssylka na strukturu "otkrytyj fajl" v yadre, i INDEKS etoj yachejki vydaetsya v vashu programmu v vide celogo chisla - tak nazyvaemogo "deskriptora fajla". Pri zakrytii fajla svyaznaya struktura v yadre unichtozhaetsya, yachejka v tablice schi- taetsya svobodnoj, t.e. svyaz' programmy i fajla razryvaetsya. Deskriptory yavlyayutsya lokal'nymi dlya kazhdoj programmy. T.e. esli dve programmy otkryli odin i tot zhe fajl - deskriptory etogo fajla v kazhdoj iz nih ne obyazatel'no sovpadut (hotya i mogut). Obratno: odinakovye deskriptory (nomera) v raznyh program- mah ne obyazatel'no oboznachayut odin i tot zhe fajl. Sleduet uchest' i eshche odnu veshch': neskol'ko ili odin processov mogut otkryt' odin i tot zhe fajl odnovremenno neskol'ko raz. Pri etom budet sozdano neskol'ko "svyazuyushchih" struktur (po odnoj dlya kazhdogo otkrytiya); kazhdaya iz nih budet imet' SVOJ ukazatel' chteniya/zapisi. Vozmozhna i situa- ciya, kogda neskol'ko deskriptorov ssylayutsya k odnoj strukture - smotri nizhe opisanie vyzova dup2. fd u_ofile[] struct file 0 ## ------------- 1---##---------------->| f_flag | 2 ## | f_count=3 | 3---##---------------->| f_inode---------* ... ## *-------------->| f_offset | | process1 | ------!------ | | ! V 0 ## | struct file ! struct inode 1 ## | ------------- ! ------------- 2---##-* | f_flag | ! | i_count=2 | 3---##--->| f_count=1 | ! | i_addr[]----* ... ## | f_inode----------!-->| ... | | adresa process2 | f_offset | ! ------------- | blokov -------!----- *=========* | fajla ! ! V 0 ! ukazateli R/W ! i_size-1 @@@@@@@@@@@!@@@@@@@@@@@@@@@@@@@@@!@@@@@@ fajl na diske /* otkryt' fajl */ int fd = open(char imya_fajla[], int kak_otkryt'); ... /* kakie-to operacii s fajlom */ close(fd); /* zakryt' */ Parametr kak_otkryt': #include <fcntl.h> O_RDONLY - tol'ko dlya chteniya. O_WRONLY - tol'ko dlya zapisi. O_RDWR - dlya chteniya i zapisi. O_APPEND - inogda ispol'zuetsya vmeste s otkrytiem dlya zapisi, "dobavlenie" v fajl: O_WRONLY|O_APPEND, O_RDWR|O_APPEND Esli fajl eshche ne sushchestvoval, to ego nel'zya otkryt': open vernet znachenie (-1), ____________________ struct file *u_ofile[NOFILE]; ssylka na I-uzel tekushchego kataloga struct inode *u_cdir; a takzhe ssylka na chast' pasporta v tablice processov struct proc *u_procp; A. Bogatyrev, 1992-95 - 139 - Si v UNIX signaliziruyushchee ob oshibke. V etom sluchae fajl nado sozdat': int fd = creat(char imya_fajla[], int kody_dostupa); Deskriptor fd budet otkryt dlya zapisi v etot novyj pustoj fajl. Esli zhe fajl uzhe sushchestvoval, creat opustoshaet ego, t.e. unichtozhaet ego prezhnee soderzhimoe i delaet ego dlinu ravnoj 0L bajt. Kody_dostupa zadayut prava pol'zovatelej na dostup k fajlu. |to chislo zadaet bitovuyu shkalu iz 9i bit, sootvetstvuyushchih stroke bity: 876 543 210 rwx rwx rwx r - mozhno chitat' fajl w - mozhno zapisyvat' v fajl x - mozhno vypolnyat' programmu iz etogo fajla Pervaya gruppa - eta prava vladel'ca fajla, vtoraya - chlenov ego gruppy, tretyaya - vseh prochih. |ti kody dlya vladel'ca fajla imeyut eshche i mnemonicheskie imena (ispol'zuemye v vyzove stat): #include <sys/stat.h> /* Tam opredeleno: */ #define S_IREAD 0400 #define S_IWRITE 0200 #define S_IEXEC 0100 Podrobnosti - v rukovodstvah po sisteme UNIX. Otmetim v chastnosti, chto open() mozhet vernut' kod oshibki fd < 0 ne tol'ko v sluchae, kogda fajl ne sushchestvuet (errno==ENOENT), no i v sluchae, kogda vam ne razreshen sootvetstvuyushchij dostup k etomu fajlu (errno==EACCES; pro peremennuyu koda oshibki errno sm. v glave "Vzaimodejstvie s UNIX"). Vyzov creat - eto prosto raznovidnost' vyzova open v forme fd = open( imya_fajla, O_WRONLY|O_TRUNC|O_CREAT, kody_dostupa); O_TRUNC oznachaet, chto esli fajl uzhe sushchestvuet, to on dolzhen byt' opustoshen pri otkry- tii. Kody dostupa i vladelec ne izmenyayutsya. O_CREAT oznachaet, chto fajl dolzhen byt' sozdan, esli ego ne bylo (bez etogo flaga fajl ne sozdastsya, a open vernet fd < 0). |tot flag trebuet zadaniya tret'ego argumenta kody_dostupa|-. Esli fajl uzhe sushchestvuet - etot flag ne imeet nikakogo effekta, no zato vstupaet v dejstvie O_TRUNC. Sushchestvuet takzhe flag O_EXCL kotoryj mozhet ispol'zovat'sya sovmestno s O_CREAT. On delaet sleduyushchee: esli fajl uzhe sushchestvuet, open vernet kod oshibki (errno==EEXIST). Esli fajl ne ____________________ |- Zametim, chto na samom dele kody dostupa u novogo fajla budut ravny di_mode = (kody_dostupa & ~u_cmask) | IFREG; (dlya kataloga vmesto IFREG budet IFDIR), gde maska u_cmask zadaetsya sistemnym vyzovom umask(u_cmask); (vyzov vydaet prezhnee znachenie maski) i v dal'nejshem nasleduetsya vsemi potomkami dan- nogo processa (ona hranitsya v u-area processa). |ta maska pozvolyaet zapretit' dostup k opredelennym operaciyam dlya vseh sozdavaemyh nami fajlov, nesmotrya na yavno zadannye kody dostupa, naprimer umask(0077); /* ???------ */ delaet znachashchimi tol'ko pervye 3 bita kodov dostupa (dlya vladel'ca fajla). Ostal'nye bity budut ravny nulyu. Vse eto otnositsya i k sozdaniyu katalogov vyzovom mkdir. A. Bogatyrev, 1992-95 - 140 - Si v UNIX sushchestvoval - srabatyvaet O_CREAT i fajl sozdaetsya. |to pozvolyaet predohranit' uzhe sushchestvuyushchie fajly ot unichtozheniya. Fajl udalyaetsya pri pomoshchi int unlink(char imya_fajla[]); U kazhdoj programmy po umolchaniyu otkryty tri pervyh deskriptora, obychno svyazannye 0 - s klaviaturoj (dlya chteniya) 1 - s displeem (vydacha rezul'tatov) 2 - s displeem (vydacha soobshchenij ob oshibkah) Esli pri vyzove close(fd) deskriptor fd ne sootvetstvuet otkrytomu fajlu (ne byl otk- ryt) - nichego ne proishodit. CHasto ispol'zuetsya takaya metafora: esli predstavlyat' sebe fajly kak knizhki (tol'ko chtenie) i bloknoty (chtenie i zapis'), stoyashchie na polke, to otkrytie fajla - eto vybor bloknota po zaglaviyu na ego oblozhke i otkrytie oblozhki (na pervoj stra- nice). Teper' mozhno chitat' zapisi, dopisyvat', vycherkivat' i pravit' zapisi v sere- dine, listat' knizhku! Stranicy mozhno sopostavit' blokam fajla (sm. nizhe), a "polku" s knizhkami - katalogu. 4.2. Napishite programmu, kotoraya kopiruet soderzhimoe odnogo fajla v drugoj (novyj) fajl. Pri etom ispol'zujte sistemnye vyzovy chteniya i zapisi read i write. |ti sis- vyzovy peresylayut massivy bajt iz pamyati v fajl i naoborot. No lyubuyu peremennuyu mozhno rassmatrivat' kak massiv bajt, esli zabyt' o strukture dannyh v peremennoj! CHitajte i zapisyvajte fajly bol'shimi kuskami, kratnymi 512 bajtam. |to umen'shit chislo obrashchenij k disku. Shema: char buffer[512]; int n; int fd_inp, fd_outp; ... while((n = read (fd_inp, buffer, sizeof buffer)) > 0) write(fd_outp, buffer, n); Privedem neskol'ko primerov ispol'zovaniya write: char c = 'a'; int i = 13, j = 15; char s[20] = "foobar"; char p[] = "FOOBAR"; struct { int x, y; } a = { 666, 999 }; /* sozdaem fajl s dostupom rw-r--r-- */ int fd = creat("aFile", 0644); write(fd, &c, 1); write(fd, &i, sizeof i); write(fd, &j, sizeof(int)); write(fd, s, strlen(s)); write(fd, &a, sizeof a); write(fd, p, sizeof(p) - 1); close(fd); Obratite vnimanie na takie momenty: - Pri ispol'zovanii write() i read() nado peredavat' ADRES dannogo, kotoroe my hotim zapisat' v fajl (mesta, kuda my hotim prochitat' dannye iz fajla). - Operacii read i write vozvrashchayut chislo dejstvitel'no prochitannyh/zapisannyh bajt (pri zapisi ono mozhet byt' men'she ukazannogo nami, esli na diske ne hvataet mesta; pri chtenii - esli ot pozicii chteniya do konca fajla soderzhitsya men'she informacii, chem my zatrebovali). - Operacii read/write prodvigayut ukazatel' chteniya/zapisi RWptr += prochitannoe_ili_zapisannoe_chislo_bajt; Pri otkrytii fajla ukazatel' stoit na nachale fajla: RWptr=0. Pri zapisi fajl A. Bogatyrev, 1992-95 - 141 - Si v UNIX esli nado avtomaticheski uvelichivaet svoj razmer. Pri chtenii - esli my dostignem konca fajla, to read budet vozvrashchat' "prochitano 0 bajt" (t.e. pri chtenii ukaza- tel' chteniya ne mozhet stat' bol'she razmera fajla). - Argument skol'koBajt imeet tip unsigned, a ne prosto int: int n = read (int fd, char *adres, unsigned skol'koBajt); int n = write(int fd, char *adres, unsigned skol'koBajt); Privedem uproshchennye shemy logiki etih sisvyzovov, kogda oni rabotayut s obychnym disko- vym fajlom (v UNIX ustrojstva tozhe vyglyadyat dlya programm kak fajly, no inogda s oso- bymi svojstvami): 4.2.1. m = write(fd, addr, n); esli( FAJL[fd] ne otkryt na zapis') to vernut' (-1); esli(n == 0) to vernut' 0; esli( FAJL[fd] otkryt na zapis' s flagom O_APPEND ) to RWptr = dlina_fajla; /* t.e. vstat' na konec fajla */ esli( RWptr > dlina_fajla ) to zapolnit' nulyami bajty fajla v intervale FAJL[fd][ dlina_fajla..RWptr-1 ] = '\0'; skopirovat' bajty iz pamyati processa v fajl FAJL[fd][ RWptr..RWptr+n-1 ] = addr[ 0..n-1 ]; otvodya na diske novye bloki, esli nado RWptr += n; esli( RWptr > dlina_fajla ) to dlina_fajla = RWptr; vernut' n; 4.2.2. m = read(fd, addr, n); esli( FAJL[fd] ne otkryt na chtenie) to vernut' (-1); esli( RWptr >= dlina_fajla ) to vernut' 0; m = MIN( n, dlina_fajla - RWptr ); skopirovat' bajty iz fajla v pamyat' processa addr[ 0..m-1 ] = FAJL[fd][ RWptr..RWptr+m-1 ]; RWptr += m; vernut' m; 4.3. Najdite oshibki v fragmente programmy: #define STDOUT 1 /* deskriptor standartnogo vyvoda */ int i; static char s[20] = "hi\n"; char c = '\n'; struct a{ int x,y; char ss[5]; } po; scanf( "%d%d%d%s%s", i, po.x, po.y, s, po.ss); write( STDOUT, s, strlen(s)); write( STDOUT, c, 1 ); /* zapisat' 1 bajt */ Otvet: v funkcii scanf pered argumentom i dolzhna stoyat' operaciya "adres", to est' &i. Analogichno pro &po.x i &po.y. Zametim, chto s - eto massiv, t.e. s i tak est' adres, poetomu pered s operaciya & ne nuzhna; analogichno pro po.ss - zdes' & ne trebuetsya. V sistemnom vyzove write vtoroj argument dolzhen byt' adresom dannogo, kotoroe my hotim zapisat' v fajl. Poetomu my dolzhny byli napisat' &c (vo vtorom vyzove write). Oshibka v scanf - ukazanie znacheniya peremennoj vmesto ee adresa - yavlyaetsya dovol'no rasprostranennoj i ne mozhet byt' obnaruzhena kompilyatorom (dazhe pri ispol'zo- vanii prototipa funkcii scanf(char *fmt, ...), tak kak scanf - funkciya s peremennym A. Bogatyrev, 1992-95 - 142 - Si v UNIX chislom argumentov zaranee ne opredelennyh tipov). Prihoditsya polagat'sya isklyuchitel'no na sobstvennuyu vnimatel'nost'! 4.4. Kak po deskriptoru fajla uznat', otkryt on na chtenie, zapis', chtenie i zapis' odnovremenno? Vot dva varianta resheniya: #include <fcntl.h> #include <stdio.h> #include <sys/param.h> /* tam opredeleno NOFILE */ #include <errno.h> char *typeOfOpen(fd){ int flags; if((flags=fcntl (fd, F_GETFL, NULL)) < 0 ) return NULL; /* fd veroyatno ne otkryt */ flags &= O_RDONLY | O_WRONLY | O_RDWR; switch(flags){ case O_RDONLY: return "r"; case O_WRONLY: return "w"; case O_RDWR: return "r+w"; default: return NULL; } } char *type2OfOpen(fd){ extern errno; /* sm. glavu "sistemnye vyzovy" */ int r=1, w=1; errno = 0; read(fd, NULL, 0); if( errno == EBADF ) r = 0; errno = 0; write(fd, NULL, 0); if( errno == EBADF ) w = 0; return (w && r) ? "r+w" : w ? "w" : r ? "r" : "closed"; } main(){ int i; char *s, *p; for(i=0; i < NOFILE; i++ ){ s = typeOfOpen(i); p = type2OfOpen(i); printf("%d:%s %s\n", i, s? s: "closed", p); } } Konstanta NOFILE oznachaet maksimal'noe chislo odnovremenno otkrytyh fajlov dlya odnogo processa (eto razmer tablicy otkrytyh processom fajlov, tablicy deskriptorov). Izu- chite opisanie sistemnogo vyzova fcntl (file control). 4.5. Napishite funkciyu rename() dlya pereimenovaniya fajla. Ukazanie: ispol'zujte sis- temnye vyzovy link() i unlink(). Otvet: A. Bogatyrev, 1992-95 - 143 - Si v UNIX rename( from, to ) char *from, /* staroe imya */ *to; /* novoe imya */ { unlink( to ); /* udalit' fajl to */ if( link( from, to ) < 0 ) /* svyazat' */ return (-1); unlink( from ); /* steret' staroe imya */ return 0; /* OK */ } Vyzov link(sushchestvuyushchee_imya, novoe_imya); sozdaet fajlu al'ternativnoe imya - v UNIX fajl mozhet imet' neskol'ko imen: tak kazhdyj katalog imeet kakoe-to imya v roditel'skom kataloge, a takzhe imya "." v sebe samom. Katalog zhe, soderzhashchij podkatalogi, imeet nekotoroe imya v svoem roditel'skom kata- loge, imya "." v sebe samom, i po odnomu imeni ".." v kazhdom iz svoih podkatalogov. |tot vyzov budet neudachen, esli fajl novoe_imya uzhe sushchestvuet; a takzhe esli my popytaemsya sozdat' al'ternativnoe imya v drugoj fajlovoj sisteme. Vyzov unlink(imya_fajla) udalyaet imya fajla. Esli fajl bol'she ne imeet imen - on unichtozhaetsya. Zdes' est' odna tonkost': rassmotrim fragment int fd; close(creat("/tmp/xyz", 0644)); /*Sozdat' pustoj fajl*/ fd = open("/tmp/xyz", O_RDWR); unlink("/tmp/xyz"); ... close(fd); Pervyj operator sozdaet pustoj fajl. Zatem my otkryvaem fajl i unichtozhaem ego edinstvennoe imya. No poskol'ku est' programma, otkryvshaya etot fajl, on ne udalyaetsya nemedlenno! Programma dalee rabotaet s bezymyannym fajlom pri pomoshchi deskriptora fd. Kak tol'ko fajl zakryvaetsya - on budet unichtozhen sistemoj (kak ne imeyushchij imen). Takoj tryuk ispol'zuetsya dlya sozdaniya vremennyh rabochih fajlov. Fajl mozhno udalit' iz kataloga tol'ko v tom sluchae, esli dannyj katalog imeet dlya vas kod dostupa "zapis'". Kody dostupa samogo fajla pri udalenii ne igrayut roli. V sovremennyh versiyah UNIX est' sistemnyj vyzov rename, kotoryj delaet to zhe samoe, chto i napisannaya nami odnoimennaya funkciya. 4.6. Sushchestvovanie al'ternativnyh imen u fajla pozvolyaet nam reshit' nekotorye prob- lemy, kotorye mogut vozniknut' pri ispol'zovanii chuzhoj programmy, ot kotoroj net ishodnogo teksta (kotoruyu nel'zya popravit'). Pust' programma vydaet nekotoruyu infor- maciyu v fajl zz.out (i eto imya zhestko zafiksirovano v nej, i ne zadaetsya cherez argu- menty programmy): /* |ta programma kompiliruetsya v a.out */ main(){ int fd = creat("zz.out", 0644); write(fd, "It's me\n", 8); } My zhe hotim poluchit' vyvod na terminal, a ne v fajl. Ochevidno, my dolzhny sdelat' fajl zz.out sinonimom ustrojstva /dev/tty (sm. konec etoj glavy). |to mozhno sdelat' koman- doj ln: $ rm zz.out ; ln /dev/tty zz.out $ a.out $ rm zz.out ili programmno: A. Bogatyrev, 1992-95 - 144 - Si v UNIX /* |ta programma kompiliruetsya v start */ /* i vyzyvaetsya vmesto a.out */ #include <stdio.h> main(){ unlink("zz.out"); link("/dev/tty", "zz.out"); if( !fork()){ execl("a.out", NULL); } else wait(NULL); unlink("zz.out"); } (pro fork, exec, wait smotri v glave pro UNIX). Eshche odin primer: programma a.out zhelaet zapustit' programmu /usr/bin/vi (smotri pro funkciyu system() snosku cherez neskol'ko stranic): main(){ ... system("/usr/bin/vi xx.c"); ... } Na vashej zhe mashine redaktor vi pomeshchen v /usr/local/bin/vi. Togda vy prosto sozdaete al'ternativnoe imya etomu redaktoru: $ ln /usr/local/bin/vi /usr/bin/vi Pomnite, chto al'ternativnoe imya fajlu mozhno sozdat' lish' v toj zhe fajlovoj sisteme, gde soderzhitsya ishodnoe imya. V semejstve BSD |- eto ogranichenie mozhno obojti, sozdav "simvol'nuyu ssylku" vyzovom symlink(link_to_filename,link_file_name_to_be_created); Simvol'naya ssylka - eto fajl, soderzhashchij imya drugogo fajla (ili kataloga). Sistema ne proizvodit avtomaticheskij podschet chisla takih ssylok, poetomu vozmozhny "visyachie" ssylki - ukazyvayushchie na uzhe udalennyj fajl. Prochest' soderzhimoe fajla-ssylki mozhno sistemnym vyzovom char linkbuf[ MAXPATHLEN + 1]; /* kuda pomestit' otvet */ int len = readlink(pathname, linkbuf, sizeof linkbuf); linkbuf[len] = '\0'; Sistemnyj vyzov stat avtomaticheski razymenovyvaet simvol'nye ssylki i vydaet informa- ciyu pro ukazuemyj fajl. Sistemnyj vyzov lstat (analog stat za isklyucheniem nazvaniya) vydaet informaciyu pro samu ssylku (tip fajla S_IFLNK). Kody dostupa k ssylke ne imeyut nikakogo znacheniya dlya sistemy, sushchestvenny tol'ko kody dostupa samogo ukazue- mogo fajla. Eshche raz: simvol'nye ssylki udobny dlya ukazaniya fajlov i katalogov na drugom diske. Pust' u vas ne pomeshchaetsya na disk katalog /opt/wawa. Vy mozhete razmestit' katalog wawa na diske USR: /usr/wawa. Posle chego sozdat' simvol'nuyu ssylku iz /opt: ln -s /usr/wawa /opt/wawa chtoby programmy videli etot katalog pod ego prezhnim imenem /opt/wawa. Eshche raz: hard link - to, chto sozdaetsya sistemnym vyzovom link, imeet tot zhe I-node (indeksnyj 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 ); 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 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 funkcii, pozvolyayushchej raspechatat' vse polya srazu (odnako takaya funkciya mozhet byt' napisana vami dlya konkretnogo tipa struk- tur). Takzhe ne sushchestvuet formata dlya scanf(), kotoryj vvodil by strukturu celikom. Vvodit' mozhno tol'ko po chastyam - kazhdoe pole otdel'no. 5.3. Napishite programmu, kotoraya po nomeru mesyaca vozvrashchaet obshchee chislo dnej goda vplot' do etogo mesyaca. 5.4. Peredelajte predydushchuyu programmu takim obrazom, chtoby ona po napisannomu buk- vami nazvaniyu mesyaca vozvrashchala obshchee chislo dnej goda vplot' do etogo mesyaca. V prog- ramme ispol'zujte funkciyu strcmp(). 5.5. Peredelajte predydushchuyu programmu takim obrazom, chtoby ona zaprashivala u pol'zo- vatelya den', mesyac, god i vydavala obshchee kolichestvo dnej v godu vplot' do dannogo dnya. Mesyac mozhet oboznachat'sya nomerom, nazvaniem mesyaca ili ego abbreviaturoj. 5.6. Sostav'te strukturu dlya uchetnoj kartoteki sluzhashchego, kotoraya soderzhala by sle- duyushchie svedeniya: familiyu, imya, otchestvo; god rozhdeniya; domashnij adres; mesto raboty, dolzhnost'; zarplatu; datu postupleniya na rabotu. 5.7. CHto pechataet programma? struct man { char name[20]; int salary; } workers[] = { { "Ivanov", 200 }, { "Petrov", 180 }, { "Sidorov", 150 } }, *wptr, chief = { "nachal'nik", 550 }; main(){ struct man *ptr, *cptr, save; ptr = wptr = workers + 1; cptr = &chief; save = workers[2]; workers[2] = *wptr; *wptr = save; wptr++; ptr--; ptr->salary = save.salary; printf( "%c %s %s %s %s\n%d %d %d %d\n%d %d %c\n", *workers[1].name, workers[2].name, cptr->name, ptr[1].name, save.name, wptr->salary, chief.salary, (*ptr).salary, workers->salary, wptr - ptr, wptr - workers, *ptr->name ); } Otvet: S Petrov nachal'nik Sidorov Sidorov 180 550 150 150 2 2 I 5.8. Razberite sleduyushchij primer: #include <stdio.h> struct man{ A. Bogatyrev, 1992-95 - 177 - Si v UNIX char *name, town[4]; int salary; int addr[2]; } men[] = { { "Vasya", "Msc", 100, { 12, 7 } }, { "Grisha", "Len", 120, { 6, 51 } }, { "Petya", "Rig", 140, { 23, 84 } }, { NULL, "" , -1, { -1, -1 } } }; main(){ struct man *ptr, **ptrptr; int i; ptrptr = &ptr; *ptrptr = &men[1]; /* men+1 */ printf( "%s %d %s %d %c\n", ptr->name, ptr->salary, ptr->town, ptr->addr[1], ptr[1].town[2] ); (*ptrptr)++; /* kopiruem *ptr v men[0] */ men[0].name = ptr->name; /* (char *) #1 */ strcpy( men[0].town, ptr->town ); /* char [] #2 */ men[0].salary = ptr->salary; /* int #3 */ for( i=0; i < 2; i++ ) men[0].addr[i] = ptr->addr[i]; /* massiv #4 */ /* raspechatyvaem massiv struktur */ for(ptr=men; ptr->name; ptr++ ) printf( "%s %s %d\n", ptr->name, ptr->town, ptr->addr[0]); } Obratite vnimanie na takie momenty: 1) Kak proizvoditsya rabota s ukazatelem na ukazatel' (ptrptr). 2) Pri kopirovanii struktur otdel'nymi polyami, polya skalyarnyh tipov (int, char, long, ..., ukazateli) kopiruyutsya operaciej prisvaivaniya (sm. stroki s pometkami #1 i #3). Polya vektornyh tipov (massivy) kopiruyutsya pri pomoshchi cikla, poele- mentno peresylayushchego massiv (stroka #4). Stroki (massivy bukv) peresylayutsya standartnoj funkciej strcpy (stroka #2). Vse eto otnositsya ne tol'ko k polyam struktur, no i k peremennym takih tipov. Struktury mozhno takzhe kopirovat' ne po polyam, a celikom: men[0]= *ptr; 3) Zapis' argumentov funkcii printf() lesenkoj pozvolyaet luchshe videt', kakomu for- matu sootvetstvuet kazhdyj argument. 4) Pri raspechatke massiva struktur my pechataem ne opredelennoe ih kolichestvo (rav- noe razmeru massiva), a pol'zuemsya ukazatelem NULL v pole name poslednej struk- tury kak priznakom konca massiva. 5) V pole town my hranim stroki iz 3h bukv, odnako vydelyaem dlya hraneniya massiv iz 4h bajt. |to neobhodimo potomu, chto stroka "Msc" sostoit ne iz 3h, a iz 4h baj- tov: 'M','s','c','\0'. Pri rabote so strukturami i ukazatelyami bol'shuyu pomoshch' mogut okazat' risunki. Vot kak (naprimer) mozhno narisovat' dannye iz etogo primera (massiv men izobrazhen ne ves'): A. Bogatyrev, 1992-95 - 178 - Si v UNIX --ptr-- --ptrptr-- ptr | * |<------|---* | ---|--- ---------- | / =========men[0]== / men:|name | *---|-----> "Vasya" | |---------------| | |town |M|s|c|\0| | |---------------| | |salary| 100 | | |---------------| | |addr | 12 | 7 | \ ----------------- \ =========men[1]== \-->|name | *---|-----> "Grisha" ............ 5.9. Sostav'te programmu "spravochnik po tablice Mendeleeva", kotoraya po nazvaniyu himicheskogo elementa vydavala by ego harakteristiki. Tablicu inicializirujte massivom struktur. 5.10. Pri zapisi dannyh v fajl (da i voobshche) ispol'zujte struktury vmesto massivov, esli elementy massiva imeyut raznoe smyslovoe naznachenie. Ne vosprinimajte strukturu prosto kak sredstvo ob®edineniya dannyh raznyh tipov, ona mozhet byt' i sredstvom ob®e- dineniya dannyh odnogo tipa, esli eto dobavlyaet osmyslennosti nashej programme. CHem ploh fragment? int data[2]; data[0] = my_key; data[1] = my_value; write(fd, (char *) data, 2 * sizeof(int)); Vo-pervyh, togda uzh luchshe ukazat' razmer vsego massiva srazu (hotya by na tot sluchaj, esli my izmenim ego razmer na 3 i zabudem popravit' mnozhitel' s 2 na 3). write(fd, (char *) data, sizeof data); Kstati, pochemu my pishem data, a ne &data? (otvet: potomu chto imya massiva i est' ego adres). Vo-vtoryh, elementy massiva imeyut raznyj smysl, tak ne ispol'zovat' li tut strukturu? struct _data { int key; int value; } data; data.key = my_key; data.value = my_value; write(fd, &data, sizeof data); 5.11. CHto napechataet sleduyushchaya programma? Narisujte raspolozhenie ukazatelej po okon- chanii dannoj programmy. #include <stdio.h> struct lnk{ char c; A. Bogatyrev, 1992-95 - 179 - Si v UNIX struct lnk *prev, *next; } chain[20], *head = chain; add(c) char c; { head->c = c; head->next = head+1; head->next->prev = head; head++; } main(){ char *s = "012345"; while( *s ) add( *s++ ); head->c = '-'; head->next = (struct lnk *)NULL; chain->prev = chain->next; while( head->prev ){ putchar( head->prev->c ); head = head->prev; if( head->next ) head->next->prev = head->next->next; } } 5.12. Napishite programmu, sostavlyashchuyu dvunapravlennyj spisok bukv, vvodimyh s klavi- atury. Konec vvoda - bukva '\n'. Posle tret'ej bukvy vstav'te bukvu '+'. Udalite pyatuyu bukvu. Raspechatajte spisok v obratnom poryadke. Oformite operacii vstavki/udaleniya kak funkcii. |lement spiska dolzhen imet' vid: struct elem{ char letter; /* bukva */ char *word; /* slovo */ struct elem *prev; /* ssylka nazad */ struct elem *next; /* ssylka vpered */ }; struct elem *head, /* pervyj element spiska */ *tail, /* poslednij element */ *ptr, /* rabochaya peremennaya */ *prev; /* predydushchij element pri prosmotre */ int c, cmp; ... while((c = getchar()) != '\n' ) Insert(c, tail); for(ptr=head; ptr != NULL; ptr=ptr->next) printf("bukva %c\n", ptr->letter); Pamyat' luchshe otvodit' ne iz massiva, a funkciej calloc(), kotoraya analogichna funkcii malloc(), no dopolnitel'no raspisyvaet vydelennuyu pamyat' bajtom '\0' (0, NULL). Vot funkcii vstavki i udaleniya: extern char *calloc(); /* sozdat' novoe zveno spiska dlya bukvy c */ struct elem *NewElem(c) char c; { struct elem *p = (struct elem *) calloc(1, sizeof(struct elem)); /* calloc avtomaticheski obnulyaet vse polya, * v tom chisle prev i next */ p->letter = c; return p; } A. Bogatyrev, 1992-95 - 180 - Si v UNIX /* vstavka posle ptr (obychno - posle tail) */ Insert(c, ptr) char c; struct elem *ptr; { struct elem *newelem = NewElem(c), *right; if(head == NULL){ /* spisok byl pust */ head=tail=newelem; return; } right = ptr->next; ptr->next = newelem; newelem->prev = ptr; newelem->next = right; if( right ) right->prev = newelem; else tail = newelem; } /* udalit' ptr iz spiska */ Delete( ptr ) struct elem *ptr; { struct elem *left=ptr->prev, *right=ptr->next; if( right ) right->prev = left; if( left ) left->next = right; if( tail == ptr ) tail = left; if( head == ptr ) head = right; free((char *) ptr); } Napishite analogichnuyu programmu dlya spiska slov. struct elem *NewElem(char *s) { struct elem *p = (struct elem *) calloc(1, sizeof(struct elem)); p->word = strdup(s); return p; } void DeleteElem(struct elem *ptr){ free(ptr->word); free(ptr); } Uslozhnenie: vstavlyajte slova v spisok v alfavitnom poryadke. Ispol'zujte dlya etogo funkciyu strcmp(), prosmatrivajte spisok tak: struct elem *newelem; if (head == NULL){ /* spisok pust */ head = tail = NewElem(novoe_slovo); return; } /* poisk mesta v spiske */ for(cmp= -1, ptr=head, prev=NULL; ptr; prev=ptr, ptr=ptr->next ) if((cmp = strcmp(novoe_slovo, ptr->word)) <= 0 ) break; Esli cikl okonchilsya s cmp==0, to takoe slovo uzhe est' v spiske. Esli cmp < 0, to takogo slova ne bylo i ptr ukazyvaet element, pered kotorym nado vstavit' slovo novoe_slovo, a prev - posle kotorogo (prev==NULL oznachaet, chto nado vstavit' v nachalo spiska); t.e. slovo vstavlyaetsya mezhdu prev i ptr. Esli cmp > 0, to slovo nado doba- vit' v konec spiska (pri etom ptr==NULL). head ==> "a" ==> "b" ==> "d" ==> NULL | | prev "c" ptr A. Bogatyrev, 1992-95 - 181 - Si v UNIX if(cmp == 0) return; /* slovo uzhe est' */ newelem = NewElem( novoe_slovo ); if(prev == NULL){ /* v nachalo */ newelem->next = head; newelem->prev = NULL; head->prev = newelem; head = newelem; } else if(ptr == NULL){ /* v konec */ newelem->next = NULL; newelem->prev = tail; tail->next = newelem; tail = newelem; } else { /* mezhdu prev i ptr */ newelem->next = ptr; newelem->prev = prev; prev->next = newelem; ptr ->prev = newelem; } 5.13. Napishite funkcii dlya raboty s kompleksnymi chislami struct complex { double re, im; }; Naprimer, slozhenie vyglyadit tak: struct complex add( c1, c2 ) struct complex c1, c2; { struct complex sum; sum.re = c1.re + c2.re; sum.im = c1.im + c2.im; return sum; } struct complex a = { 12.0, 14.0 }, b = { 13.0, 2.0 }; main(){ struct complex c; c = add( a, b ); printf( "(%g,%g)\n", c.re, c.im ); } 5.14. Massivy v Si nel'zya prisvaivat' celikom, zato struktury - mozhno. Inogda ispol'zuyut takoj tryuk: strukturu iz edinstvennogo polya-massiva typedef struct { int ai[5]; } intarray5; intarray5 a, b = { 1, 2, 3, 4, 5 }; i teper' zakonno a = b; Zato dostup k yachejkam massiva vyglyadit teper' menee izyashchno: A. Bogatyrev, 1992-95 - 182 - Si v UNIX a.ai[2] = 14; for(i=0; i < 5; i++) printf( "%d\n", a.ai[i] ); Takzhe nevozmozhno peredat' kopiyu massiva v kachestve fakticheskogo parametra funkcii. Dazhe esli my napishem: typedef int ARR16[16]; ARR16 d; void f(ARR16 a){ printf( "%d %d\n", a[3], a[15]); a[3] = 2345; } void main(void){ d[3] = 9; d[15] = 98; f(d); printf("Now it is %d\n", d[3]); } to poslednij printf napechataet "Now it is 2345", poskol'ku v f peredaetsya adres mas- siva, no ne ego kopiya; poetomu operator a[3]=2345 izmenyaet ishodnyj massiv. Obojti eto mozhno, ispol'zovav tot zhe tryuk, poskol'ku pri peredache struktury v kachestve para- metra peredaetsya uzhe ne ee adres, a kopiya vsej struktury (kak eto i prinyato v Si vo vseh sluchayah, krome massivov). 5.15. Naposledok upomyanem pro bitovye polya - elementy struktury, zanimayushchie tol'ko chast' mashinnogo slova - tol'ko neskol'ko bitov v nem. Razmer polya v bitah zadaetsya konstrukciej :chislo_bitov. Bitovye polya ispol'zuyutsya dlya bolee kompaktnogo hraneniya informacii v strukturah (dlya ekonomii mesta). struct XYZ { /* bitovye polya dolzhny byt' unsigned */ unsigned x:2; /* 0 .. 2**2 - 1 */ unsigned y:5; /* 0 .. 2**5 - 1 */ unsigned z:1; /* YES=1 NO=0 */ } xyz; main(){ printf("%u\n", sizeof(xyz)); /* == sizeof(int) */ xyz.z = 1; xyz.y = 21; xyz.x = 3; printf("%u %u %u\n", xyz.x, ++xyz.y, xyz.z); /* Znachenie bitovogo polya beretsya po modulyu * maksimal'no dopustimogo chisla 2**chislo_bitov - 1 */ xyz.y = 32 /* maksimum */ + 7; xyz.x = 16+2; xyz.z = 11; printf("%u %u %u\n", xyz.x, xyz.y, xyz.z); /* 2 7 1 */ } Pole shiriny 1 chasto ispol'zuetsya v kachestve bitovogo flaga: vmesto #define FLAG1 01 #define FLAG2 02 #define FLAG3 04 int x; /* slovo dlya neskol'kih flagov */ x |= FLAG1; x &= ~FLAG2; if(x & FLAG3) ...; ispol'zuetsya struct flags { unsigned flag1:1, flag2:1, flag3:1; } x; x.flag1 = 1; x.flag2 = 0; if( x.flag3 ) ...; A. Bogatyrev, 1992-95 - 183 - Si v UNIX Sleduet odnako uchest', chto mashinnyj kod dlya raboty s bitovymi polyami bolee slozhen i zanimaet bol'she komand (t.e. medlennee i dlinnee). K bitovym polyam nel'zya primenit' operaciyu vzyatiya adresa "&", u nih net adresov i smeshchenij! 5.16. Primer na ispol'zovanie struktur s polem peremennogo razmera. CHast' peremen- noj dliny mozhet byt' lish' odna i obyazana byt' poslednim polem struktury. Vnimanie: eto programmistskij tryuk, ispol'zovat' ostorozhno! #include <stdio.h> #define SZ 5 extern char *malloc(); #define VARTYPE char struct obj { struct header { /* postoyannaya chast' */ int cls; int size; /* razmer peremennoj chasti */ } hdr; VARTYPE body [1]; /* chast' peremennogo razmera: v opisanii rovno ODIN element massiva */ } *items [SZ]; /* ukazateli na struktury */ #define OFFSET(field, ptr) ((char *) &ptr->field - (char *)ptr) int body_offset; /* sozdanie novoj struktury */ struct obj *newObj( int cl, char *s ) { char *ptr; struct obj *op; int n = strlen(s); /* dlina peremennoj chasti (shtuk VARTYPE) */ int newsize = sizeof(struct header) + n * sizeof(VARTYPE); printf("[n=%d newsize=%d]\n", n, newsize); /* newsize = (sizeof(struct obj) - sizeof(op->body)) + n * sizeof(op->body); Pri ispol'zovanii etogo razmera ne uchityvaetsya, chto struct(obj) vyrovnena na granicu sizeof(int). No v chastnosti sleduet uchityvat' i to, na granicu chego vyrovneno nachalo polya op->body. To est' samym pravil'nym budet newsize = body_offset + n * sizeof(op->body); */ /* otvesti massiv bajt bez vnutrennej struktury */ ptr = (char *) malloc(newsize); /* nalozhit' poverh nego strukturu */ op = (struct obj *) ptr; op->hdr.cls = cl; op->hdr.size = n; strncpy(op->body, s, n); return op; } A. Bogatyrev, 1992-95 - 184 - Si v UNIX void printobj( struct obj *p ) { register i; printf( "OBJECT(cls=%d,size=%d)\n", p->hdr.cls, p->hdr.size); for(i=0; i < p->hdr.size; i++ ) putchar( p->body[i] ); putchar( '\n' ); } char *strs[] = { "a tree", "a maple", "an oak", "the birch", "the fir" }; int main(int ac, char *av[]){ int i; printf("sizeof(struct header)=%d sizeof(struct obj)=%d\n", sizeof(struct header), sizeof(struct obj)); { struct obj *sample; printf("offset(cls)=%d\n", OFFSET(hdr.cls, sample)); printf("offset(size)=%d\n", OFFSET(hdr.size, sample)); printf("offset(body)=%d\n", body_offset = OFFSET(body, sample)); } for( i=0; i < SZ; i++ ) items[i] = newObj( i, strs[i] ); for( i=0; i < SZ; i++ ){ printobj( items[i] ); free( items[i] ); items[i] = NULL; } return 0; } 5.17. Napishite programmu, realizuyushchuyu spisok so "stareniem". |lement spiska, k kotoromu obrashchalis' poslednim, nahoditsya v golove spiska. Samyj staryj element vytesnyaetsya k hvostu spiska i v konechnom schete iz spiska udalyaetsya. Takoj algoritm ispol'zuet yadro UNIX dlya keshirovaniya blokov fajla v operativnoj pamyati: bloki, k kotorym chasto byvayut obrashcheniya osedayut v pamyati (a ne na diske). /* Spisok strok, uporyadochennyh po vremeni ih dobavleniya v spisok, * t.e. samaya "svezhaya" stroka - v nachale, samaya "drevnyaya" - v konce. * Stroki pri postuplenii mogut i povtoryat'sya! Po podobnomu principu * mozhno organizovat' buferizaciyu blokov pri obmene s diskom. */ #include <stdio.h> extern char *malloc(), *gets(); #define MAX 3 /* maksimal'naya dlina spiska */ int nelems = 0; /* tekushchaya dlina spiska */ struct elem { /* STRUKTURA |LEMENTA SPISKA */ char *key; /* Dlya blokov - eto celoe - nomer bloka */ struct elem *next; /* sleduyushchij element spiska */ /* ... i mozhet chto-to eshche ... */ } *head; /* golova spiska */ void printList(), addList(char *), forget(); A. Bogatyrev, 1992-95 - 185 - Si v UNIX void main(){ /* Vvedite a b c d b a c */ char buf[128]; while(gets(buf)) addList(buf), printList(); } /* Raspechatka spiska */ void printList(){ register struct elem *ptr; printf( "V spiske %d elementov\n", nelems ); for(ptr = head; ptr != NULL; ptr = ptr->next ) printf( "\t\"%s\"\n", ptr->key ); } /* Dobavlenie v nachalo spiska */ void addList(char *s) { register struct elem *p, *new; /* Analiz - net li uzhe v spiske */ for(p = head; p != NULL; p = p->next ) if( !strcmp(s, p->key)){ /* Est'. Perenesti v nachalo spiska */ if( head == p ) return; /* Uzhe v nachale */ /* Udalyaem iz serediny spiska */ new = p; /* Udalyaemyj element */ for(p = head; p->next != new; p = p->next ); /* p ukazyvaet na predshestvennika new */ p->next = new->next; goto Insert; } /* Net v spiske */ if( nelems >= MAX ) forget(); /* Zabyt' starejshij */ if((new = (struct elem *) malloc(sizeof(struct elem)))==NULL) goto bad; if((new->key = malloc(strlen(s) + 1)) == NULL) goto bad; strcpy(new->key, s); nelems++; Insert: new->next = head; head = new; return; bad: printf( "Net pamyati\n" ); exit(13); } /* Zabyt' hvost spiska */ void forget(){ struct elem *prev = head, *tail; if( head == NULL ) return; /* Spisok pust */ /* Edinstvennyj element ? */ if((tail = head->next) == NULL){ tail=head; head=NULL; goto Del; } for( ; tail->next != NULL; prev = tail, tail = tail->next ); prev->next = NULL; Del: free(tail->key); free(tail); nelems--; } A. Bogatyrev, 1992-95 - 186 - Si v UNIX V etoj glave rech' pojdet o processah. Skompilirovannaya programma hranitsya na diske kak obychnyj netekstovyj fajl. Kogda ona budet zagruzhena v pamyat' komp'yutera i nachnet vypolnyat'sya - ona stanet processom. UNIX - mnogozadachnaya sistema (mul'tiprogrammnaya). |to oznachaet, chto odnovre- menno mozhet byt' zapushcheno mnogo processov. Processor vypolnyaet ih v rezhime razdeleniya vremeni - vydelyaya po ocheredi kvant vremeni odnomu processu, zatem drugomu, tret'emu... V rezul'tate sozdaetsya vpechatlenie parallel'nogo vypolneniya vseh proces- sov (na mnogoprocessornyh mashinah parallel'nost' istinnaya). Processam, ozhidayushchim nekotorogo sobytiya, vremya processora ne vydelyaetsya. Bolee togo, "spyashchij" process mozhet byt' vremenno otkachan (t.e. skopirovan iz pamyati mashiny) na disk, chtoby osvobo- dit' pamyat' dlya drugih processov. Kogda "spyashchij" process dozhdetsya sobytiya, on budet "razbuzhen" sistemoj, pereveden v rang "gotovyh k vypolneniyu" i, esli byl otkachan - budet vozvrashchen s diska v pamyat' (no, mozhet byt', na drugoe mesto v pamyati!). |ta procedura nosit nazvanie "svopping" (swapping). Mozhno zapustit' neskol'ko processov, vypolnyayushchih programmu iz odnogo i togo zhe fajla; pri etom vse oni budut (esli tol'ko special'no ne bylo predusmotreno inache) nezavisimymi drug ot druga. Tak, u kazhdogo pol'zovatelya, rabotayushchego v sisteme, ime- etsya svoj sobstvennyj process-interpretator komand (svoya kopiya), vypolnyayushchij prog- rammu iz fajla /bin/csh (ili /bin/sh). Process predstavlyaet soboj izolirovannyj "mir", obshchayushchijsya s drugimi "mirami" vo Vselennoj pri pomoshchi: a) Argumentov funkcii main: void main(int argc, char *argv[], char *envp[]); Esli my naberem komandu $ a.out a1 a2 a3 to funkciya main programmy iz fajla a.out vyzovetsya s argc = 4 /* kolichestvo argumentov */ argv[0] = "a.out" argv[1] = "a1" argv[2] = "a2" argv[3] = "a3" argv[4] = NULL Po soglasheniyu argv[0] soderzhit imya vypolnyaemogo fajla iz kotorogo zagruzhena eta programma|-. b) Tak nazyvaemogo "okruzheniya" (ili "sredy") char *envp[], produblirovannogo takzhe v predopredelennoj peremennoj extern char **environ; Okruzhenie sostoit iz strok vida "IMYAPEREMENNOJ=znachenie" Massiv etih strok zavershaetsya NULL (kak i argv). Dlya polucheniya znacheniya pere- mennoj s imenem IMYA sushchestvuet standartnaya funkciya char *getenv( char *IMYA ); Ona vydaet libo znachenie, libo NULL esli peremennoj s takim imenem net. c) Otkrytyh fajlov. Po umolchaniyu (neyavno) vsegda otkryty 3 kanala: VVOD V Y V O D FILE * stdin stdout stderr sootvetstvuet fd 0 1 2 svyazan s klaviaturoj displeem ____________________ |- Imenno eto imya pokazyvaet komanda ps -ef #include <stdio.h> main(ac, av) char **av; { execl("/bin/sleep", "Take it easy", "1000", NULL); } A. Bogatyrev, 1992-95 - 187 - Si v UNIX |ti kanaly dostayutsya processu "v nasledstvo" ot zapuskayushchego processa i svyazany s displeem i klaviaturoj, esli tol'ko ne byli perenapravleny. Krome togo, prog- ramma mozhet sama yavno otkryvat' fajly (pri pomoshchi open, creat, pipe, fopen). Vsego programma mozhet odnovremenno otkryt' do 20 fajlov (schitaya standartnye kanaly), a v nekotoryh sistemah i bol'she (naprimer, 64). V MS DOS est' eshche 2 predopredelennyh kanala vyvoda: stdaux - v posledovatel'nyj kommunikacionnyj port, stdprn - na printer. d) Process imeet unikal'nyj nomer, kotoryj on mozhet uznat' vyzovom int pid = getpid(); a takzhe uznat' nomer "roditelya" vyzovom int ppid = getppid(); Processy mogut po etomu nomeru posylat' drug drugu signaly: kill(pid /* komu */, sig /* nomer signala */); i reagirovat' na nih signal (sig /*po signalu*/, f /*vyzyvat' f(sig)*/); e) Sushchestvuyut i drugie sredstva kommunikacii processov: semafory, soobshcheniya, obshchaya pamyat', setevye kommunikacii. f) Sushchestvuyut nekotorye drugie parametry (kontekst) processa: naprimer, ego tekushchij katalog, kotoryj dostaetsya v nasledstvo ot processa-"roditelya", i mozhet byt' zatem izmenen sistemnym vyzovom chdir(char *imya_novogo_kataloga); U kazhdogo processa est' svoj sobstvennyj tekushchij rabochij katalog (v otlichie ot MS DOS, gde tekushchij katalog odinakov dlya vseh zadach). K "prochim" harakteristi- kam otnesem takzhe: upravlyayushchij terminal; gruppu processov (pgrp); identifikator (nomer) vladel'ca processa (uid), identifikator gruppy vladel'ca (gid), reakcii i maski, zadannye na razlichnye signaly; i.t.p. g) Izdaniya drugih zaprosov (sistemnyh vyzovov) k operacionnoj sisteme ("bogu") dlya vypolneniya razlichnyh "vneshnih" operacij. h) Vse ostal'nye dejstviya proishodyat vnutri processa i nikak ne vliyayut na drugie processy i ustrojstva ("miry"). V chastnosti, odin process NIKAK ne mozhet polu- chit' dostup k pamyati drugogo processa, esli tot ne pozvolil emu eto yavno (meha- nizm shared memory); adresnye prostranstva processov nezavisimy i izolirovany (ravno i prostranstvo yadra izolirovano ot pamyati processov). Operacionnaya sistema vystupaet v kachestve kommunikacionnoj sredy, svyazyvayushchej "miry"-processy, "miry"-vneshnie ustrojstva (vklyuchaya terminal pol'zovatelya); a takzhe v kachestve rasporyaditelya resursov "Vselennoj", v chastnosti - vremeni (po ocheredi vyde- lyaemogo aktivnym processam) i prostranstva (v pamyati komp'yutera i na diskah). My uzhe neodnokratno upominali "sistemnye vyzovy". CHto zhe eto takoe? S tochki zreniya Si-programmista - eto obychnye funkcii. V nih peredayut argumenty, oni vozvra- shchayut znacheniya. Vneshne oni nichem ne otlichayutsya ot napisannyh nami ili bibliotechnyh funkcij i vyzyvayutsya iz programm odinakovym s nimi sposobom. S tochki zhe zreniya realizacii - est' glubokoe razlichie. Telo funkcii-sisvyzova raspolozheno ne v nashej programme, a v rezidentnoj (t.e. postoyanno nahodyashchejsya v pamyati komp'yutera) upravlyayushchej programme, nazyvaemoj yadrom operacionnoj sistemy|-. ____________________ |- Sobstvenno, operacionnaya sistema harakterizuetsya naborom predostavlyaemyh eyu sis- temnyh vyzovov, poskol'ku vse koncepcii, zalozhennye v sisteme, dostupny nam tol'ko cherez nih. Esli my imeem dve realizacii sistemy s raznym vnutrennim ustrojstvom yader, no predostavlyayushchie odinakovyj interfejs sistemnyh vyzovov (ih nabor, smysl i povedenie), to eto vse-taki odna i ta zhe sistema! YAdra mogut ne prosto otlichat'sya, no i byt' postroennymi na sovershenno razlichnyh principah: tak obstoit delo s UNIX-ami na odnoprocessornyh i mnogoprocessornyh mashinah. No dlya nas yadro - eto "chernyj yashchik", polnost'yu opredelyaemyj ego povedeniem, t.e. svoim interfejsom s programmami, no ne vnutrennim ustrojstvom. Vtorym parametrom, harakterizuyushchim OS, yavlyayutsya for- maty dannyh, ispol'zuemye sistemoj: formaty dannyh dlya sisvyzovov i format informacii v razlichnyh fajlah, v tom chisle format oformleniya vypolnyaemyh fajlov (format dannyh v fizicheskoj pamyati mashiny v etot spisok ne vhodit - on zavisim ot realizacii i ot pro- cessora). Kak pravilo, programma pishetsya tak, chtoby ispol'zovat' soglasheniya, prinya- tye v dannoj sisteme, dlya chego ona prosto vklyuchaet ryad standartnyh include-fajlov s opisaniem etih formatov. Imena etih fajlov takzhe mozhno otnesti k interfejsu sistemy. A. Bogatyrev, 1992-95 - 188 - Si v UNIX Sam termin "sistemnyj vyzov" kak raz oznachaet "vyzov sistemy dlya vypolneniya dejst- viya", t.e. vyzov funkcii v yadre sistemy. YAdro rabotaet v privelegirovannom rezhime, v kotorom imeet dostup k nekotorym sistemnym tablicam|=, registram i portam vneshnih ustrojstv i dispetchera pamyati, k kotorym obychnym programmam dostup apparatno zapreshchen (v otlichie ot MS DOS, gde vse tablicy yadra dostupny pol'zovatel'skim programmam, chto sozdaet razdol'e dlya virusov). Sistemnyj vyzov proishodit v 2 etapa: snachala v pol'- zovatel'skoj programme vyzyvaetsya bibliotechnaya funkciya-"koreshok", telo kotoroj napi- sano na assemblere i soderzhit komandu generacii programmnogo preryvaniya. |to - glav- noe otlichie ot normal'nyh Si-funkcij - vyzov po preryvaniyu. Vtorym etapom yavlyaetsya reakciya yadra na preryvanie: 1. perehod v privelegirovannyj rezhim; 2. razbiratel'stvo, KTO obratilsya k yadru, i podklyuchenie u-area etogo processa k adresnomu prostranstvu yadra (context switching); 3. izvlechenie argumentov iz pamyati zaprosivshego processa; 4. vyyasnenie, CHTO zhe hotyat ot yadra (odin iz argumentov, nevidimyj nam - eto nomer sistemnogo vyzova); 5. proverka korrektnosti ostal'nyh argumentov; 6. proverka prav processa na dopustimost' vypolneniya takogo zaprosa; 7. vyzov tela trebuemogo sistemnogo vyzova - eto obychnaya Si-funkciya v yadre; 8. vozvrat otveta v pamyat' processa; 9. vyklyuchenie privelegirovannogo rezhima; 10. vozvrat iz preryvaniya. Vo vremya sistemnogo vyzova (shag 7) process mozhet "zasnut'", dozhidayas' nekotorogo sobytiya (naprimer, nazhatiya knopki na klaviature). V eto vremya yadro peredast upravle- nie drugomu processu. Kogda nash process budet "razbuzhen" (sobytie proizoshlo) - on prodolzhit vypolnenie shagov sistemnogo vyzova. Bol'shinstvo sistemnyh vyzovov vozvrashchayut v programmu v kachestve svoego znacheniya priznak uspeha: 0 - vse sdelano, (-1) - sisvyzov zavershilsya neudachej; libo nekotoroe soderzhatel'noe znachenie pri uspehe (vrode deskriptora fajla v open(), i (-1) pri neu- dache. V sluchae neudachnogo zaversheniya v predopredelennuyu peremennuyu errno zanositsya nomer oshibki, opisyvayushchij prichinu neudachi (kody oshibok predopredeleny, opisany v include-fajle <errno.h> i imeyut vid Echtoto). Zametim, chto pri UDACHE eta peremennaya prosto ne izmenyaetsya i mozhet soderzhat' lyuboj musor, poetomu proveryat' ee imeet smysl lish' v sluchae, esli oshibka dejstvitel'no proizoshla: #include <errno.h> /* kody oshibok */ extern int errno; extern char *sys_errlist[]; int value; if((value = sys_call(...)) < 0 ){ printf("Error:%s(%d)\n", sys_errlist[errno], errno ); exit(errno); /* prinuditel'noe zavershenie programmy */ } ____________________ Povedenie vseh programm v sisteme vytekaet iz povedeniya sistemnyh vyzovov, koto- rymi oni pol'zuyutsya. Dazhe to, chto UNIX yavlyaetsya mnogozadachnoj sistemoj, neposredst- venno vytekaet iz nalichiya sistemnyh vyzovov fork, exec, wait i specifikacii ih funk- cionirovaniya! To zhe mozhno skazat' pro yazyk Si - mobil'nost' programmy zavisit v osnovnom ot nabora ispol'zuemyh v nej bibliotechnyh funkcij (i, v men'shej stepeni, ot dialekta sa- mogo yazyka, kotoryj dolzhen udovletvoryat' standartu na yazyk Si). Esli dve raznye sis- temy predostavlyayut vse eti funkcii (kotorye mogut byt' po-raznomu realizovany, no dolzhny delat' odno i to zhe), to programma budet kompilirovat'sya i rabotat' v oboih sistemah, bolee togo, rabotat' v nih odinakovo. |= Takim kak tablica processov, tablica otkrytyh fajlov (vseh vmeste i dlya kazhdogo processa), i.t.p. A. Bogatyrev, 1992-95 - 189 - Si v UNIX Predopredelennyj massiv sys_errlist, hranyashchijsya v standartnoj biblioteke, soderzhit stroki-rasshifrovku smysla oshibok (po-anglijski). Posmotrite opisanie funkcii per- ror(). 6.1.1. Ispol'zuya sistemnyj vyzov stat, napishite programmu, opredelyayushchuyu tip fajla: obychnyj fajl, katalog, ustrojstvo, FIFO-fajl. Otvet: #include <sys/types.h> #include <sys/stat.h> typeOf( name ) char *name; { int type; struct stat st; if( stat( name, &st ) < 0 ){ printf( "%s ne sushchestvuet\n", name ); return 0; } printf("Fajl imeet %d imen\n", st.st_nlink); switch(type = (st.st_mode & S_IFMT)){ case S_IFREG: printf( "Obychnyj fajl razmerom %ld bajt\n", st.st_size ); break; case S_IFDIR: printf( "Katalog\n" ); break; case S_IFCHR: /* bajtoorientirovannoe */ case S_IFBLK: /* blochnoorientirovannoe */ printf( "Ustrojstvo\n" ); break; case S_IFIFO: printf( "FIFO-fajl\n" ); break; default: printf( "Drugoj tip\n" ); break; } return type; } 6.1.2. Napishite programmu, pechatayushchuyu: svoi argumenty, peremennye okruzheniya, infor- maciyu o vseh otkrytyh eyu fajlah i ispol'zuemyh trubah. Dlya etoj celi ispol'zujte sistemnyj vyzov struct stat st; int used, fd; for(fd=0; fd < NOFILE; fd++ ){ used = fstat(fd, &st) < 0 ? 0 : 1; ... } Programma mozhet ispol'zovat' deskriptory fajlov s nomerami 0..NOFILE-1 (obychno 0..19). Esli fstat dlya kakogo-to fd vernul kod oshibki (<0), eto oznachaet, chto dannyj deskriptor ne svyazan s otkrytym fajlom (t.e. ne ispol'zuetsya). NOFILE opredeleno v include-fajle <sys/param.h>, soderzhashchem raznoobraznye parametry dannoj sistemy. 6.1.3. Napishite uproshchennyj analog komandy ls, raspechatyvayushchij soderzhimoe tekushchego kataloga (fajla s imenem ".") bez sortirovki imen po alfavitu. Predusmotrite chtenie kataloga, ch'e imya zadaetsya kak argument programmy. Imena "." i ".." ne vydavat'. Format kataloga opisan v header-fajle <sys/dir.h> i v "kanonicheskoj" versii vyg- lyadit tak: katalog - eto fajl, sostoyashchij iz struktur direct, kazhdaya opisyvaet odno imya fajla, vhodyashchego v katalog: A. Bogatyrev, 1992-95 - 190 - Si v UNIX struct direct { unsigned short d_ino; /* 2 bajta: nomer I-uzla */ char d_name[DIRSIZ]; /* imya fajla */ }; V semejstve BSD format kataloga neskol'ko inoj - tam zapisi imeyut raznuyu dlinu, zavi- syashchuyu ot dliny imeni fajla, kotoroe mozhet imet' dlinu ot 1 do 256 simvolov. Imya fajla mozhet sostoyat' iz lyubyh simvolov, krome '\0', sluzhashchego priznakom konca imeni i '/', sluzhashchego razdelitelem. V imeni dopustimy probely, upravlyayushchie simvoly (no ne rekomenduyutsya!), lyuboe chislo tochek (v otlichie ot MS DOS, gde dopustima edinstvennaya tochka, otdelyayushchaya sobstvenno imya ot suffiksa (rasshireniya)), razresheny dazhe nepechatnye (t.e. upravlyayushchie) simvoly! Esli imya fajla imeet dlinu 14 (DIRSIZ) simvolov, to ono ne okanchivaetsya bajtom '\0'. V etom sluchae dlya pechati imeni fajla vozmozhny tri podhoda: 1. Vyvodit' simvoly pri pomoshchi putchar()-a v cikle. Cikl preryvat' po indeksu rav- nomu DIRSIZ, libo po dostizheniyu bajta '\0'. 2. Skopirovat' pole d_name v drugoe mesto: char buf[ DIRSIZ + 1 ]; strncpy(buf, d.d_name, DIRSIZ); buf[ DIRSIZ ] = '\0'; |tot sposob luchshij, esli imya fajla nado ne prosto napechatat', no i zapomnit' na budushchee, chtoby ispol'zovat' v svoej programme. 3. Ispol'zovat' takuyu osobennost' funkcii printf(): #include <sys/types.h> #include <sys/dir.h> struct direct d; ... printf( "%*.*s\n", DIRSIZ, DIRSIZ, d.d_name ); Esli fajl byl stert, to v pole d_ino zapisi kataloga budet soderzhat'sya 0 (imenno poetomu I-uzly numeruyutsya nachinaya s 1, a ne s 0). Pri udalenii fajla soderzhimoe ego (bloki) unichtozhaetsya, I-uzel osvobozhdaetsya, no imya v kataloge ne zatiraetsya fizi- cheski, a prosto pomechaetsya kak stertoe: d_ino=0; Katalog pri etom nikak ne uplotnya- etsya i ne ukorachivaetsya! Poetomu imena s d_ino==0 vydavat' ne sleduet - eto imena uzhe unichtozhennyh fajlov. Pri sozdanii novogo imeni (creat, link, mknod) sistema prosmatrivaet katalog i pereispol'zuet pervyj ot nachala svobodnyj slot (yachejku kataloga) gde d_ino==0, zapi- syvaya novoe imya v nego (tol'ko v etot moment staroe imya-prizrak okonchatel'no ischeznet fizicheski). Esli pustyh mest net - katalog udlinyaetsya. Lyuboj katalog vsegda soderzhit dva standartnyh imeni: "." - ssylka na etot zhe katalog (na ego sobstvennyj I-node), ".." - na vyshelezhashchij katalog. U kornevogo kataloga "/" oba etih imeni ssylayutsya na nego zhe samogo (t.e. soderzhat d_ino==2). Imya kataloga ne soderzhitsya v nem samom. Ono soderzhitsya v "roditel'skom" kataloge ... Katalog v UNIX - eto obychnyj diskovyj fajl. Vy mozhete chitat' ego iz svoih prog- ramm. Odnako nikto (vklyuchaya superpol'zovatelya|=) ne mozhet zapisyvat' chto-libo v kata- log pri pomoshchi write. Izmeneniya soderzhimogo katalogov vypolnyaet tol'ko yadro, otvechaya na zaprosy v vide sistemnyh vyzovov creat, unlink, link, mkdir, rmdir, rename, mknod. Kody dostupa dlya kataloga interpretiruyutsya sleduyushchim obrazom: w zapis' S_IWRITE. Oznachaet pravo sozdavat' i unichtozhat' v kataloge imena fajlov pri ____________________ |= Superpol'zovatel' (superuser) imeet uid==0. |to "privelegirovannyj" pol'zova- tel', kotoryj imeet pravo delat' VSE. Emu dostupny lyubye sisvyzovy i fajly, nesmotrya na kody dostupa i.t.p. A. Bogatyrev, 1992-95 - 191 - Si v UNIX pomoshchi etih vyzovov. To est': pravo sozdavat', udalyat' i pereimenovyvat' fajly v kataloge. Otmetim, chto dlya pereimenovaniya ili udaleniya fajla vam ne trebuetsya imet' dostup po zapisi k samomu fajlu - dostatochno imet' dostup po zapisi k katalogu, soderzhashchemu ego imya! r chtenie S_IREAD. Pravo chitat' katalog kak obychnyj fajl (pravo vypolnyat' opendir, sm. nizhe): blagodarya etomu my mozhem poluchit' spisok imen fajlov, soderzhashchihsya v kataloge. Odnako, esli my ZARANEE znaem imena fajlov v kataloge, my MOZHEM rabo- tat' s nimi - esli imeem pravo dostupa "vypolnenie" dlya etogo kataloga! x vypolnenie S_IEXEC. Razreshaet poisk v kataloge. Dlya otkrytiya fajla, sozdaniya/udaleniya fajla, perehoda v drugoj katalog (chdir), sistema vypolnyaet sleduyushchie dejstviya (osushchestvlyaemye funkciej namei() v yadre): chtenie kataloga i poisk v nem ukazan- nogo imeni fajla ili kataloga; najdennomu imeni sootvetstvuet nomer I-uzla d_ino; po nomeru uzla sistema schityvaet s diska sam I-uzel nuzhnogo fajla i po nemu dobiraetsya do soderzhimogo fajla. Kod "vypolnenie" - eto kak raz razreshenie takogo prosmotra kataloga sistemoj. Esli katalog imeet dostup na chtenie - my mozhem poluchit' spisok fajlov (t.e. primenit' komandu ls); no esli on pri etom ne imeet koda dostupa "vypolnenie" - my ne smozhem poluchit' dostupa ni k odnomu iz fajlov kataloga (ni otkryt', ni udalit', ni sozdat', ni sdelat' stat, ni chdir). T.e. "chtenie" razreshaet primenenie vyzova read, a "vypolnenie" - funkcii yadra namei. Fakticheski "vypolnenie" oznachaet "dostup k fajlam v dannom kataloge"; eshche bolee tochno - k I-nodam fajlov etogo kataloga. t sticky bit S_ISVTX - dlya kataloga on oznachaet, chto udalit' ili pereimenovat' nekij fajl v dannom kataloge mogut tol'ko: vladelec kataloga, vladelec dannogo fajla, super- pol'zovatel'. I nikto drugoj. |to isklyuchaet udalenie fajlov chuzhimi. Sovet: dlya kataloga polezno imet' takie kody dostupa: chmod o-w,+t katalog V sistemah BSD ispol'zuetsya, kak uzhe bylo upomyanuto, format kataloga s peremennoj dlinoj zapisej. CHtoby imet' udobnyj dostup k imenam v kataloge, voznikli special'nye funkcii chteniya kataloga: opendir, closedir, readdir. Pokazhem, kak prostejshaya komanda ls realizuetsya cherez eti funkcii. A. Bogatyrev, 1992-95 - 192 - Si v UNIX #include <stdio.h> #include <sys/types.h> #include <dirent.h> int listdir(char *dirname){ register struct dirent *dirbuf; DIR *fddir; ino_t dot_ino = 0, dotdot_ino = 0; if((fddir = opendir (dirname)) == NULL){ fprintf(stderr, "Can't read %s\n", dirname); return 1; } /* Bez sortirovki po alfavitu */ while ((dirbuf = readdir (fddir)) != NULL ) { if (dirbuf->d_ino == 0) continue; if (strcmp (dirbuf->d_name, "." ) == 0){ dot_ino = dirbuf->d_ino; continue; } else if(strcmp (dirbuf->d_name, "..") == 0){ dotdot_ino = dirbuf->d_ino; continue; } else printf("%s\n", dirbuf->d_name); } closedir (fddir); if(dot_ino == 0) printf("Povrezhdennyj katalog: net imeni \".\"\n"); if(dotdot_ino == 0) printf("Povrezhdennyj katalog: net imeni \"..\"\n"); if(dot_ino && dot_ino == dotdot_ino) printf("|to kornevoj katalog diska\n"); return 0; } int main(int ac, char *av[]){ int i; if(ac > 1) for(i=1; i < ac; i++) listdir(av[i]); else listdir("."); return 0; } Obratite vnimanie, chto tut ne trebuetsya dobavlenie '\0' v konec polya d_name, pos- kol'ku ego predostavlyaet nam sama funkciya readdir(). 6.1.4. Napishite programmu udaleniya fajlov i katalogov, zadannyh v argv. Delajte stat, chtoby opredelit' tip fajla (fajl/katalog). Programma dolzhna otkazyvat'sya uda- lyat' fajly ustrojstv. Dlya udaleniya pustogo kataloga (ne soderzhashchego inyh imen, krome "." i "..") sle- duet ispol'zovat' sisvyzov rmdir(imya_kataloga); (esli katalog ne pust - errno poluchit znachenie EEXIST); a dlya udaleniya obychnyh fajlov (ne katalogov) unlink(imya_fajla); Programma dolzhna zaprashivat' podtverzhdenie na udalenie kazhdogo fajla, vydavaya ego imya, tip, razmer v kilobajtah i vopros "udalit' ?". 6.1.5. Napishite funkciyu rekursivnogo obhoda dereva podkatalogov i pechati imen vseh fajlov v nem. Klyuch U42 oznachaet fajlovuyu sistemu s dlinnymi imenami fajlov (BSD 4.2). A. Bogatyrev, 1992-95 - 193 - Si v UNIX /*#!/bin/cc -DFIND -DU42 -DMATCHONLY treemk.c match.c -o tree -lx * Obhod poddereva katalogov (po motivam Kernigan & Ritchi). * Klyuchi kompilyacii: * BSD-4.2 BSD-4.3 -DU42 * XENIX s kanonicheskoj fajl.sist. nichego * XENIX s bibliotekoj -lx -DU42 * programma poiska fajlov -DFIND * programma rekursivnogo udaleniya -DRM_REC * programma podscheta ispol'zuemogo mesta na diske BEZ_KLYUCHA */ #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/param.h> /* dlya MAXPATHLEN */ #if defined(M_XENIX) && defined(U42) # include <sys/ndir.h> /* XENIX + U42 emulyaciya */ #else # include <dirent.h> # define stat(f,s) lstat(f,s) /* ne prohodit' po simvol'nym ssylkam */ # define d_namlen d_reclen #endif /* proverka: katalog li eto */ #define isdir(st) ((st.st_mode & S_IFMT) == S_IFDIR) struct stat st; /* dlya sisvyzova stat() */ char buf[MAXPATHLEN+1]; /* bufer dlya imeni fajla */ #define FAILURE (-1) /* kod neudachi */ #define SUCCESS 1 /* kod uspeha */ #define WARNING 0 /* nefatal'naya oshibka */ /* Soobshcheniya ob oshibkah vo vremya obhoda dereva: */ #ifndef ERR_CANT_READ # define ERR_CANT_READ(name) \ fprintf( stderr, "\tNe mogu chitat' \"%s\"\n", name), WARNING # define ERR_NAME_TOO_LONG() \ fprintf( stderr, "\tSlishkom dlinnoe polnoe imya\n" ), WARNING #endif /* Prototipy dlya predvaritel'nogo ob®yavleniya funkcij. */ extern char *strrchr(char *, char); int directory (char *name, int level, int (*enter)(char *full, int level, struct stat *st), int (*leave)(char *full, int level), int (*touch)(char *full, int level, struct stat *st)); /* Funkcii-obrabotchiki enter, leave, touch dolzhny * vozvrashchat' (-1) dlya preryvaniya prosmotra dereva, * libo znachenie >= 0 dlya prodolzheniya. */ A. Bogatyrev, 1992-95 - 194 - Si v UNIX /* Obojti derevo s kornem v rootdir */ int walktree ( char *rootdir, /* koren' dereva */ int (*enter)(char *full, int level, struct stat *st), int (*leave)(char *full, int level), int (*touch)(char *full, int level, struct stat *st) ){ /* proverka korrektnosti kornya */ if( stat(rootdir, &st) < 0 || !isdir(st)){ fprintf( stderr, "\tPlohoj koren' dereva \"%s\"\n", rootdir ); return FAILURE; /* neudacha */ } strcpy (buf, rootdir); return act (buf, 0, enter, leave, touch); } /* Ocenka fajla s imenem name. */ int act (char *name, int level, int (*enter)(char *full, int level, struct stat *st), int (*leave)(char *full, int level), int (*touch)(char *full, int level, struct stat *st)) { if (stat (name, &st) < 0) return WARNING; /* oshibka, no ne fatal'naya */ if(isdir(st)){ /* pozvat' obrabotchik katalogov */ if(enter) if( enter(name, level, &st) == FAILURE ) return FAILURE; return directory (name, level+1, enter, leave, touch); } else { /* pozvat' obrabotchik fajlov */ if(touch) return touch (name, level, &st); else return SUCCESS; } } A. Bogatyrev, 1992-95 - 195 - Si v UNIX /* Obrabotat' katalog: prochitat' ego i najti podkatalogi */ int directory (char *name, int level, int (*enter)(char *full, int level, struct stat *st), int (*leave)(char *full, int level), int (*touch)(char *full, int level, struct stat *st)) { #ifndef U42 struct direct dirbuf; int fd; #else register struct dirent *dirbuf; DIR *fd; extern DIR *opendir(); #endif char *nbp, *tail, *nep; int i, retcode = SUCCESS; #ifndef U42 if ((fd = open (name, 0)) < 0) { #else if ((fd = opendir (name)) == NULL) { #endif return ERR_CANT_READ(name); } tail = nbp = name + strlen (name); /* ukazatel' na zakryvayushchij \0 */ if( strcmp( name, "/" )) /* esli ne "/" */ *nbp++ = '/'; *nbp = '\0'; #ifndef U42 if (nbp + DIRSIZ + 2 >= name + MAXPATHLEN) { *tail = '\0'; return ERR_NAME_TOO_LONG(); } #endif #ifndef U42 while (read(fd, (char *) &dirbuf, sizeof(dirbuf)) == sizeof(dirbuf)){ if (dirbuf.d_ino == 0) /* stertyj fajl */ continue; if (strcmp (dirbuf.d_name, "." ) == 0 || strcmp (dirbuf.d_name, "..") == 0) /* ne interesuyut */ continue; for (i = 0, nep = nbp; i < DIRSIZ; i++) *nep++ = dirbuf.d_name[i]; # else /*U42*/ while ((dirbuf = readdir (fd)) != NULL ) { if (dirbuf->d_ino == 0) continue; if (strcmp (dirbuf->d_name, "." ) == 0 || strcmp (dirbuf->d_name, "..") == 0) continue; for (i = 0, nep = nbp; i < dirbuf->d_namlen ; i++) *nep++ = dirbuf->d_name[i]; #endif /*U42*/ *nep = '\0'; if( act(name, level, enter, leave, touch) == FAILURE) { retcode = FAILURE; break; } } A. Bogatyrev, 1992-95 - 196 - Si v UNIX #ifndef U42 close (fd); #else closedir(fd); #endif *tail = '\0'; /* vosstanovit' staroe name */ if(retcode != FAILURE && leave) if( leave(name, level) == FAILURE) retcode = FAILURE; return retcode; } /* -------------------------------------------------------------- */ /* Disk Usage -- Ocenka mesta, zanimaemogo fajlami poddereva */ /* -------------------------------------------------------------- */ /* Pereschet bajtov v kilobajty */ #define KB(s) (((s)/1024L) + ((s)%1024L ? 1L:0L)) /* ili #define KB(s) (((s) + 1024L - 1) / 1024L) */ long size; /* obshchij razmer */ long nfiles; /* vsego fajlov */ long ndirs; /* iz nih katalogov */ #define WARNING_LIMIT 150L /* podozritel'no bol'shoj fajl */ static int du_touch (char *name, int level, struct stat *st){ long sz; size += (sz = KB(st->st_size)); /* razmer fajla v Kb. */ nfiles++; #ifndef TREEONLY if( sz >= WARNING_LIMIT ) fprintf(stderr,"\tVnimanie! \"%s\" ochen' bol'shoj: %ld Kb.\n", name, sz); #endif /*TREEONLY*/ return SUCCESS; } static int du_enter (char *name, int level, struct stat *st){ #ifndef TREEONLY fprintf( stderr, "Katalog \"%s\"\n", name ); #endif size += KB(st->st_size); /* razmer kataloga v Kb. */ nfiles++; ++ndirs; return SUCCESS; } long du (char *name){ size = nfiles = ndirs = 0L; walktree(name, du_enter, NULL, du_touch ); return size; } A. Bogatyrev, 1992-95 - 197 - Si v UNIX /* -------------------------------------------------------------- */ /* Rekursivnoe udalenie fajlov i katalogov */ /* -------------------------------------------------------------- */ int deleted; /* skol'ko fajlov i katalogov udaleno */ static int recrm_dir (char *name, int level){ if( rmdir(name) >= 0){ deleted++; return SUCCESS; } fprintf(stderr, "Ne mogu rmdir '%s'\n", name); return WARNING; } static int recrm_file(char *name, int level, struct stat *st){ if( unlink(name) >= 0){ deleted++; return SUCCESS; } fprintf(stderr, "Ne mogu rm '%s'\n", name); return WARNING; } int recrmdir(char *name){ int ok_code; deleted = 0; ok_code = walktree(name, NULL, recrm_dir, recrm_file); printf("Udaleno %d fajlov i katalogov v %s\n", deleted, name); return ok_code; } /* -------------------------------------------------------------- */ /* Poisk fajlov s podhodyashchim imenem (po shablonu imeni) */ /* -------------------------------------------------------------- */ char *find_PATTERN; static int find_check(char *fullname, int level, struct stat *st){ char *basename = strrchr(fullname, '/'); if(basename) basename++; else basename = fullname; if( match(basename, find_PATTERN)) printf("Level#%02d %s\n", level, fullname); if( !strcmp( basename, "core")){ printf("Najden damp %s, poisk prekrashchen.\n", fullname); return FAILURE; } return SUCCESS; } void find (char *root, char *pattern){ find_PATTERN = pattern; walktree(root, find_check, NULL, find_check); } A. Bogatyrev, 1992-95 - 198 - Si v UNIX /* -------------------------------------------------------------- */ #ifndef TREEONLY void main(int argc, char *argv[]){ #ifdef FIND if(argc != 3){ fprintf(stderr, "Arg count\n"); exit(1); } find(argv[1], argv[2]); #else # ifdef RM_REC for(argv++; *argv; argv++) recrmdir(*argv); # else du( argc == 1 ? "." : argv[1] ); printf( "%ld kilobajt v %ld fajlah.\n", size, nfiles ); printf( "%ld katalogov.\n", ndirs ); # endif #endif exit(0); } #endif /*TREEONLY*/ 6.1.6. Ispol'zuya predydushchij algoritm, napishite programmu rekursivnogo kopirovaniya poddereva katalogov v drugoe mesto. Dlya sozdaniya novyh katalogov ispol'zujte sistem- nyj vyzov mkdir(imya_kataloga, kody_dostupa); 6.1.7. Ispol'zuya tot zhe algoritm, napishite programmu udaleniya kataloga, kotoraya uda- lyaet vse fajly v nem i, rekursivno, vse ego podkatalogi. Takim obrazom, udalyaetsya derevo katalogov. V UNIX podobnuyu operaciyu vypolnyaet komanda rm -r imya_kataloga_kornya_dereva 6.1.8. Ispol'zuya vse tot zhe algoritm obhoda, napishite analog komandy find, kotoryj budet pozvolyat': - nahodit' vse fajly, ch'i imena udovletvoryayut zadannomu shablonu (ispol'zujte funk- ciyu match() iz glavy "Tekstovaya obrabotka"); - nahodit' vse vypolnyaemye fajly: obychnye fajly S_IFREG, u kotoryh (st.st_mode & 0111) != 0 Kak uzhe yasno, sleduet pol'zovat'sya vyzovom stat dlya proverki kazhdogo fajla. 6.2.1. Napishite funkciyu, perevodyashchuyu god, mesyac, den', chasy, minuty i sekundy v chislo sekund, proshedshee do ukazannogo momenta s 00 chasov 00 minut 00 sekund 1 YAnvarya 1970 goda. Vnimanie: rezul'tat dolzhen imet' tip long (tochnee time_t). |ta funkciya oblegchit vam sravnenie dvuh momentov vremeni, zadannyh v obshcheprinya- tom "chelovecheskom" formate, poskol'ku sravnit' dva long chisla gorazdo proshche, chem sravnivat' po ocheredi gody, zatem, esli oni ravny - mesyacy, esli mesyacy ravny - daty, i.t.d.; a takzhe oblegchit izmerenie intervala mezhdu dvumya sobytiyami - on vychislyaetsya prosto kak raznost' dvuh chisel. V sisteme UNIX vremya obrabatyvaetsya i hranitsya imenno v vide chisla sekund; v chastnosti tekushchee astronomicheskoe vremya mozhno uznat' sistemnym vyzovom #include <sys/types.h> #include <time.h> time_t t = time(NULL); /* time(&t); */ Funkciya struct tm *tm = localtime( &t ); A. Bogatyrev, 1992-95 - 199 - Si v UNIX razlagaet chislo sekund na otdel'nye sostavlyayushchie, soderzhashchiesya v int-polyah struktury: tm_year god (nado pribavlyat' 1900) tm_yday den' v godu 0..365 tm_mon nomer mesyaca 0..11 (0 - YAnvar') tm_mday data mesyaca 1..31 tm_wday den' nedeli 0..6 (0 - Voskresen'e) tm_hour chasy 0..23 tm_min minuty 0..59 tm_sec sekundy 0..59 Nomera mesyaca i dnya nedeli nachinayutsya s nulya, chtoby vy mogli ispol'zovat' ih v kachestve indeksov: char *months[] = { "YAnvar'", "Fevral'", ..., "Dekabr'" }; printf( "%s\n", months[ tm->tm_mon ] ); Primer ispol'zovaniya etih funkcij est' v prilozhenii. Ustanovit' vremya v sisteme mozhet superpol'zovatel' vyzovom stime(&t); 6.2.2. Napishite funkciyu pechati tekushchego vremeni v formate CHCH:MM:SS DD-MES-GG. Ispol'zujte sistemnyj vyzov time() i funkciyu localtime(). Sushchestvuet standartnaya funkciya ctime(), kotoraya pechataet vremya v formate: /* Mon Mar 25 18:56:36 1991 */ #include <stdio.h> #include <time.h> main(){ /* komanda date */ time_t t = time(NULL); char *s = ctime(&t); printf("%s", s); } Obratite vnimanie, chto stroka s uzhe soderzhit na konce simvol '\n'. 6.2.3. Struktura stat, zapolnyaemaya sistemnym vyzovom stat(), krome prochih polej soderzhit polya tipa time_t st_ctime, st_mtime i st_atime - vremya poslednego izmeneniya soderzhimogo I-uzla fajla, vremya poslednego izmeneniya fajla i vremya poslednego dostupa k fajlu. - Pole st_ctime izmenyaetsya (ustanavlivaetsya ravnym tekushchemu astronomicheskomu vre- meni) pri primenenii k fajlu vyzovov creat, chmod, chown, link, unlink, mknod, utime|-, write (t.k. izmenyaetsya dlina fajla); |to pole sleduet rassmatrivat' kak vremya modifikacii prav dostupa k fajlu; - st_mtime - write, creat, mknod, utime; |to pole sleduet rassmatrivat' kak vremya modifikacii soderzhimogo fajla (dannyh); - st_atime - read, creat, mknod, utime; |to pole sleduet rassmatrivat' kak vremya chteniya soderzhimogo fajla (dannyh). Modificirujte funkciyu typeOf(), chtoby ona pechatala eshche i eti daty. ____________________ |- Vremya modifikacii fajla mozhno izmenit' na tekushchee astronomicheskoe vremya i ne proizvodya zapisi v fajl. Dlya etogo ispol'zuetsya vyzov utime(imyaFajla, NULL); On ispol'zuetsya dlya vzaimodejstviya s programmoj make - v komande touch. Izmenit' vremya mozhno tol'ko svoemu fajlu. A. Bogatyrev, 1992-95 - 200 - Si v UNIX 6.2.4. Napishite analog komandy ls -tm, vydayushchej spisok imen fajlov tekushchego kata- loga, otsortirovannyj po ubyvaniyu polya st_mtime, to est' nedavno modificirovannye fajly vydayutsya pervymi. Dlya kazhdogo prochitannogo iz kataloga imeni nado sdelat' stat; imena fajlov i vremena sleduet sohranit' v massive struktur, a zatem otsortiro- vat' ego. 6.2.5. Napishite analogichnuyu programmu, sortiruyushchuyu fajly v poryadke vozrastaniya ih razmera (st_size). 6.2.6. Napishite analog komandy ls -l, vydayushchij imena fajlov kataloga i ih kody dos- tupa v formate rwxrw-r--. Dlya polucheniya kodov dostupa ispol'zujte vyzov stat stat( imyaFajla, &st); kodyDostupa = st.st_mode & 0777; Dlya izmeneniya kodov dostupa ispol'zuetsya vyzov chmod(imya_fajla, novye_kody); Mozhno izmenyat' kody dostupa, sootvetstvuyushchie bitovoj maske 0777 | S_ISUID | S_ISGID | S_ISVTX (smotri <sys/stat.h>). Tip fajla (sm. funkciyu typeOf) ne mozhet byt' izmenen. Izme- nit' kody dostupa k fajlu mozhet tol'ko ego vladelec. Pechatajte eshche nomer I-uzla fajla: pole d_ino kataloga libo pole st_ino struktury stat. 6.2.7. Vot programma, kotoraya kazhdye 2 sekundy proveryaet - ne izmenilos' li soderzhi- moe tekushchego kataloga: #include <sys/types.h> #include <sys/stat.h> extern char *ctime(); main(){ time_t last; struct stat st; for( stat(".", &st), last=st.st_mtime; ; sleep(2)){ stat(".", &st); if(last != st.st_mtime){ last = st.st_mtime; printf("Byl sozdan ili udalen kakoj-to fajl: %s", ctime(&last)); } } } Modificirujte ee, chtoby ona soobshchala kakoe imya (imena) bylo udaleno ili sozdano (dlya etogo nado pri zapuske programmy prochitat' i zapomnit' soderzhimoe kataloga, a pri obnaruzhenii modifikacii - perechitat' katalog i sravnit' ego s prezhnim soderzhimym). 6.2.8. Napishite po analogii programmu, kotoraya vydaet soobshchenie, esli ukazannyj vami fajl byl kem-to prochitan, zapisan ili udalen. Vam sleduet otslezhivat' izmenenie polej st_atime, st_mtime i znachenie stat() < 0 sootvetstvenno. Esli fajl udalen - programma zavershaetsya. 6.2.9. Sovremennye UNIX-mashiny imeyut vstroennye tajmery (kak pravilo neskol'ko) s dovol'no vysokim razresheniem. Nekotorye iz nih mogut ispol'zovat'sya kak "budil'niki" s obratnym otschetom vremeni: v tajmer zagruzhaetsya nekotoroe znachenie; tajmer vedet obratnyj otschet, umen'shaya zagruzhennyj schetchik; kak tol'ko eto vremya istekaet - posy- laetsya signal processu, zagruzivshemu tajmer. A. Bogatyrev, 1992-95 - 201 - Si v UNIX Vot kak, k primeru, vyglyadit funkciya zaderzhki v mikrosekundah (millionnyh dolyah sekundy). Primechanie: etu funkciyu ne sleduet ispol'zovat' vperemezhku s funkciyami sleep i alarm (smotri stat'yu pro nih nizhe, v glave pro signaly). #include <sys/types.h> #include <signal.h> #include <sys/time.h> void do_nothing() {} /* Zaderzhka na usec millionnyh dolej sekundy (mikrosekund) */ void usleep(unsigned int usec) { struct itimerval new, old; /* struct itimerval soderzhit polya: struct timeval it_interval; struct timeval it_value; Gde struct timeval soderzhit polya: long tv_sec; -- chislo celyh sekund long tv_usec; -- chislo mikrosekund */ struct sigaction new_vec, old_vec; if (usec == 0) return; /* Pole tv_sec soderzhit chislo celyh sekund. Pole tv_usec soderzhit chislo mikrosekund. it_value - eto vremya, cherez kotoroe V PERVYJ raz tajmer "prozvonit", to est' poshlet nashemu processu signal SIGALRM. Vremya, ravnoe nulyu, nemedlenno ostanovit tajmer. it_interval - eto interval vremeni, kotoryj budet zagruzhat'sya v tajmer posle kazhdogo "zvonka" (no ne v pervyj raz). Vremya, ravnoe nulyu, ostanovit tajmer posle ego pervogo "zvonka". */ new.it_interval.tv_sec = 0; new.it_interval.tv_usec = 0; new.it_value.tv_sec = usec / 1000000; new.it_value.tv_usec = usec % 1000000; A. Bogatyrev, 1992-95 - 202 - Si v UNIX /* Sohranyaem prezhnyuyu reakciyu na signal SIGALRM v old_vec, zanosim v kachestve novoj reakcii do_nothing() */ new_vec.sa_handler = do_nothing; sigemptyset(&new_vec.sa_mask); new_vec.sa_flags = 0; sighold(SIGALRM); sigaction(SIGALRM, &new_vec, &old_vec); /* Zagruzka interval'nogo tajmera znacheniem new, nachalo otscheta. * Prezhnee znachenie spasti v old. * Vmesto &old mozhno takzhe NULL - ne spasat'. */ setitimer(ITIMER_REAL, &new, &old); /* ZHdat' prihoda signala SIGALRM */ sigpause(SIGALRM); /* Vosstanovit' reakciyu na SIGALRM */ sigaction(SIGALRM, &old_vec, (struct sigaction *) 0); sigrelse(SIGALRM); /* Vosstanovit' prezhnie parametry tajmera */ setitimer(ITIMER_REAL, &old, (struct itimerval *) 0); } 6.2.10. Vtoroj primer ispol'zovaniya tajmera - eto tajmer, otschityvayushchij tekushchee vremya sutok (a takzhe datu). CHtoby poluchit' znachenie etogo tajmera ispol'zuetsya vyzov funkcii gettimeofday #include <time.h> void main(){ struct timeval timenow; gettimeofday(&timenow, NULL); printf("%u sec, %u msec\n", timenow.tv_sec, timenow.tv_usec ); printf("%s", ctime(&timenow.tv_sec)); exit(0); } Pole tv_sec soderzhit chislo sekund, proshedshee s polunochi 1 yanvarya 1970 goda do dannogo momenta; v chem polnost'yu sootvetstvuet sistemnomu vyzovu time. Odnako plyus k tomu pole tv_usec soderzhit chislo millionnyh dolej tekushchej sekundy (znachenie etogo polya vsegda men'she 1000000). 6.2.11. K dannomu paragrafu vernites', izuchiv razdel pro fork() i exit(). Kazhdyj process mozhet prebyvat' v dvuh fazah: sistemnoj (vnutri tela sistemnogo vyzova - ego vypolnyaet dlya nas yadro operacionnoj sistemy) i pol'zovatel'skoj (vnutri koda samoj programmy). Vremya, zatrachennoe processom v kazhdoj faze, mozhet byt' izmeryano sistemnym vyzovom times(). Krome togo, etot vyzov pozvolyaet uznat' summarnoe vremya, zatrachennoe porozhdennymi processami (porozhdennymi pri pomoshchi fork). Sistemnyj vyzov zapolnyaet strukturu A. Bogatyrev, 1992-95 - 203 - Si v UNIX struct tms { clock_t tms_utime; clock_t tms_stime; clock_t tms_cutime; clock_t tms_cstime; }; i vozvrashchaet znachenie #include <sys/times.h> struct tms time_buf; clock_t real_time = times(&time_buf); Vse vremena izmeryayutsya v "tikah" - nekotoryh dolyah sekundy. CHislo tikov v sekunde mozhno uznat' takim sistemnym vyzovom (v sisteme Solaris): #include <unistd.h> clock_t HZ = sysconf(_SC_CLK_TCK); V staryh sistemah, gde tajmer rabotal ot seti peremennogo toka, eto chislo poluchalos' ravnym 60 (60 Gerc - chastota seti peremennogo toka). V sovremennyh sistemah eto 100. Polya struktury soderzhat: tms_utime vremya, zatrachennoe vyzyvayushchim processom v pol'zovatel'skoj faze. tms_stime vremya, zatrachennoe vyzyvayushchim processom v sistemnoj faze. tms_cutime vremya, zatrachennoe porozhdennymi processami v pol'zovatel'skoj faze: ono ravno summe vseh tms_utime i tms_cutime porozhdennyh processov (rekursivnoe summirova- nie). tms_cstime vremya, zatrachennoe porozhdennymi processami v sistemnoj faze: ono ravno summe vseh tms_stime i tms_cstime porozhdennyh processov (rekursivnoe summirovanie). real_time vremya, sootvetstvuyushchee astronomicheskomu vremeni sistemy. Imeet smysl meryat' tol'ko ih raznost'. Vot primer programmy: #include <stdio.h> #include <unistd.h> /* _SC_CLK_TCK */ #include <signal.h> /* SIGALRM */ #include <sys/time.h> /* ne ispol'zuetsya */ #include <sys/times.h> /* struct tms */ struct tms tms_stop, tms_start; clock_t real_stop, real_start; clock_t HZ; /* chislo ticks v sekunde */ A. Bogatyrev, 1992-95 - 204 - Si v UNIX /* Zasech' vremya momenta starta processa */ void hello(void){ real_start = times(&tms_start); } /* Zasech' vremya okonchaniya processa */ void bye(int n){ real_stop = times(&tms_stop); #ifdef CRONO /* Raznost' vremen */ tms_stop.tms_utime -= tms_start.tms_utime; tms_stop.tms_stime -= tms_start.tms_stime; #endif /* Raspechatat' vremena */ printf("User time = %g seconds [%lu ticks]\n", tms_stop.tms_utime / (double)HZ, tms_stop.tms_utime); printf("System time = %g seconds [%lu ticks]\n", tms_stop.tms_stime / (double)HZ, tms_stop.tms_stime); printf("Children user time = %g seconds [%lu ticks]\n", tms_stop.tms_cutime / (double)HZ, tms_stop.tms_cutime); printf("Children system time = %g seconds [%lu ticks]\n", tms_stop.tms_cstime / (double)HZ, tms_stop.tms_cstime); printf("Real time = %g seconds [%lu ticks]\n", (real_stop - real_start) / (double)HZ, real_stop - real_start); exit(n); } /* Po signalu SIGALRM - zavershit' process */ void onalarm(int nsig){ printf("Vyhod #%d ================\n", getpid()); bye(0); } /* Porozhdennyj process */ void dochild(int n){ hello(); printf("Start #%d ================\n", getpid()); signal(SIGALRM, onalarm); /* Zakazat' signal SIGALRM cherez 1 + n*3 sekund */ alarm(1 + n*3); for(;;){} /* zaciklit'sya v user mode */ } A. Bogatyrev, 1992-95 - 205 - Si v UNIX #define NCHLD 4 int main(int ac, char *av[]){ int i; /* Uznat' chislo tikov v sekunde */ HZ = sysconf(_SC_CLK_TCK); setbuf(stdout, NULL); hello(); for(i=0; i < NCHLD; i++) if(fork() == 0) dochild(i); while(wait(NULL) > 0); printf("Vyhod MAIN =================\n"); bye(0); return 0; } i ee vydacha: Start #3883 ================ Start #3884 ================ Start #3885 ================ Start #3886 ================ Vyhod #3883 ================ User time = 0.72 seconds [72 ticks] System time = 0.01 seconds [1 ticks] Children user time = 0 seconds [0 ticks] Children system time = 0 seconds [0 ticks] Real time = 1.01 seconds [101 ticks] Vyhod #3884 ================ User time = 1.88 seconds [188 ticks] System time = 0.01 seconds [1 ticks] Children user time = 0 seconds [0 ticks] Children system time = 0 seconds [0 ticks] Real time = 4.09 seconds [409 ticks] Vyhod #3885 ================ User time = 4.41 seconds [441 ticks] System time = 0.01 seconds [1 ticks] Children user time = 0 seconds [0 ticks] Children system time = 0 seconds [0 ticks] Real time = 7.01 seconds [701 ticks] Vyhod #3886 ================ User time = 8.9 seconds [890 ticks] System time = 0 seconds [0 ticks] Children user time = 0 seconds [0 ticks] Children system time = 0 seconds [0 ticks] Real time = 10.01 seconds [1001 ticks] Vyhod MAIN ================= User time = 0.01 seconds [1 ticks] System time = 0.04 seconds [4 ticks] Children user time = 15.91 seconds [1591 ticks] Children system time = 0.03 seconds [3 ticks] Real time = 10.41 seconds [1041 ticks] Obratite vnimanie, chto 72+188+441+890=1591 (pole tms_cutime dlya main). 6.2.12. Eshche odna programma: hronometrirovanie vypolneniya drugoj programmy. Primer: timer ls -l A. Bogatyrev, 1992-95 - 206 - Si v UNIX /* Hronometrirovanie vypolneniya programmy */ #include <stdio.h> #include <unistd.h> #include <sys/times.h> extern errno; typedef struct _timeStamp { clock_t real_time; clock_t cpu_time; clock_t child_time; clock_t child_sys, child_user; } TimeStamp; TimeStamp TIME(){ struct tms tms; TimeStamp st; st.real_time = times(&tms); st.cpu_time = tms.tms_utime + tms.tms_stime + tms.tms_cutime + tms.tms_cstime; st.child_time = tms.tms_cutime + tms.tms_cstime; st.child_sys = tms.tms_cstime; st.child_user = tms.tms_cutime; return st; } void PRTIME(TimeStamp start, TimeStamp stop){ clock_t HZ = sysconf(_SC_CLK_TCK); clock_t real_time = stop.real_time - start.real_time; clock_t cpu_time = stop.cpu_time - start.cpu_time; clock_t child_time = stop.child_time - start.child_time; printf("%g real, %g cpu, %g child (%g user, %g sys), %ld%%\n", real_time / (double)HZ, cpu_time / (double)HZ, child_time / (double)HZ, stop.child_user / (double)HZ, stop.child_sys / (double)HZ, (child_time * 100L) / (real_time ? real_time : 1) ); } A. Bogatyrev, 1992-95 - 207 - Si v UNIX TimeStamp start, stop; int main(int ac, char *av[]){ char *prog = *av++; if(*av == NULL){ fprintf(stderr, "Usage: %s command [args...]\n", prog); return(1); } start = TIME(); if(fork() == 0){ execvp(av[0], av); perror(av[0]); exit(errno); } while(wait(NULL) > 0); stop = TIME(); PRTIME(start, stop); return(0); } 6.3.1. Sistemnyj vyzov ustat() pozvolyaet uznat' kolichestvo svobodnogo mesta v fajlo- voj sisteme, soderzhashchej zadannyj fajl (v primere nizhe - tekushchij katalog): #include <sys/types.h> #include <sys/stat.h> #include <ustat.h> struct stat st; struct ustat ust; void main(int ac, char *av[]){ char *file = (ac==1 ? "." : av[1]); if( stat(file, &st) < 0) exit(1); ustat(st.st_dev, &ust); printf("Na diske %*.*s\n" "%ld svobodnyh blokov (%ld Kb)\n" "%d svobodnyh I-uzlov\n", sizeof ust.f_fname, sizeof ust.f_fname, ust.f_fname, /* nazvanie fajlovoj sistemy (metka) */ ust.f_tfree, /* bloki po 512 bajt */ (ust.f_tfree * 512L) / 1024, ust.f_tinode ); } Obratite vnimanie na zapis' dlinnoj stroki v printf: stroki, perechislennye posledova- tel'no, skleivayutsya ANSI C kompilyatorom v odnu dlinnuyu stroku: char s[] = "This is" " a line " "of words"; sovpadaet s char s[] = "This is a line of words"; 6.3.2. Bolee pravil'no, odnako, pol'zovat'sya sisvyzovom statvfs - statistika po vir- tual'noj fajlovoj sisteme. Rassmotrim ego v sleduyushchem primere: kopirovanie fajla s proverkoj na nalichie svobodnogo mesta. A. Bogatyrev, 1992-95 - 208 - Si v UNIX #include <stdio.h> #include <string.h> #include <stdlib.h> #include <stdarg.h> #include <fcntl.h> /* O_RDONLY */ #include <sys/types.h> #include <sys/stat.h> #include <sys/statvfs.h> #include <sys/param.h> /* MAXPATHLEN */ char *progname; /* imya programmy */ void error(char *fmt, ...){ va_list args; va_start(args, fmt); fprintf(stderr, "%s: ", progname); vfprintf(stderr, fmt, args); fputc('\n', stderr); va_end(args); } int copyFile(char *to, char *from){ /* kuda, otkuda */ char newname[MAXPATHLEN+1]; char answer[20]; struct stat stf, stt; int fdin, fdout; int n, code = 0; char iobuf[64 * 1024]; char *dirname = NULL, *s; if((fdin = open(from, O_RDONLY)) < 0){ error("Cannot read %s", from); return (-1); } fstat(fdin, &stf); if((stf.st_mode & S_IFMT) == S_IFDIR){ close(fdin); error("%s is a directory", from); return (-2); } A. Bogatyrev, 1992-95 - 209 - Si v UNIX if(stat(to, &stt) >= 0){ /* Fajl uzhe sushchestvuet */ if((stt.st_mode & S_IFMT) == S_IFDIR){ /* I eto katalog */ /* Vydelit' poslednyuyu komponentu puti from */ if((s = strrchr(from, '/')) && s[1]) s++; else s = from; dirname = to; /* Celevoj fajl - fajl v etom kataloge */ sprintf(newname, "%s/%s", to, s); to = newname; if(stat(to, &stt) < 0) goto not_exist; } if(stt.st_dev == stf.st_dev && stt.st_ino == stf.st_ino){ error("%s: cannot copy file to itself", from); return (-3); } switch(stt.st_mode & S_IFMT){ case S_IFBLK: case S_IFCHR: case S_IFIFO: break; default: printf("%s already exists, overwrite ? ", to); fflush(stdout); *answer = '\0'; gets(answer); if(*answer != 'y'){ /* NO */ close(fdin); return (-4); } break; } } A. Bogatyrev, 1992-95 - 210 - Si v UNIX not_exist: printf("COPY %s TO %s\n", from, to); if((stf.st_mode & S_IFMT) == S_IFREG){ /* Proverka nalichiya svobodnogo mesta v kataloge dirname */ struct statvfs fs; char tmpbuf[MAXPATHLEN+1]; if(dirname == NULL){ /* To 'to' - eto imya fajla, a ne kataloga */ strcpy(tmpbuf, to); if(s = strrchr(tmpbuf, '/')){ if(*tmpbuf != '/' || s != tmpbuf){ /* Imena "../xxx" * i vtoroj sluchaj: * absolyutnye imena ne v korne, * to est' ne "/" i ne "/xxx" */ *s = '\0'; }else{ /* "/" ili "/xxx" */ if(s[1]) s[1] = '\0'; } dirname = tmpbuf; } else dirname = "."; } if(statvfs(dirname, &fs) >= 0){ size_t size = (geteuid() == 0 ) ? /* Dostupno superpol'zovatelyu: bajt */ fs.f_frsize * fs.f_bfree : /* Dostupno obychnomu pol'zovatelyu: bajt */ fs.f_frsize * fs.f_bavail; if(size < stf.st_size){ error("Not enough free space on %s: have %lu, need %lu", dirname, size, stf.st_size); close(fdin); return (-5); } } } if((fdout = creat(to, stf.st_mode)) < 0){ error("Can't create %s", to); close(fdin); return (-6); } else { fchmod(fdout, stf.st_mode); fchown(fdout, stf.st_uid, stf.st_gid); } A. Bogatyrev, 1992-95 - 211 - Si v UNIX while (n = read (fdin, iobuf, sizeof iobuf)) { if(n < 0){ error ("read error"); code = (-7); goto done; } if(write (fdout, iobuf, n) != n) { error ("write error"); code = (-8); goto done; } } done: close (fdin); close (fdout); /* Proverit': sootvetstvuet li rezul'tat ozhidaniyam */ if(stat(to, &stt) >= 0 && (stt.st_mode & S_IFMT) == S_IFREG){ if(stf.st_size < stt.st_size){ error("File has grown at the time of copying"); } else if(stf.st_size > stt.st_size){ error("File too short, target %s removed", to); unlink(to); code = (-9); } } return code; } int main(int argc, char *argv[]){ int i, code = 0; progname = argv[0]; if(argc < 3){ error("Usage: %s from... to", argv[0]); return 1; } for(i=1; i < argc-1; i++) code |= copyFile(argv[argc-1], argv[i]) < 0 ? 1 : 0; return code; } Vozvrashchaemaya struktura struct statvfs soderzhit takie polya (v chastnosti): Tipa long: f_frsize razmer bloka f_blocks razmer fajlovoj sistemy v blokah f_bfree svobodnyh blokov (dlya superpol'zovatelya) f_bavail svobodnyh blokov (dlya vseh ostal'nyh) f_files chislo I-nodes v fajlovoj sisteme f_ffree svobodnyh I-nodes (dlya superpol'zovatelya) f_favail svobodnyh I-nodes (dlya vseh ostal'nyh) Tipa char * f_basetype tip fajlovoj sistemy: ufs, nfs, ... A. Bogatyrev, 1992-95 - 212 - Si v UNIX Po dva znacheniya dano potomu, chto operacionnaya sistema rezerviruet chast' fajlovoj sis- temy dlya ispol'zovaniya TOLXKO superpol'zovatelem (chtoby administrator smog raspihat' fajly v sluchae perepolneniya diska, i imel rezerv na eto). ufs - eto UNIX file system iz BSD 4.x Processy v UNIX ispol'zuyut mnogo raznyh mehanizmov vzaimodejstviya. Odnim iz nih yavlyayutsya signaly. Signaly - eto asinhronnye sobytiya. CHto eto znachit? Snachala ob®yasnim, chto takoe sinhronnye sobytiya: ya dva raza v den' podhozhu k pochtovomu yashchiku i proveryayu - net li v nem pochty (sobytij). Vo-pervyh, ya proizvozhu opros - "net li dlya menya sobytiya?", v programme eto vyglyadelo by kak vyzov funkcii oprosa i, mozhet byt', ozhidaniya sobytiya. Vo-vtoryh, ya znayu, chto pochta mozhet ko mne prijti, poskol'ku ya podpisalsya na kakie-to gazety. To est' ya predvaritel'no zakazyval eti sobytiya. Shema s sinhronnymi sobytiyami ochen' rasprostranena. Kassir sidit u kassy i ozhi- daet, poka k nemu v okoshechko ne zaglyanet klient. Poezd periodicheski proezzhaet mimo svetofora i ostanavlivaetsya, esli gorit krasnyj. Funkciya Si passivno "spit" do teh por, poka ee ne vyzovut; odnako ona vsegda gotova vypolnit' svoyu rabotu (obsluzhit' klienta). Takoe ozhidayushchee zakaza (sobytiya) dejstvuyushchee lico nazyvaetsya server. Posle vypolneniya zakaza server vnov' perehodit v sostoyanie ozhidaniya vyzova. Itak, esli sobytie ozhidaetsya v special'nom meste i v opredelennye momenty vremeni (izdaetsya nekij vyzov dlya OPROSA) - eto sinhronnye sobytiya. Kanonicheskij primer - funkciya gets, kotoraya zaderzhit vypolnenie programmy, poka s klaviatury ne budet vvedena stroka. Bol'shinstvo ozhidanij vnutri sistemnyh vyzovov - sinhronny. YAdro OS vystu- paet dlya programm pol'zovatelej v roli servera, vypolnyayushchego sisvyzovy (hotya i ne tol'ko v etoj roli - yadro inogda predprinimaet i aktivnye dejstviya: peredacha proces- sora drugomu processu cherez opredelennoe vremya (rezhim razdeleniya vremeni), ubivanie processa pri oshibke, i.t.p.). Signaly - eto asinhronnye sobytiya. Oni prihodyat neozhidanno, v lyuboj moment vre- meni - vrode telefonnogo zvonka. Krome togo, ih ne trebuetsya zakazyvat' - signal processu mozhet postupit' sovsem bez povoda. Analogiya iz zhizni takova: chelovek sidit i pishet pis'mo. Vdrug ego oklikayut posredi frazy - on otvlekaetsya, otvechaet na vop- ros, i vnov' prodolzhaet prervannoe zanyatie. CHelovek ne ozhidal etogo oklika (byt' mozhet, on gotov k nemu, no on ne oziralsya po storonam special'no). Krome togo, sig- nal mog postupit' kogda on pisal 5-oe predlozhenie, a mog - kogda 34-oe. Moment vre- meni, v kotoryj proizojdet preryvanie, ne fiksirovan. Signaly imeyut nomera, prichem ih kolichestvo ogranicheno - est' opredelennyj spisok dopustimyh signalov. Nomera i mnemonicheskie imena signalov perechisleny v include- fajle <signal.h> i imeyut vid SIGnechto. Dopustimy signaly s nomerami 1..NSIG-1, gde NSIG opredeleno v etom fajle. Pri poluchenii signala my uznaem ego nomer, no ne uznaem nikakoj inoj informacii: ni ot kogo postupil signal, ni chto ot nas hotyat. Prosto "zvonit telefon". CHtoby poluchit' dopolnitel'nuyu informaciyu, nash process dolzhen vzyat' ee iz drugogo izvestnogo mesta; naprimer - prochest' zakaz iz nekotorogo fajla, ob imeni kotorogo vse nashi programmy zaranee "dogovorilis'". Signaly processu mogut postupat' tremya putyami: - Ot drugogo processa, kotoryj yavno posylaet ego nam vyzovom kill(pid, sig); gde pid - identifikator (nomer) processa-poluchatelya, a sig - nomer signala. Poslat' signal mozhno tol'ko rodstvennomu processu - zapushchennomu tem zhe pol'zova- telem. - Ot operacionnoj sistemy. Sistema mozhet posylat' processu ryad signalov, signali- ziruyushchih ob oshibkah, naprimer pri obrashchenii programmy po nesushchestvuyushchemu adresu ili pri oshibochnom nomere sistemnogo vyzova. Takie signaly obychno prekrashchayut nash process. - Ot pol'zovatelya - s klaviatury terminala mozhno nazhimom nekotoryh klavish poslat' signaly SIGINT i SIGQUIT. Sobstvenno, signal posylaetsya drajverom terminala pri poluchenii im s klaviatury opredelennyh simvolov. Tak mozhno prervat' zaciklivshu- yusya ili nadoevshuyu programmu. Process-poluchatel' dolzhen kak-to otreagirovat' na signal. Programma mozhet: A. Bogatyrev, 1992-95 - 213 - Si v UNIX - proignorirovat' signal (ne otvetit' na zvonok); - perehvatit' signal (snyat' trubku), vypolnit' kakie-to dejstviya, zatem prodolzhit' prervannoe zanyatie; - byt' ubitoj signalom (zvonok byl podkreplen broskom granaty v okno); V bol'shinstve sluchaev signal po umolchaniyu ubivaet process-poluchatel'. Odnako process mozhet izmenit' eto umolchanie i zadat' svoyu reakciyu yavno. |to delaetsya vyzovom signal: #include <signal.h> void (*signal(int sig, void (*react)() )) (); Parametr react mozhet imet' znachenie: SIG_IGN signal sig budet otnyne ignorirovat'sya. Nekotorye signaly (naprimer SIGKILL) nevozmozhno perehvatit' ili proignorirovat'. SIG_DFL vosstanovit' reakciyu po umolchaniyu (obychno - smert' poluchatelya). imya_funkcii Naprimer void fr(gotsig){ ..... } /* obrabotchik */ ... signal (sig, fr); ... /* zadanie reakcii */ Togda pri poluchenii signala sig budet vyzvana funkciya fr, v kotoruyu v kachestve argumenta sistemoj budet peredan nomer signala, dejstvitel'no vyzvavshego ee - gotsig==sig. |to polezno, t.k. mozhno zadat' odnu i tu zhe funkciyu v kachestve reakcii dlya neskol'kih signalov: ... signal (sig1, fr); signal(sig2, fr); ... Posle vozvrata iz funkcii fr() programma prodolzhitsya s prervannogo mesta. Pered vyzovom funkcii-obrabotchika reakciya avtomaticheski sbrasyvaetsya v reakciyu po umolchaniyu SIG_DFL, a posle vyhoda iz obrabotchika snova vosstanavlivaetsya v fr. |to znachit, chto vo vremya raboty funkcii-obrabotchika mozhet prijti signal, kotoryj ub'et programmu. Privedem spisok nekotoryh signalov; polnoe opisanie posmotrite v dokumentacii. Kolonki tablicy: G - mozhet byt' perehvachen; D - po umolchaniyu ubivaet process (k), ignoriruetsya (i); C - obrazuetsya damp pamyati processa: fajl core, kotoryj zatem mozhet byt' issledovan otladchikom adb; F - reakciya na signal sbrasyvaetsya; S - posylaetsya obychno sistemoj, a ne yavno. signal G D C F S smysl SIGTERM + k - + - zavershit' process SIGKILL - k - + - ubit' process SIGINT + k - + - preryvanie s klavish SIGQUIT + k + + - preryvanie s klavish SIGALRM + k - + + budil'nik SIGILL + k + - + zapreshchennaya komanda SIGBUS + k + + + obrashchenie po nevernomu SIGSEGV + k + + + adresu SIGUSR1, USR2 + i - + - pol'zovatel'skie SIGCLD + i - + + smert' potomka - Signal SIGILL ispol'zuetsya inogda dlya emulyacii komand s plavayushchej tochkoj, chto proishodit primerno tak: pri obnaruzhenii "zapreshchennoj" komandy dlya otsutstvuyu- shchego processora "plavayushchej" arifmetiki apparatura daet preryvanie i sistema posylaet processu signal SIGILL. Po signalu vyzyvaetsya funkciya-emulyator plavayu- shchej arifmetiki (podklyuchaemaya k vypolnyaemomu fajlu avtomaticheski), kotoraya i obrabatyvaet trebuemuyu komandu. |to mozhet proishodit' mnogo raz, imenno poetomu A. Bogatyrev, 1992-95 - 214 - Si v UNIX reakciya na etot signal ne sbrasyvaetsya. - SIGALRM posylaetsya v rezul'tate ego zakaza vyzovom alarm() (sm. nizhe). - Signal SIGCLD posylaetsya processu-roditelyu pri vypolnenii processom-potomkom sisvyzova exit (ili pri smerti vsledstvie polucheniya signala). Obychno process- roditel' pri poluchenii takogo signala (esli on ego zakazyval) reagiruet, vypol- nyaya v obrabotchike signala vyzov wait (sm. nizhe). Po-umolchaniyu etot signal igno- riruetsya. - Reakciya SIG_IGN ne sbrasyvaetsya v SIG_DFL pri prihode signala, t.e. signal igno- riruetsya postoyanno. - Vyzov signal vozvrashchaet staroe znachenie reakcii, kotoroe mozhet byt' zapomneno v peremennuyu vida void (*f)(); a potom vosstanovleno. - Sinhronnoe ozhidanie (sisvyzov) mozhet inogda byt' prervano asinhronnym sobytiem (signalom), no ob etom nizhe. Nekotorye versii UNIX predostavlyayut bolee razvitye sredstva raboty s signalami. Opishem nekotorye iz sredstv, imeyushchihsya v BSD (v drugih sistemah oni mogut byt' smode- lirovany drugimi sposobami). Pust' u nas v programme est' "kriticheskaya sekciya", vo vremya vypolneniya kotoroj prihod signalov nezhelatelen. My mozhem "zamorozit'" (zablokirovat') signal, otlozhiv moment ego postupleniya do "razmorozki": | sighold(sig); zablokirovat' signal | : KRITICHESKAYA :<---processu poslan signal sig, SEKCIYA : no on ne vyzyvaet reakciyu nemedlenno, | : a "visit", ozhidaya razresheniya. | : sigrelse(sig); razblokirovat' |<----------- sig | nakopivshiesya signaly dohodyat, | vyzyvaetsya reakciya. Esli vo vremya blokirovki processu bylo poslano neskol'ko odinakovyh signalov sig, to pri razblokirovanii postupit tol'ko odin. Postuplenie signalov vo vremya blokirovki prosto otmechaetsya v special'noj bitovoj shkale v pasporte processa (primerno tak): mask |= (1 << (sig - 1)); i pri razblokirovanii signala sig, esli sootvetstvuyushchij bit vystavlen, to prihodit odin takoj signal (sistema vyzyvaet funkciyu reakcii). To est' sighold zastavlyaet prihodyashchie signaly "nakaplivat'sya" v special'noj maske, vmesto togo, chtoby nemedlenno vyzyvat' reakciyu na nih. A sigrelse razreshaet "nako- pivshimsya" signalam (esli oni est') prijti i vyzyvaet reakciyu na nih. Funkciya sigset(sig, react); analogichna funkcii signal, za isklyucheniem togo, chto na vremya raboty obrabotchika sig- nala react, prihod signala sig blokiruetsya; to est' pered vyzovom react kak by dela- etsya sighold, a pri vyhode iz obrabotchika - sigrelse. |to znachit, chto esli vo vremya raboty obrabotchika signala pridet takoj zhe signal, to programma ne budet ubita, a "zapomnit" prishedshij signal, i obrabotchik budet vyzvan povtorno (kogda srabotaet sigrelse). Funkciya sigpause(sig); vyzyvaetsya vnutri "ramki" sighold(sig); ... sigpause(sig); ... sigrelse(sig); A. Bogatyrev, 1992-95 - 215 - Si v UNIX i vyzyvaet zaderzhku vypolneniya processa do prihoda signala sig. Funkciya razreshaet prihod signala sig (obychno na nego dolzhna byt' zadana reakciya pri pomoshchi sigset), i "zasypaet" do prihoda signala sig. V UNIX standarta POSIX dlya upravleniya signalami est' vyzovy sigaction, sigproc- mask, sigpending, sigsuspend. Posmotrite v dokumentaciyu! 6.4.1. Napishite programmu, vydayushchuyu na ekran fajl /etc/termcap. Perehvatyvajte sig- nal SIGINT, pri poluchenii signala zaprashivajte "Prodolzhat'?". Po otvetu 'y' - pro- dolzhit' vydachu; po 'n' - zavershit' programmu; po 'r' - nachat' vydavat' fajl s nachala: lseek(fd,0L,0). Ne zabud'te zanovo pereustanovit' reakciyu na SIGINT, poskol'ku posle polucheniya signala reakciya avtomaticheski sbrasyvaetsya. #include <signal.h> void onintr(sig){ /* sig - nomer signala */ signal (sig, onintr); /* vosstanovit' reakciyu */ ... zapros i dejstviya ... } main(){ signal (SIGINT, onintr); ... } Signal preryvaniya mozhno ignorirovat'. |to delaetsya tak: signal (SIGINT, SIG_IGN); Takuyu programmu nel'zya prervat' s klaviatury. Napomnim, chto reakciya SIG_IGN sohranya- etsya pri prihode signala. 6.4.2. Sistemnyj vyzov, nahodyashchijsya v sostoyanii ozhidaniya kakogo-to sobytiya (read zhdushchij nazhatiya knopki na klaviature, wait zhdushchij okonchaniya processa-potomka, i.t.p.), mozhet byt' prervan signalom. Pri etom sisvyzov vernet znachenie "oshibka" (-1) i errno stanet ravno EINTR. |to pozvolyaet nam pisat' sistemnye vyzovy s vystavleniem tajma- uta: esli sobytie ne proishodit v techenie zadannogo vremeni, to zavershit' ozhidanie i prervat' sisvyzov. Dlya etoj celi ispol'zuetsya vyzov alarm(sec), zakazyvayushchij posylku signala SIGALRM nashej programme cherez celoe chislo sec sekund (0 - otmenyaet zakaz): #include <signal.h> void (*oldaction)(); int alarmed; /* prozvonil budil'nik */ void onalarm(nsig){ alarmed++; } ... /* ustanovit' reakciyu na signal */ oldaction = signal (SIGALRM, onalarm); /* zakazat' budil'nik cherez TIMEOUT sek. */ alarmed = 0; alarm ( TIMEOUT /* sec */ ); sys_call(...); /* zhdet sobytiya */ // esli nas sbil signal, to po signalu budet // eshche vyzvana reakciya na nego - onalarm if(alarmed){ // sobytie tak i ne proizoshlo. // vyzov prervan signalom t.k. isteklo vremya. }else{ alarm(0); /* otmenit' zakaz signala */ // sobytie proizoshlo, sisvyzov uspel // zavershit'sya do istecheniya vremeni. } signal (SIGALRM, oldaction); Napishite programmu, kotoraya ozhidaet vvoda s klaviatury v techenie 10 sekund. Esli nichego ne vvedeno - pechataet "Net vvoda", inache - pechataet "Spasibo". Dlya vvoda mozhno ispol'zovat' kak vyzov read, tak i funkciyu gets (ili getchar), poskol'ku A. Bogatyrev, 1992-95 - 216 - Si v UNIX funkciya eta vse ravno vnutri sebya izdaet sistemnyj vyzov read. Issledujte, kakoe znachenie vozvrashchaet fgets (gets) v sluchae preryvaniya ee sistemnym vyzovom. /* Kopirovanie standartnogo vvoda na standartnyj vyvod * s ustanovlennym tajm-autom. * |to pozvolyaet ispol'zovat' programmu dlya chteniya iz FIFO-fajlov * i s klaviatury. * Nebol'shaya modifikaciya pozvolyaet ispol'zovat' programmu * dlya kopirovaniya "rastushchego" fajla (t.e. takogo, kotoryj v * nastoyashchij moment eshche prodolzhaet zapisyvat'sya). * Zamechanie: * V DEMOS-2.2 signal NE sbivaet chtenie iz FIFO-fajla, * a poluchenie signala otkladyvaetsya do vyhoda iz read() * po uspeshnomu chteniyu informacii. Pol'zujtes' open()-om * s flagom O_NDELAY, chtoby poluchit' trebuemyj effekt. * * Vyzov: a.out /dev/tty * * Po motivam knigi M.Dansmura i G.Dejvisa. */ #define WAIT_TIME 5 /* zhdat' 5 sekund */ #define MAX_TRYS 5 /* maksimum 5 popytok */ #define BSIZE 256 #define STDIN 0 /* deskriptor standartnogo vvoda */ #define STDOUT 1 /* deskriptor standartnogo vyvoda */ #include <signal.h> #include <errno.h> #include <stdio.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> char buffer [ BSIZE ]; extern int errno; /* kod oshibki */ void timeout(nsig){ signal( SIGALRM, timeout ); } void main(argc, argv) char **argv;{ int fd, n, trys = 0; struct stat stin, stout; if( argc != 2 ){ fprintf(stderr, "Vyzov: %s fajl\n", argv[0]); exit(1); } if((fd = !strcmp(argv[1],"-")? STDIN : open(argv[1],O_RDONLY)) < 0){ fprintf(stderr, "Ne mogu chitat' %s\n", argv[1]); exit(2); } /* Proverit', chto vvod ne sovpadaet s vyvodom, * hardcat aFile >> aFile * krome sluchaya, kogda vyvod - terminal. * Takaya proverka polezna dlya programm-fil'trov (STDIN->STDOUT), * chtoby isklyuchit' porchu ishodnoj informacii */ fstat(fd, &stin); fstat(STDOUT, &stout); if( !isatty(STDOUT) && stin.st_ino == stout.st_ino && stin.st_dev == stout.st_dev ){ fprintf(stderr, "\aVvod == vyvodu, vozmozhno poteryana informaciya v %s.\n",argv[1]); exit(33); } A. Bogatyrev, 1992-95 - 217 - Si v UNIX signal( SIGALRM, timeout ); while( trys < MAX_TRYS ){ alarm( WAIT_TIME ); /* zakazat' signal cherez 5 sek */ /* i zhdem vvoda ... */ n = read( fd, buffer, BSIZE ); alarm(0); /* otmenili zakaz signala */ /* (hotya, vozmozhno, on uzhe poluchen) */ /* proveryaem: pochemu my slezli s vyzova read() ? */ if( n < 0 && errno == EINTR ){ /* My byli sbity signalom SIGALRM, * kod oshibki EINTR - sisvyzov prervan * nekim signalom. */ fprintf( stderr, "\7timed out (%d raz)\n", ++trys ); continue; } if( n < 0 ){ /* oshibka chteniya */ fprintf( stderr, "read error.\n" ); exit(4); } if( n == 0 ){ /* dostignut konec fajla */ fprintf( stderr, "Dostignut EOF.\n\n" ); exit(0); } /* kopiruem prochitannuyu informaciyu */ write( STDOUT, buffer, n ); trys = 0; } fprintf( stderr, "Vse popytki provalilis'.\n" ); exit(5); } Esli my hotim, chtoby sisvyzov ne mog preryvat'sya signalom, my dolzhny zashchitit' ego: #include <signal.h> void (*fsaved)(); ... fsaved = signal (sig, SIG_IGN); sys_call(...); signal (sig, fsaved); ili tak: sighold(sig); sys_call(...); sigrelse(sig); Signalami mogut byt' prervany ne vse sistemnye vyzovy i ne pri vseh obstoyatel'stvah. 6.4.3. Napishite funkciyu sleep(n), zaderzhivayushchuyu vypolnenie programmy na n sekund. Vospol'zujtes' sistemnym vyzovom alarm(n) (budil'nik) i vyzovom pause(), kotoryj zaderzhivaet programmu do polucheniya lyubogo signala. Predusmotrite restart pri poluche- nii vo vremya ozhidaniya drugogo signala, nezheli SIGALRM. Sohranyajte zakaz alarm, sde- lannyj do vyzova sleep (alarm vydaet chislo sekund, ostavsheesya do zaversheniya predydu- shchego zakaza). Na samom dele est' takaya STANDARTNAYA funkciya. Otvet: A. Bogatyrev, 1992-95 - 218 - Si v UNIX #include <sys/types.h> #include <stdio.h> #include <signal.h> int got; /* prishel li signal */ void onalarm(int sig) { printf( "Budil'nik\n" ); got++; } /* signal poluchen */ void sleep(int n){ time_t time(), start = time(NULL); void (*save)(); int oldalarm, during = n; if( n <= 0 ) return; got = 0; save = signal(SIGALRM, onalarm); oldalarm = alarm(3600); /* Uznat' staryj zakaz */ if( oldalarm ){ printf( "Byl zakazan signal, kotoryj pridet cherez %d sek.\n", oldalarm ); if(oldalarm > n) oldalarm -= n; else { during = n = oldalarm; oldalarm = 1; } } printf( "n=%d oldalarm=%d\n", n, oldalarm ); while( n > 0 ){ printf( "alarm(%d)\n", n ); alarm(n); /* zakazat' SIGALRM cherez n sekund */ pause(); if(got) break; /* inache my sbity s pause drugim signalom */ n = during - (time(NULL) - start); /* proshlo vremeni */ } printf( "alarm(%d) pri vyhode\n", oldalarm ); alarm(oldalarm); /* alarm(0) - otmena zakaza signala */ signal(SIGALRM, save); /* vosstanovit' reakciyu */ } void onintr(int nsig){ printf( "Signal SIGINT\n"); signal(SIGINT, onintr); } void onOldAlarm(int nsig){ printf( "Zvonit staryj budil'nik\n"); } void main(){ int time1 = 0; /* 5, 10, 20 */ setbuf(stdout, NULL); signal(SIGINT, onintr); signal(SIGALRM, onOldAlarm); alarm(time1); sleep(10); if(time1) pause(); printf("CHao!\n"); } A. Bogatyrev, 1992-95 - 219 - Si v UNIX 6.4.4. Napishite "chasy", vydayushchie tekushchee vremya kazhdye 3 sekundy. #include <signal.h> #include <time.h> #include <stdio.h> void tick(nsig){ time_t tim; char *s; signal (SIGALRM, tick); alarm(3); time(&tim); s = ctime(&tim); s[ strlen(s)-1 ] = '\0'; /* obrubit' '\n' */ fprintf(stderr, "\r%s", s); } main(){ tick(0); for(;;) pause(); } 6.5.1. Kakie klassy pamyati imeyut dannye, v kakih segmentah programmy oni raspolo- zheny? char x[] = "hello"; int y[25]; char *p; main(){ int z = 12; int v; static int w = 25; static int q; char s[20]; char *pp; ... v = w + z; /* #1 */ } Otvet: Peremennaya Klass pamyati Segment Nachal'noe znachenie x static data/DATA "hello" y static data/BSS {0, ..., 0} p static data/BSS NULL z auto stack 12 v auto stack ne opredeleno w static data/DATA 25 q static data/BSS 0 s auto stack ne opredeleno pp auto stack ne opredeleno main static text/TEXT Bol'shimi bukvami oboznacheny segmenty, hranimye v vypolnyaemom fajle: DATA - eto inicializirovannye staticheskie dannye (kotorym prisvoeny nachal'nye znache- niya). Oni pomeshchayutsya kompilyatorom v fajl v vide gotovyh konstant, a pri zapuske programmy (pri ee zagruzke v pamyat' mashiny), prosto kopiruyutsya v pamyat' iz fajla. BSS (Block Started by Symbol) - neinicializirovannye staticheskie dannye. Oni po umolchaniyu imeyut nachal'noe zna- chenie 0 (NULL, "", '\0'). |ta pamyat' raspisyvaetsya nulyami pri zapuske prog- rammy, a v fajle hranitsya lish' ee razmer. A. Bogatyrev, 1992-95 - 220 - Si v UNIX TEXT - segment, soderzhashchij mashinnye komandy (kod). Hranyashchayasya v fajle vypolnyaemaya programma imeet takzhe zagolovok - v nem v chastnosti soderzhatsya razmery perechislennyh segmentov i ih mestopolozhenie v fajle; i eshche - v samom konce fajla - tablicu imen. V nej soderzhatsya imena vseh funkcij i peremennyh, ispol'zuemyh v programme, i ih adresa. |ta tablica ispol'zuetsya otladchikami adb i sdb, a takzhe pri sborke programmy iz neskol'kih ob®ektnyh fajlov programmoj ld. Prosmotret' ee mozhno komandoj nm imyaFajla Dlya ekonomii diskovogo prostranstva etu tablicu chasto udalyayut, chto delaetsya komandoj strip imyaFajla Razmery segmentov mozhno uznat' komandoj size imyaFajla Programma, zagruzhennaya v pamyat' komp'yutera (t.e. process), sostoit iz 3x segmentov, otnosyashchihsya neposredstvenno k programme: stack - stek dlya lokal'nyh peremennyh funkcij (avtomaticheskih peremennyh). |tot seg- ment sushchestvuet tol'ko u vypolnyayushchejsya programmy, poskol'ku otvedenie pamyati v steke proizvoditsya vypolneniem nekotoryh mashinnyh komand (poetomu opisanie avto- maticheskih peremennyh v Si - eto na samom dele vypolnyaemye operatory, hotya i ne s tochki zreniya yazyka). Segment steka avtomaticheski rastet po mere nadobnosti (esli my vyzyvaem novye i novye funkcii, otvodyashchie peremennye v steke). Za etim sledit apparatura dispetchera pamyati. data - segment, v kotoryj skleeny segmenty staticheskih dannyh DATA i BSS, zagruzhennye iz fajla. |tot segment takzhe mozhet izmenyat' svoj razmer, no delat' eto nado yavno - sistemnymi vyzovami sbrk ili brk. V chastnosti, funkciya malloc() dlya raz- meshcheniya dinamicheski otvodimyh dannyh uvelichivaet razmer etogo segmenta. text - eto vypolnyaemye komandy, kopiya segmenta TEXT iz fajla. Tak stroka s metkoj #1 soderzhitsya v vide mashinnyh komand imenno v etom segmente. Krome togo, kazhdyj process imeet eshche: proc - eto rezidentnaya chast' pasporta processa v tablice processov v yadre operacion- noj sistemy; user - eto 4-yj segment processa - nerezidentnaya chast' pasporta (u-area). K etomu segmentu imeet dostup tol'ko yadro, no ne sama programma. Pasport processa byl podelen na 2 chasti tol'ko iz soobrazhenij ekonomii pamyati v yadre: kontekst processa (tablica otkrytyh fajlov, ssylka na I-uzel tekushchego kataloga, tab- lica reakcij na signaly, ssylka na I-uzel upravlyayushchego terminala, i.t.p.) nuzhen yadru tol'ko pri obsluzhivanii tekushchego aktivnogo processa. Kogda aktiven drugoj process - eta informaciya v pamyati yadra ne nuzhna. Bolee togo, esli process iz-za nehvatki mesta v pamyati mashiny byl otkachan na disk, eta informaciya takzhe mozhet byt' otkachana na disk i podkachana nazad lish' vmeste s processom. Poetomu kontekst byl vydelen v otdel'nyj segment, i segment etot podklyuchaetsya k adresnomu prostranstvu yadra lish' pri vypolne- nii processom kakogo-libo sistemnogo vyzova (eto podklyuchenie nazyvaetsya "pereklyuchenie konteksta" - context switch). CHetyre segmenta processa mogut raspolagat'sya v pamyati mashiny ne obyazatel'no podryad - mezhdu nimi mogut lezhat' segmenty drugih processov. Shema sostavnyh chastej processa: P R O C E S S tablica processov: pasport v yadre segmenty v pamyati struct proc[] ####---------------> stack 1 #### data 2 text 3 kontekst: struct user 4 A. Bogatyrev, 1992-95 - 221 - Si v UNIX Kazhdyj process imeet unikal'nyj nomer, hranyashchijsya v pole p_pid v strukture proc|-. V nej takzhe hranyatsya: adresa segmentov processa v pamyati mashiny (ili na diske, esli process otkachan); p_uid - nomer vladel'ca processa; p_ppid - nomer processa-roditelya; p_pri, p_nice - prioritety processa; p_pgrp - gruppa processa; p_wchan - ozhidaemoe processom sobytie; p_flag i p_stat - sostoyanie processa; i mnogoe drugoe. Struktura proc opredelena v include-fajle <sys/proc.h>, a struktura user - v <sys/user.h>. 6.5.2. Sistemnyj vyzov fork() (vilka) sozdaet novyj process: kopiyu processa, izdav- shego vyzov. Otlichie etih processov sostoit tol'ko v vozvrashchaemom fork-om znachenii: 0 - v novom processe. pid novogo processa - v ishodnom. Vyzov fork mozhet zavershit'sya neudachej esli tablica processov perepolnena. Prostejshij sposob sdelat' eto: main(){ while(1) if( ! fork()) pause(); } Odno gnezdo tablicy processov zarezervirovano - ego mozhet ispol'zovat' tol'ko super- pol'zovatel' (v celyah zhiznesposobnosti sistemy: hotya by dlya togo, chtoby zapustit' programmu, ubivayushchuyu vse eti processy-varvary). Vyzov fork sozdaet kopiyu vseh 4h segmentov processa i vydelyaet porozhdennomu pro- cessu novyj pasport i nomer. Inogda segment text ne kopiruetsya, a ispol'zuetsya pro- cessami sovmestno ("razdelyaemyj segment") v celyah ekonomii pamyati. Pri kopirovanii segmenta user kontekst porozhdayushchego processa nasleduetsya porozhdennym processom (sm. nizhe). Provedite opyt, dokazyvayushchij chto porozhdennyj sistemnym vyzovom fork() process i porodivshij ego - ravnopravny. Povtorite neskol'ko raz programmu: #include <stdio.h> int pid, i, fd; char c; main(){ fd = creat( "TEST", 0644); if( !(pid = fork())){ /* syn: porozhdennyj process */ c = 'a'; for(i=0; i < 5; i++){ write(fd, &c, 1); c++; sleep(1); } printf("Syn %d okonchen\n", getpid()); exit(0); } /* else process-otec */ c = 'A'; for(i=0; i < 5; i++){ write(fd, &c, 1); c++; sleep(1); } printf("Roditel' %d processa %d okonchen\n", getpid(), pid ); } V fajle TEST my budem ot sluchaya k sluchayu poluchat' stroki vida aABbCcDdEe ili AaBbcdCDEe chto govorit o tom, chto pervym "prosnut'sya" posle fork() mozhet lyuboj iz dvuh proces- sov. Esli zhe opyt daet ustojchivo stroki, nachinayushchiesya s odnoj i toj zhe bukvy - znachit ____________________ |- Process mozhet uznat' ego vyzovom pid=getpid(); A. Bogatyrev, 1992-95 - 222 - Si v UNIX v dannoj realizacii sistemy odin iz processov vse zhe zapuskaetsya ran'she. No ne stoit ispol'zovat' etot effekt - pri perenose na druguyu sistemu ego mozhet ne byt'! Dannyj opyt osnovan na sleduyushchem svojstve sistemy UNIX: pri sistemnom vyzove fork() porozhdennyj process poluchaet vse otkrytye porozhdayushchim processom fajly "v nas- ledstvo" - eto sootvetstvuet tomu, chto tablica otkrytyh processom fajlov kopiruetsya v process-potomok. Imenno tak, v chastnosti, peredayutsya ot otca k synu standartnye kanaly 0, 1, 2: porozhdennomu processu ne nuzhno otkryvat' standartnye vvod, vyvod i vyvod oshibok yavno. Iznachal'no zhe oni otkryvayutsya special'noj programmoj pri vashem vhode v sistemu. do vyzova fork(); tablica otkrytyh fajlov processa 0 ## ---<--- klaviatura 1 ## --->--- displej 2 ## --->--- displej ... ## fd ## --->--- fajl TEST ... ## posle fork(); PROCESS-PAPA PROCESS-SYN 0 ## ---<--- klaviatura --->--- ## 0 1 ## --->--- displej ---<--- ## 1 2 ## --->--- displej ---<--- ## 2 ... ## ## ... fd ## --->--- fajl TEST ---<--- ## fd ... ## | ## ... *--RWptr-->FAJL Ssylki iz tablic otkrytyh fajlov v processah ukazyvayut na struktury "otkrytyj fajl" v yadre (sm. glavu pro fajly). Takim obrazom, dva processa poluchayut dostup k odnoj i toj zhe strukture i, sledovatel'no, imeyut obshchij ukazatel' chteniya/zapisi dlya etogo fajla. Poetomu, kogda processy "otec" i "syn" pishut po deskriptoru fd, oni pol'zuyutsya odnim i tem zhe ukazatelem R/W, t.e. informaciya ot oboih processov zapisyvaetsya posle- dovatel'no. Na principe nasledovaniya i sovmestnogo ispol'zovaniya otkrytyh fajlov osnovan takzhe sistemnyj vyzov pipe. Porozhdennyj process nasleduet takzhe: reakcii na signaly (!!!), tekushchij katalog, upravlyayushchij terminal, nomer vladel'ca processa i gruppu vladel'ca, i.t.p. Pri sistemnom vyzove exec() (kotoryj zamenyaet programmu, vypolnyaemuyu processom, na programmu iz ukazannogo fajla) vse otkrytye kanaly takzhe dostayutsya v nasledstvo novoj programme (a ne zakryvayutsya). 6.5.3. Process-kopiya eto horosho, no ne sovsem to, chto nam hotelos' by. Nam hochetsya zapustit' programmu, soderzhashchuyusya v vypolnyaemom fajle (naprimer a.out). Dlya etogo sushchestvuet sistemnyj vyzov exec, kotoryj imeet neskol'ko raznovidnostej. Rassmotrim tol'ko dve: char *path; char *argv[], *envp[], *arg0, ..., *argn; execle(path, arg0, arg1, ..., argn, NULL, envp); execve(path, argv, envp); Sistemnyj vyzov exec zamenyaet programmu, vypolnyaemuyu dannym processom, na programmu, zagruzhaemuyu iz fajla path. V dannom sluchae path dolzhno byt' polnym imenem fajla ili imenem fajla ot tekushchego kataloga: /usr/bin/vi a.out ../mybin/xkick A. Bogatyrev, 1992-95 - 223 - Si v UNIX Fajl dolzhen imet' kod dostupa "vypolnenie". Pervye dva bajta fajla (v ego zago- lovke), rassmatrivaemye kak short int, soderzhat tak nazyvaemoe "magicheskoe chislo" (A_MAGIC), svoe dlya kazhdogo tipa mashin (smotri include-fajl <a.out.h>). Ego pomeshchaet v nachalo vypolnyaemogo fajla redaktor svyazej ld pri komponovke programmy iz ob®ektnyh fajlov. |to chislo dolzhno byt' pravil'nym, inache sistema otkazhetsya zapuskat' prog- rammu iz etogo fajla. Byvaet neskol'ko raznyh magicheskih chisel, oboznachayushchih raznye sposoby organizacii programmy v pamyati. Naprimer, est' variant, v kotorom segmenty text i data skleeny vmeste (togda text ne razdelyaem mezhdu processami i ne zashchishchen ot modifikacii programmoj), a est' - gde dannye i tekst nahodyatsya v razdel'nyh adresnyh prostranstvah i zapis' v text zapreshchena (apparatno). Ostal'nye argumenty vyzova - arg0, ..., argn - eto argumenty funkcii main novoj programmy. Vo vtoroj forme vyzova argumenty ne perechislyayutsya yavno, a zanosyatsya v mas- siv. |to pozvolyaet formirovat' proizvol'nyj massiv strok-argumentov vo vremya raboty programmy: char *argv[20]; argv[0]="ls"; argv[1]="-l"; argv[2]="-i"; argv[3]=NULL; execv( "/bin/ls", argv); libo execl( "/bin/ls", "ls","-l","-i", NULL): V rezul'tate etogo vyzova tekushchaya programma zavershaetsya (no ne process!) i vmesto nee zapuskaetsya programma iz zadannogo fajla: segmenty stack, data, text staroj programmy unichtozhayutsya; sozdayutsya novye segmenty data i text, zagruzhaemye iz fajla path; otvo- ditsya segment stack (pervonachal'no - ne ochen' bol'shogo razmera); segment user sohra- nyaetsya ot staroj programmy (za isklyucheniem reakcij na signaly, otlichnyh ot SIG_DFL i SIG_IGN - oni budut sbrosheny v SIG_DFL). Zatem budet vyzvana funkciya main novoj programmy s argumentami argv: void main( argc, argv ) int argc; char *argv[]; { ... } Kolichestvo argumentov - argc - podschitaet sama sistema. Stroka NULL ne podschityva- etsya. Process ostaetsya tem zhe samym - on imeet tot zhe pasport (tol'ko adresa segmentov izmenilis'); tot zhe nomer (pid); vse otkrytye prezhnej programmoj fajly ostayutsya otk- rytymi (s temi zhe deskriptorami); tekushchij katalog takzhe nasleduetsya ot staroj prog- rammy; signaly, kotorye ignorirovalis' eyu, takzhe budut ignorirovat'sya (ostal'nye sbrasyvayutsya v SIG_DFL). Zato "sushchnost'" processa podvergaetsya pererozhdeniyu - on vypolnyaet teper' inuyu programmu. Takim obrazom, sistemnyj vyzov exec osushchestvlyaet vyzov funkcii main, nahodyashchejsya v drugoj programme, peredavaya ej svoi argumenty v kachestve vhodnyh. Sistemnyj vyzov exec mozhet ne udat'sya, esli ukazannyj fajl path ne sushchestvuet, libo vy ne imeete prava ego vypolnyat' (takie kody dostupa), libo on ne yavlyaetsya vypolnyaemoj programmoj (nevernoe magicheskoe chislo), libo slishkom velik dlya dannoj mashiny (sistemy), libo fajl otkryt kakim-nibud' processom (naprimer eshche zapisyvaetsya kompilyatorom). V etom sluchae prodolzhitsya vypolnenie prezhnej programmy. Esli zhe vyzov uspeshen - vozvrata iz exec ne proishodit voobshche (poskol'ku upravlenie pereda- etsya v druguyu programmu). Argument argv[0] obychno polagayut ravnym path. Po nemu programma, imeyushchaya nes- kol'ko imen (v fajlovoj sisteme), mozhet vybrat' CHTO ona dolzhna delat'. Tak programma /bin/ls imeet al'ternativnye imena lr, lf, lx, ll. Zapuskaetsya odna i ta zhe prog- ramma, no v zavisimosti ot argv[0] ona dalee delaet raznuyu rabotu. Argument envp - eto "okruzhenie" programmy (sm. nachalo etoj glavy). Esli on ne zadan - peredaetsya okruzhenie tekushchej programmy (nasleduetsya soderzhimoe massiva, na kotoryj ukazyvaet peremennaya environ); esli zhe zadan yavno (naprimer, okruzhenie skopi- rovano v kakoj-to massiv i chast' peremennyh podpravlena ili dobavleny novye peremen- nye) - novaya programma poluchit novoe okruzhenie. Napomnim, chto okruzhenie mozhno pro- chest' iz predopredelennoj peremennoj char **environ, libo iz tret'ego argumenta funk- cii main (sm. nachalo glavy), libo funkciej getenv(). A. Bogatyrev, 1992-95 - 224 - Si v UNIX Sistemnye vyzovy fork i exec ne skleeny v odin vyzov potomu, chto mezhdu fork i exec v processe-syne mogut proishodit' nekotorye dejstviya, narushayushchie simmetriyu processa-otca i porozhdennogo processa: ustanovka reakcij na signaly, perenapravlenie vvoda/vyvoda, i.t.p. Smotri primer "interpretator komand" v prilozhenii. V MS DOS, ne imeyushchej parallel'nyh processov, vyzovy fork, exec i wait skleeny v odin vyzov spawn. Zato pri etom prihoditsya delat' perenapravleniya vvoda-vyvoda v porozhdayushchem processe pered spawn, a posle nego - vosstanavlivat' vse kak bylo. 6.5.4. Zavershit' process mozhno sistemnym vyzovom void exit( unsigned char retcode ); Iz etogo vyzova ne byvaet vozvrata. Process zavershaetsya: segmenty stack, data, text, user unichtozhayutsya (pri etom vse otkrytye processom fajly zakryvayutsya); pamyat', koto- ruyu oni zanimali, schitaetsya svobodnoj i v nee mozhet byt' pomeshchen drugoj process. Prichina smerti otmechaetsya v pasporte processa - v strukture proc v tablice processov vnutri yadra. No pasport eshche ne unichtozhaetsya! |to sostoyanie processa nazyvaetsya "zombi" - zhivoj mertvec. V pasport processa zanositsya kod otveta retcode. |tot kod mozhet byt' prochitan processom-roditelem (tem, kto sozdal etot process vyzovom fork). Prinyato, chto kod 0 oznachaet uspeshnoe zavershenie processa, a lyuboe polozhitel'noe znachenie 1..255 oznachaet neudachnoe zavershenie s takim kodom oshibki. Kody oshibok zaranee ne predopredeleny: eto lichnoe delo processov otca i syna - ustanovit' mezhdu soboj kakie-to soglasheniya po etomu povodu. V staryh programmah inogda pisalos' exit(-1); |to nekorrektno - kod otveta dolzhen byt' neotricatelen; kod -1 prevrashchaetsya v kod 255. CHasto ispol'zuetsya konstrukciya exit(errno); Programma mozhet zavershit'sya ne tol'ko yavno vyzyvaya exit, no i eshche dvumya sposo- bami: - esli proishodit vozvrat upravleniya iz funkcii main(), t.e. ona konchilas' - to vyzov exit() delaetsya neyavno, no s nepredskazuemym znacheniem retcode; - process mozhet byt' ubit signalom. V etom sluchae on ne vydaet nikakogo koda otveta v process-roditel', a vydaet priznak "process ubit". 6.5.5. V dejstvitel'nosti exit() - eto eshche ne sam sistemnyj vyzov zaversheniya, a standartnaya funkciya. Sam sistemnyj vyzov nazyvaetsya _exit(). My mozhem pereoprede- lit' funkciyu exit() tak, chtoby po okonchanii programmy proishodili nekotorye dejstviya: void exit(unsigned code){ /* Dobavlennyj mnoj dopolnitel'nyj operator: */ printf("Zakonchit' rabotu, " "kod otveta=%u\n", code); /* Standartnye operatory: */ _cleanup(); /* zakryt' vse otkrytye fajly. * |to standartnaya funkciya |= */ _exit(code); /* sobstvenno sisvyzov */ } int f(){ return 17; } void main(){ printf("aaaa\n"); printf("bbbb\n"); f(); /* potom otkommentirujte eto: exit(77); */ } Zdes' funkciya exit vyzyvaetsya neyavno po okonchanii main, ee podstavlyaet v programmu kompilyator. Delo v tom, chto pri zapuske programmy exec-om, pervym nachinaet vypol- nyat'sya kod tak nazyvaemogo "startera", podkleennogo pri sborke programmy iz fajla /lib/crt0.o. On vyglyadit primerno tak (v dejstvitel'nosti on napisan na assemblere): ... // vychislit' argc, nastroit' nekotorye parametry. main(argc, argv, envp); exit(); A. Bogatyrev, 1992-95 - 225 - Si v UNIX ili tak (vzyato iz proekta GNU|-|-): int errno = 0; char **environ; _start(int argc, int arga) { /* OS and Compiler dependent!!!! */ char **argv = (char **) &arga; char **envp = environ = argv + argc + 1; /* ... vozmozhno eshche kakie-to inicializacii, * napodobie setlocale( LC_ALL, "" ); v SCO UNIX */ exit (main(argc, argv, envp)); } Gde dolzhno byt' int main(int argc, char *argv[], char *envp[]){ ... return 0; /* vmesto exit(0); */ } Adres funkcii _start() pomechaetsya v odnom iz polej zagolovka fajla formata a.out kak adres, na kotoryj sistema dolzhna peredat' upravlenie posle zagruzki programmy v pamyat' (tochka vhoda). Kakoj kod otveta popadet v exit() v etih primerah (esli otsutstvuet yavnyj vyzov exit ili return) - nepredskazuemo. Na IBM PC v vyshenapisannom primere etot kod raven 17, to est' znacheniyu, vozvrashchennomu poslednej vyzyvavshejsya funkciej. Odnako eto ne kakoe-to special'noe soglashenie, a sluchajnyj effekt (tak uzh ustroen kod, sozdavaemyj etim kompilyatorom). 6.5.6. Process-otec mozhet dozhdat'sya okonchaniya svoego potomka. |to delaetsya sistem- nym vyzovom wait i nuzhno po sleduyushchej prichine: pust' otec - eto interpretator komand. Esli on zapustil process i prodolzhil svoyu rabotu, to oba processa budut predprinimat' popytki chitat' vvod s klaviatury terminala - interpretator zhdet komand, a zapushchennaya programma zhdet dannyh. Komu iz nih budet postupat' nabiraemyj nami tekst - nepreds- kazuemo! Vyvod: interpretator komand dolzhen "zasnut'" na to vremya, poka rabotaet porozhdennyj im process: int pid; unsigned short status; ... if((pid = fork()) == 0 ){ /* porozhdennyj process */ ... // perenapravleniya vvoda-vyvoda. ... // nastrojka signalov. exec(....); perror("exec ne udalsya"); exit(1); } /* inache eto porodivshij process */ while((pid = wait(&status)) > 0 ) printf("Okonchilsya syn pid=%d s kodom %d\n", pid, status >> 8); printf( "Bol'she net synovej\n"); ____________________ |= _cleanup() zakryvaet fajly, otkrytye fopen()om, "vytryahaya" pri etom dannye, na- koplennye v buferah, v fajl. Pri avarijnom zavershenii programmy fajly vse ravno zak- ryvayutsya, no uzhe ne yavno, a operacionnoj sistemoj (v vyzove _exit). Pri etom soder- zhimoe nedosbroshennyh buferov budet uteryano. ____________________ |-|- GNU - programmy, rasprostranyaemye v ishodnyh tekstah iz Free Software Founda- A. Bogatyrev, 1992-95 - 226 - Si v UNIX wait priostanavlivaet|- vypolnenie vyzvavshego processa do momenta okonchaniya lyubogo iz porozhdennyh im processov (ved' mozhno bylo zapustit' i neskol'kih synovej!). Kak tol'ko kakoj-to potomok okonchitsya - wait prosnetsya i vydast nomer (pid) etogo potomka. Kogda nikogo iz zhivyh "synovej" ne ostalos' - on vydast (-1). YAsno, chto processy mogut okanchivat'sya ne v tom poryadke, v kotorom ih porozhdali. V peremennuyu status zanositsya v special'nom vide kod otveta okonchivshegosya processa, libo nomer signala, kotorym on byl ubit. #include <sys/types.h> #include <sys/wait.h> ... int status, pid; ... while((pid = wait(&status)) > 0){ if( WIFEXITED(status)){ printf( "Process %d umer s kodom %d\n", pid, WEXITSTATUS(status)); } else if( WIFSIGNALED(status)){ printf( "Process %d ubit signalom %d\n", pid, WTERMSIG(status)); if(WCOREDUMP(status)) printf( "Obrazovalsya core\n" ); /* core - obraz pamyati processa dlya otladchika adb */ } else if( WIFSTOPPED(status)){ printf( "Process %d ostanovlen signalom %d\n", pid, WSTOPSIG(status)); } else if( WIFCONTINUED(status)){ printf( "Process %d prodolzhen\n", pid); } } ... Esli kod otveta nas ne interesuet, my mozhem pisat' wait(NULL). Esli u nashego processa ne bylo ili bol'she net zhivyh synovej - vyzov wait nichego ne zhdet, a vozvrashchaet znachenie (-1). V napisannom primere cikl while pozvolyaet dozh- dat'sya okonchaniya vseh potomkov. V tot moment, kogda process-otec poluchaet informaciyu o prichine smerti potomka, pasport umershego processa nakonec vycherkivaetsya iz tablicy processov i mozhet byt' pereispol'zovan novym processom. Do togo, on hranitsya v tablice processov v sostoya- nii "zombie" - "zhivoj mertvec". Tol'ko dlya togo, chtoby kto-nibud' mog uzat' status ego zaversheniya. Esli process-otec zavershilsya ran'she svoih synovej, to kto zhe sdelaet wait i vycherknet pasport? |to sdelaet process nomer 1: /etc/init. Esli otec umer ran'she processov-synovej, to sistema zastavlyaet process nomer 1 "usynovit'" eti processy. init obychno nahoditsya v cikle, soderzhashchem v nachale vyzov wait(), to est' ozhidaet ____________________ tion (FSF). Sredi nih - C++ kompilyator g++ i redaktor emacs. Smysl slov GNU - "gen- erally not UNIX" - proekt byl osnovan kak protivodejstvie nachavshejsya kommercializacii UNIX i zakrytiyu ego ishodnyh tekstov. "Sdelat' kak v UNIX, no luchshe". |- "ZHivoj" process mozhet prebyvat' v odnom iz neskol'kih sostoyanij: process ozhidaet nastupleniya kakogo-to sobytiya ("spit"), pri etom emu ne vydelyaetsya vremya processora, t.k. on ne gotov k vypolneniyu; process gotov k vypolneniyu i stoit v ocheredi k proces- soru (poskol'ku processor vypolnyaet drugoj process); process gotov i vypolnyaetsya pro- cessorom v dannyj moment. Poslednee sostoyanie mozhet proishodit' v dvuh rezhimah - pol'zovatel'skom (vypolnyayutsya komandy segmenta text) i sistemnom (processom byl izdan sistemnyj vyzov, i sejchas vypolnyaetsya funkciya v yadre). Ozhidanie sobytiya byvaet tol'ko v sistemnoj faze - vnutri sistemnogo vyzova (t.e. eto "sinhronnoe" ozhidanie). Neak- tivnye processy ("spyashchie" ili zhdushchie resursa processora) mogut byt' vremenno otkachany na disk. A. Bogatyrev, 1992-95 - 227 - Si v UNIX okonchaniya lyubogo iz svoih synovej (a oni u nego vsegda est', o chem my pogovorim pod- robnee chut' pogodya). Takim obrazom init zanimaetsya chistkoj tablicy processov, hotya eto ne edinstvennaya ego funkciya. Vot shema, poyasnyayushchaya zhiznennyj cikl lyubogo processa: |pid=719,csh | if(!fork())------->--------* pid=723,csh | | zagruzit' wait(&status) exec("a.out",...) <-- a.out : main(...){ s diska : | :pid=719,csh | pid=723,a.out spit(zhdet) rabotaet : | : exit(status) umer : } prosnulsya <---prosnis'!--RIP | |pid=719,csh Zamet'te, chto nomer porozhdennogo processa ne obyazan byt' sleduyushchim za nomerom rodi- telya, a tol'ko bol'she nego. |to svyazano s tem, chto drugie processy mogli sozdat' v sisteme novye processy do togo, kak nash process izdal svoj vyzov fork. 6.5.7. Krome togo, wait pozvolyaet otslezhivat' ostanovku processa. Process mozhet byt' priostanovlen pri pomoshchi posylki emu signalov SIGSTOP, SIGTTIN, SIGTTOU, SIGTSTP. Poslednie tri signala posylaet pri opredelennyh obstoyatel'stvah drajver terminala, k primeru SIGTSTP - pri nazhatii klavishi CTRL/Z. Prodolzhaetsya process posylkoj emu signala SIGCONT. V dannom kontekste, odnako, nas interesuyut ne sami eti signaly, a drugaya shema manipulyacii s otslezhivaniem statusa porozhdennyh processov. Esli ukazano yavno, sis- tema mozhet posylat' processu-roditelyu signal SIGCLD v moment izmeneniya statusa lyubogo iz ego potomkov. |to pozvolit processu-roditelyu nemedlenno sdelat' wait i nemedlenno otrazit' izmenenie sostoyanie processa-potomka v svoih vnutrennih spiskah. Dannaya shema programmiruetsya tak: void pchild(){ int pid, status; sighold(SIGCLD); while((pid = waitpid((pid_t) -1, &status, WNOHANG|WUNTRACED)) > 0){ dorecord: zapisat'_informaciyu_ob_izmeneniyah; } sigrelse(SIGCLD); /* Reset */ signal(SIGCLD, pchild); } ... main(){ ... /* Po signalu SIGCLD vyzyvat' funkciyu pchild */ signal(SIGCLD, pchild); ... glavnyj_cikl; } Sekciya s vyzovom waitpid (raznovidnost' vyzova wait), prikryta paroj funkcij sighold-sigrelse, zapreshchayushchih prihod signala SIGCLD vnutri etoj kriticheskoj sekcii. A. Bogatyrev, 1992-95 - 228 - Si v UNIX Sdelano eto vot dlya chego: esli process nachnet modificirovat' tablicy ili spiski v rajone metki dorecord:, a v etot moment pridet eshche odin signal, to funkciya pchild budet vyzvana rekursivno i tozhe popytaetsya modificirovat' tablicy i spiski, v kotoryh eshche ostalis' nezavershennymi perestanovki ssylok, elementov, schetchikov. |to privedet k razrusheniyu dannyh. Poetomu signaly dolzhny prihodit' posledovatel'no, i funkcii pchild vyzyvat'sya takzhe posledovatel'no, a ne rekursivno. Funkciya sighold otkladyvaet dostavku signala (esli on sluchitsya), a sigrelse - razreshaet dostavit' nakopivshiesya signaly (no esli ih prishlo neskol'ko odnogo tipa - vse oni dostavlyayutsya kak odin takoj signal. Otsyuda - cikl vokrug waitpid). Flag WNOHANG - oznachaet "ne zhdat' vnutri vyzova wait", esli ni odin iz potomkov ne izmenil svoego sostoyaniya; a prosto vernut' kod (-1)". |to pozvolyaet vyzyvat' pchild dazhe bez polucheniya signala: nichego ne proizojdet. Flag WUNTRACED - oznachaet "vydavat' informaciyu takzhe ob ostanovlennyh processah". 6.5.8. Kak uzhe bylo skazano, pri exec vse otkrytye fajly dostayutsya v nasledstvo novoj programme (v chastnosti, esli mezhdu fork i exec byli perenapravleny vyzovom dup2 standartnye vvod i vyvod, to oni ostanutsya perenapravlennymi i u novoj programmy). CHto delat', esli my ne hotim, chtoby nasledovalis' vse otkrytye fajly? (Hotya by potomu, chto bol'shinstvom iz nih novaya programma pol'zovat'sya ne budet - v osnovnom ona budet ispol'zovat' lish' fd 0, 1 i 2; a yachejki v tablice otkrytyh fajlov processa oni zanimayut). Vo-pervyh, nenuzhnye deskriptory mozhno yavno zakryt' close v promezhutke mezhdu fork-om i exec-om. Odnako ne vsegda my pomnim nomera deskriptorov dlya etoj operacii. Bolee radikal'noj meroj yavlyaetsya total'naya chistka: for(f = 3; f < NOFILE; f++) close(f); Est' bolee elegantnyj put'. Mozhno pometit' deskriptor fajla special'nym flagom, oznachayushchim, chto vo vremya vyzova exec etot deskriptor dolzhen byt' avtomaticheski zakryt (rezhim file-close-on-exec - fclex): #include <fcntl.h> int fd = open(.....); fcntl (fd, F_SETFD, 1); Otmenit' etot rezhim mozhno tak: fcntl (fd, F_SETFD, 0); Zdes' est' odna tonkost': etot flag ustanavlivaetsya ne dlya struktury file - "otkrytyj fajl", a neposredstvenno dlya deskriptora v tablice otkrytyh processom fajlov (massiv flagov: char u_pofile[NOFILE]). On ne sbrasyvaetsya pri zakrytii fajla, poetomu nas mozhet ozhidat' syurpriz: ... fcntl (fd, F_SETFD, 1); ... close(fd); ... int fd1 = open( ... ); Esli fd1 okazhetsya ravnym fd, to deskriptor fd1 budet pri exec-e zakryt, chego my yavno ne ozhidali! Poetomu pered close(fd) polezno bylo by otmenit' rezhim fclex. 6.5.9. Kazhdyj process imeet upravlyayushchij terminal (short *u_ttyp). On dostaetsya pro- cessu v nasledstvo ot roditelya (pri fork i exec) i obychno sovpadaet s terminalom, s na kotorom rabotaet dannyj pol'zovatel'. Kazhdyj process otnositsya k nekotoroj gruppe processov (int p_pgrp), kotoraya takzhe nasleduetsya. Mozhno poslat' signal vsem processam ukazannoj gruppy pgrp: kill( -pgrp, sig ); Vyzov kill( 0, sig ); posylaet signal sig vsem processam, ch'ya gruppa sovpadaet s gruppoj posylayushchego A. Bogatyrev, 1992-95 - 229 - Si v UNIX processa. Process mozhet uznat' svoyu gruppu: int pgrp = getpgrp(); a mozhet stat' "liderom" novoj gruppy. Vyzov setpgrp(); delaet sleduyushchie operacii: /* U processa bol'she net upravl. terminala: */ if(p_pgrp != p_pid) u_ttyp = NULL; /* Gruppa processa polagaetsya ravnoj ego id-u: */ p_pgrp = p_pid; /* new group */ V svoyu ochered', upravlyayushchij terminal tozhe imeet nekotoruyu gruppu (t_pgrp). |to znache- nie ustanavlivaetsya ravnym gruppe processa, pervym otkryvshego etot terminal: /* chast' procedury otkrytiya terminala */ if( p_pid == p_pgrp // lider gruppy && u_ttyp == NULL // eshche net upr.term. && t_pgrp == 0 ){ // u terminala net gruppy u_ttyp = &t_pgrp; t_pgrp = p_pgrp; } Takim processom obychno yavlyaetsya process registracii pol'zovatelya v sisteme (kotoryj sprashivaet u vas imya i parol'). Pri zakrytii terminala vsemi processami (chto byvaet pri vyhode pol'zovatelya iz sistemy) terminal teryaet gruppu: t_pgrp=0; Pri nazhatii na klaviature terminala nekotoryh klavish: c_cc[ VINTR ] obychno DEL ili CTRL/C c_cc[ VQUIT ] obychno CTRL/\ drajver terminala posylaet sootvetstvenno signaly SIGINT i SIGQUIT vsem processam gruppy terminala, t.e. kak by delaet kill( -t_pgrp, sig ); Imenno poetomu my mozhem prervat' process nazhatiem klavishi DEL. Poetomu, esli process sdelal setpgrp(), to signal s klaviatury emu poslat' nevozmozhno (t.k. on imeet svoj unikal'nyj nomer gruppy != gruppe terminala). Esli process eshche ne imeet upravlyayushchego terminala (ili uzhe ego ne imeet posle setpgrp), to on mozhet sdelat' lyuboj terminal (kotoryj on imeet pravo otkryt') uprav- lyayushchim dlya sebya. Pervyj zhe fajl-ustrojstvo, yavlyayushchijsya interfejsom drajvera termina- lov, kotoryj budet otkryt etim processom, stanet dlya nego upravlyayushchim terminalom. Tak process mozhet imet' kanaly 0, 1, 2 svyazannye s odnim terminalom, a preryvaniya polu- chat' s klaviatury drugogo (kotoryj on sdelal upravlyayushchim dlya sebya). Process registracii pol'zovatelya v sisteme - /etc/getty (nazvanie proishodit ot "get tty" - poluchit' terminal) - zapuskaetsya processom nomer 1 - /etc/init-om - na kazhdom iz terminalov, zaregistrirovannyh v sisteme, kogda - sistema tol'ko chto byla zapushchena; - libo kogda pol'zovatel' na kakom-to terminale vyshel iz sistemy (interpretator komand zavershilsya). V sil'nom uproshchenii getty mozhet byt' opisan tak: void main(ac, av) char *av[]; { int f; struct termio tmodes; for(f=0; f < NOFILE; f++) close(f); /* Otkaz ot upravlyayushchego terminala, * osnovanie novoj gruppy processov. */ setpgrp(); /* Pervonachal'noe yavnoe otkrytie terminala */ A. Bogatyrev, 1992-95 - 230 - Si v UNIX /* Pri etom terminal av[1] stanet upr. terminalom */ open( av[1], O_RDONLY ); /* fd = 0 */ open( av[1], O_RDWR ); /* fd = 1 */ f = open( av[1], O_RDWR ); /* fd = 2 */ // ... Schityvanie parametrov terminala iz fajla // /etc/gettydefs. Tip trebuemyh parametrov linii // zadaetsya metkoj, ukazyvaemoj v av[2]. // Zapolnenie struktury tmodes trebuemymi // znacheniyami ... i ustanovka mod terminala. ioctl (f, TCSETA, &tmodes); // ... zapros imeni i parolya ... chdir (domashnij_katalog_pol'zovatelya); execl ("/bin/csh", "-csh", NULL); /* Zapusk interpretatora komand. Gruppa processov, * upravl. terminal, deskriptory 0,1,2 nasleduyutsya. */ } Zdes' posledovatel'nye vyzovy open zanimayut posledovatel'nye yachejki v tablice otkry- tyh processom fajlov (poisk kazhdoj novoj nezanyatoj yachejki proizvoditsya s nachala tab- licy) - v itoge po deskriptoram 0,1,2 otkryvaetsya fajl-terminal. Posle etogo desk- riptory 0,1,2 nasleduyutsya vsemi potomkami interpretatora komand. Process init zapus- kaet po odnomu processu getty na kazhdyj terminal, kak by delaya /etc/getty /dev/tty01 m & /etc/getty /dev/tty02 m & ... i ozhidaet okonchaniya lyubogo iz nih. Posle vhoda pol'zovatelya v sistemu na kakom-to terminale, sootvetstvuyushchij getty prevrashchaetsya v interpretator komand (pid processa sohranyaetsya). Kak tol'ko kto-to iz nih umret - init perezapustit getty na sootvetst- vuyushchem terminale (vse oni - ego synov'ya, poetomu on znaet - na kakom imenno termi- nale). Processy mogut obmenivat'sya mezhdu soboj informaciej cherez fajly. Sushchestvuyut fajly s neobychnym povedeniem - tak nazyvaemye FIFO-fajly (first in, first out), vedu- shchie sebya podobno ocheredi. U nih ukazateli chteniya i zapisi razdeleny. Rabota s takim fajlom napominaet protalkivanie sharov cherez trubu - s odnogo konca my vtalkivaem dan- nye, s drugogo konca - vynimaem ih. Operaciya chteniya iz pustoj "truby" proiostanovit vyzov read (i izdavshij ego process) do teh por, poka kto-nibud' ne zapishet v FIFO- fajl kakie-nibud' dannye. Operaciya pozicionirovaniya ukazatelya - lseek() - neprime- nima k FIFO-fajlam. FIFO-fajl sozdaetsya sistemnym vyzovom #include <sys/types.h> #include <sys/stat.h> mknod( imyaFajla, S_IFIFO | 0666, 0 ); gde 0666 - kody dostupa k fajlu. Pri pomoshchi FIFO-fajla mogut obshchat'sya dazhe nerodst- vennye processy. Raznovidnost'yu FIFO-fajla yavlyaetsya bezymyannyj FIFO-fajl, prednaznachennyj dlya obmena informaciej mezhdu processom-otcom i processom-synom. Takoj fajl - kanal svyazi kak raz i nazyvaetsya terminom "truba" ili pipe. On sozdaetsya vyzovom pipe: int conn[2]; pipe(conn); Esli by fajl-truba imel imya PIPEFILE, to vyzov pipe mozhno bylo by opisat' kak A. Bogatyrev, 1992-95 - 231 - Si v UNIX mknod("PIPEFILE", S_IFIFO | 0600, 0); conn[0] = open("PIPEFILE", O_RDONLY); conn[1] = open("PIPEFILE", O_WRONLY); unlink("PIPEFILE"); Pri vyzove fork kazhdomu iz dvuh processov dostanetsya v nasledstvo para deskriptorov: pipe(conn); fork(); conn[0]----<---- ----<-----conn[1] FIFO conn[1]---->---- ---->-----conn[0] process A process B Pust' process A budet posylat' informaciyu v process B. Togda process A sdelaet: close(conn[0]); // t.k. ne sobiraetsya nichego chitat' write(conn[1], ... ); a process B close(conn[1]); // t.k. ne sobiraetsya nichego pisat' read (conn[0], ... ); Poluchaem v itoge: conn[1]---->----FIFO---->-----conn[0] process A process B Obychno postupayut eshche bolee elegantno, perenapravlyaya standartnyj vyvod A v kanal conn[1] dup2 (conn[1], 1); close(conn[1]); write(1, ... ); /* ili printf */ a standartnyj vvod B - iz kanala conn[0] dup2(conn[0], 0); close(conn[0]); read(0, ... ); /* ili gets */ |to sootvetstvuet konstrukcii $ A | B zapisannoj na yazyke SiSHell. Fajl, vydelyaemyj pod pipe, imeet ogranichennyj razmer (i poetomu obychno celikom osedaet v buferah v pamyati mashiny). Kak tol'ko on zapolnen celikom - process, pishu- shchij v trubu vyzovom write, priostanavlivaetsya do poyavleniya svobodnogo mesta v trube. |to mozhet privesti k vozniknoveniyu tupikovoj situacii, esli pisat' programmu neakku- ratno. Pust' process A yavlyaetsya synom processa B, i pust' process B izdaet vyzov wait, ne zakryv kanal conn[0]. Process zhe A ochen' mnogo pishet v trubu conn[1]. My poluchaem situaciyu, kogda oba processa spyat: A potomu chto truba perepolnena, a process B nichego iz nee ne chitaet, tak kak zhdet okonchaniya A; B potomu chto process-syn A ne okonchilsya, a on ne mozhet okonchit'sya poka ne dopishet svoe soobshchenie. Resheniem sluzhit zapret processu B delat' vyzov wait do teh por, poka on ne prochitaet VSYU informaciyu iz truby (ne poluchit EOF). Tol'ko sdelav posle etogo close(conn[0]); A. Bogatyrev, 1992-95 - 232 - Si v UNIX process B imeet pravo sdelat' wait. Esli process B zakroet svoyu storonu truby close(conn[0]) prezhde, chem process A zakonchit zapis' v nee, to pri vyzove write v processe A, sistema prishlet processu A signal SIGPIPE - "zapis' v kanal, iz kotorogo nikto ne chitaet". 6.6.1. Otkrytie FIFO fajla privedet k blokirovaniyu processa ("zasypaniyu"), esli v bufere FIFO fajla pusto. Process zasnet vnutri vyzova open do teh por, poka v bufere chto-nibud' ne poyavitsya. CHtoby izbezhat' takoj situacii, a, naprimer, sdelat' chto-nibud' inoe poleznoe v eto vremya, nam nado bylo by oprosit' fajl na predmet togo - mozhno li ego otkryt'? |to delaetsya pri pomoshchi flaga O_NDELAY u vyzova open. int fd = open(filename, O_RDONLY|O_NDELAY); Esli open vedet k blokirovke processa vnutri vyzova, vmesto etogo budet vozvrashcheno znachenie (-1). Esli zhe fajl mozhet byt' nemedlenno otkryt - vozvrashchaetsya normal'nyj deskriptor so znacheniem >=0, i fajl otkryt. O_NDELAY yavlyaetsya zavisimym ot semantiki togo fajla, kotoryj my otkryvaem. K primeru, mozhno ispol'zovat' ego s fajlami ustrojstv, naprimer imenami, vedushchimi k posledovatel'nym portam. |ti fajly ustrojstv (porty) obladayut tem svojstvom, chto odnovremenno ih mozhet otkryt' tol'ko odin process (tak ustroena realizaciya funkcii open vnutri drajvera etih ustrojstv). Poetomu, esli odin process uzhe rabotaet s por- tom, a v eto vremya vtoroj pytaetsya ego zhe otkryt', vtoroj "zasnet" vnutri open, i budet dozhidat'sya osvobozhdeniya porta close pervym processom. CHtoby ne zhdat' - sleduet otkryvat' port s flagom O_NDELAY. #include <stdio.h> #include <fcntl.h> /* Ubrat' bol'she ne nuzhnyj O_NDELAY */ void nondelay(int fd){ fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) & ~O_NDELAY); } int main(int ac, char *av[]){ int fd; char *port = ac > 1 ? "/dev/term/a" : "/dev/cua/a"; retry: if((fd = open(port, O_RDWR|O_NDELAY)) < 0){ perror(port); sleep(10); goto retry; } printf("Port %s otkryt.\n", port); nondelay(fd); printf("Rabota s portom, vyzovi etu programmu eshche raz!\n"); sleep(60); printf("Vse.\n"); return 0; } Vot protokol: A. Bogatyrev, 1992-95 - 233 - Si v UNIX su# a.out & a.out xxx [1] 22202 Port /dev/term/a otkryt. Rabota s portom, vyzovi etu programmu eshche raz! /dev/cua/a: Device busy /dev/cua/a: Device busy /dev/cua/a: Device busy /dev/cua/a: Device busy /dev/cua/a: Device busy /dev/cua/a: Device busy Vse. Port /dev/cua/a otkryt. Rabota s portom, vyzovi etu programmu eshche raz! su# Teper' pogovorim pro nelokal'nyj perehod. Standartnaya funkciya setjmp pozvolyaet ustanovit' v programme "kontrol'nuyu tochku"|-, a funkciya longjmp osushchestvlyaet pryzhok v etu tochku, vypolnyaya za odin raz vyhod srazu iz neskol'kih vyzvannyh funkcij (esli nado)|=. |ti funkcii ne yavlyayutsya sistemnymi vyzovami, no poskol'ku oni realizuyutsya mashinno-zavisimym obrazom, a ispol'zuyutsya chashche vsego kak reakciya na nekotoryj signal, rech' o nih idet v etom razdele. Vot kak, naprimer, vyglyadit restart programmy po preryvaniyu s klaviatury: #include <signal.h> #include <setjmp.h> jmp_buf jmp; /* kontrol'naya tochka */ /* prygnut' v kontrol'nuyu tochku */ void onintr(nsig){ longjmp(jmp, nsig); } main(){ int n; n = setjmp(jmp); /* ustanovit' kontrol'nuyu tochku */ if( n ) printf( "Restart posle signala %d\n", n); signal (SIGINT, onintr); /* reakciya na signal */ printf("Nachali\n"); ... } setjmp vozvrashchaet 0 pri zapominanii kontrol'noj tochki. Pri pryzhke v kontrol'nuyu tochku pri pomoshchi longjmp, my okazyvaemsya snova v funkcii setjmp, i eta funkciya vozv- rashchaet nam znachenie vtorogo argumenta longjmp, v etom primere - nsig. Pryzhok v kontrol'nuyu tochku ochen' udobno ispol'zovat' v algoritmah perebora s vozvratom (backtracking): libo - esli otvet najden - pryzhok na pechat' otveta, libo - esli vetv' perebora zashla v tupik - pryzhok v tochku vetvleniya i vybor drugoj al'terna- tivy. Pri etom mozhno delat' pryzhki i v rekursivnyh vyzovah odnoj i toj zhe funkcii: s bolee vysokogo urovnya rekursii v vyzov bolee nizkogo urovnya (v etom sluchae jmp_buf luchshe delat' avtomaticheskoj peremennoj - svoej dlya kazhdogo urovnya vyzova funkcii). ____________________ |- V nekotorom bufere zapominaetsya tekushchee sostoyanie processa: polozhenie vershiny steka vyzovov funkcij (stack pointer); sostoyanie vseh registrov processora, vklyuchaya registr adresa tekushchej mashinnoj komandy (instruction pointer). |= |to dostigaetsya vosstanovleniem sostoyaniya processa iz bufera. Izmeneniya, prois- shedshie za vremya mezhdu setjmp i longjmp v staticheskih dannyh ne otmenyayutsya (t.k. oni ne sohranyalis'). A. Bogatyrev, 1992-95 - 234 - Si v UNIX 6.7.1. Perepishite sleduyushchij algoritm pri pomoshchi longjmp. #define FOUND 1 /* otvet najden */ #define NOTFOUND 0 /* otvet ne najden */ int value; /* rezul'tat */ main(){ int i; for(i=2; i < 10; i++){ printf( "probuem i=%d\n", i); if( test1(i) == FOUND ){ printf("otvet %d\n", value); break; } } } test1(i){ int j; for(j=1; j < 10 ; j++ ){ printf( "probuem j=%d\n", j); if( test2(i,j) == FOUND ) return FOUND; /* "skvoznoj" return */ } return NOTFOUND; } test2(i, j){ printf( "probuem(%d,%d)\n", i, j); if( i * j == 21 ){ printf( " Godyatsya (%d,%d)\n", i,j); value = j; return FOUND; } return NOTFOUND; } Vot otvet, ispol'zuyushchij nelokal'nyj perehod vmesto cepochki return-ov: #include <setjmp.h> jmp_buf jmp; main(){ int i; if( i = setjmp(jmp)) /* posle pryzhka */ printf("Otvet %d\n", --i); else /* ustanovka tochki */ for(i=2; i < 10; i++) printf( "probuem i=%d\n", i), test1(i); } test1(i){ int j; for(j=1; j < 10 ; j++ ) printf( "probuem j=%d\n", j), test2(i,j); } test2(i, j){ printf( "probuem(%d,%d)\n", i, j); if( i * j == 21 ){ printf( " Godyatsya (%d,%d)\n", i,j); longjmp(jmp, j + 1); } } Obratite vnimanie, chto pri vozvrate otveta cherez vtoroj argument longjmp my pribavili 1, a pri pechati otveta my etu edinicu otnyali. |to sdelano na sluchaj otveta j==0, chtoby funkciya setjmp ne vernula by v etom sluchae znachenie 0 (priznak ustanovki kont- rol'noj tochki). 6.7.2. V chem oshibka? #include <setjmp.h> A. Bogatyrev, 1992-95 - 235 - Si v UNIX jmp_buf jmp; main(){ g(); longjmp(jmp,1); } g(){ printf("Vyzvana g\n"); f(); printf("Vyhozhu iz g\n"); } f(){ static n; printf( "Vyzvana f\n"); setjmp(jmp); printf( "Vyhozhu iz f %d-yj raz\n", ++n); } Otvet: longjmp delaet pryzhok v funkciyu f(), iz kotoroj uzhe proizoshel vozvrat upravle- niya. Pri perehode v telo funkcii v obhod ee zagolovka ne vypolnyayutsya mashinnye komandy "prologa" funkcii - funkciya ostaetsya "neaktivirovannoj". Pri vozvrate iz vyzvannoj takim "nelegal'nym" putem funkcii voznikaet oshibka, i programma padaet. Moral': v funkciyu, kotoraya NIKEM NE VYZVANA, nel'zya peredavat' upravlenie. Obratnyj pryzhok - iz f() v main() - byl by zakonen, poskol'ku funkciya main() yavlyaetsya aktivnoj, kogda upravlenie nahoditsya v tele funkcii f(). T.e. mozhno "prygat'" iz vyzvannoj funkcii v vyzyvayushchuyu: iz f() v main() ili v g(); i iz g() v main(); -- -- | f | stek prygat' | g | vyzovov sverhu vniz | main | funkcij mozhno - eto sootvetstvuet ---------- vykidyvaniyu neskol'kih verhnih sloev steka no nel'zya naoborot: iz main() v g() ili f(); a takzhe iz g() v f(). Mozhno takzhe sovershat' pryzhok v predelah odnoj i toj zhe funkcii: f(){ ... A: setjmp(jmp); ... longjmp(jmp, ...); ... /* eto kak by goto A; */ } UNIX - mnogopol'zovatel'skaya sistema. |to znachit, chto odnovremenno na raznyh terminalah, podklyuchennyh k mashine, mogut rabotat' raznye pol'zovateli (a mozhet i odin na neskol'kih terminalah). Na kazhdom terminale rabotaet svoj interpretator komand, yavlyayushchijsya potomkom processa /etc/init. 6.8.1. Teper' - pro funkcii, pozvolyayushchie uznat' nekotorye dannye pro lyubogo pol'zo- vatelya sistemy. Kazhdyj pol'zovatel' v UNIX imeet unikal'nyj nomer: identifikator pol'zovatelya (user id), a takzhe unikal'noe imya: registracionnoe imya, kotoroe on nabi- raet dlya vhoda v sistemu. Vsya informaciya o pol'zovatelyah hranitsya v fajle /etc/passwd. Sushchestvuyut funkcii, pozvolyayushchie po nomeru pol'zovatelya uznat' registra- cionnoe imya i naoborot, a zaodno poluchit' eshche nekotoruyu informaciyu iz passwd: A. Bogatyrev, 1992-95 - 236 - Si v UNIX #include <stdio.h> #include <pwd.h> struct passwd *p; int uid; /* nomer */ char *uname; /* reg. imya */ uid = getuid(); p = getpwuid( uid ); ... p = getpwnam( uname ); |ti funkcii vozvrashchayut ukazateli na staticheskie struktury, skrytye vnutri etih funk- cij. Struktury eti imeyut polya: p->pw_uid identif. pol'zovatelya (int uid); p->pw_gid identif. gruppy pol'zovatelya; i ryad polej tipa char[] p->pw_name registracionnoe imya pol'zovatelya (uname); p->pw_dir polnoe imya domashnego kataloga (kataloga, stanovyashchegosya tekushchim pri vhode v sistemu); p->pw_shell interpretator komand (esli "", to imeetsya v vidu /bin/sh); p->pw_comment proizvol'naya uchetnaya informaciya (ne ispol'zuetsya); p->pw_gecos proizvol'naya uchetnaya informaciya (obychno FIO); p->pw_passwd zashifrovannyj parol' dlya vhoda v sistemu. Istinnyj parol' nigde ne hranitsya vovse! Funkcii vozvrashchayut znachenie p==NULL, esli ukazannyj pol'zovatel' ne sushchestvuet (nap- rimer, esli zadan nevernyj uid). uid hozyaina dannogo processa mozhno uznat' vyzovom getuid, a uid vladel'ca fajla - iz polya st_uid struktury, zapolnyaemoj sistemnym vyzo- vom stat (a identifikator gruppy vladel'ca - iz polya st_gid). Zadanie: modificirujte nash analog programmy ls, chtoby on vydaval v tekstovom vide imya vladel'ca kazhdogo fajla v kataloge. 6.8.2. Vladelec fajla mozhet izmenit' svoemu fajlu identifikatory vladel'ca i gruppy vyzovom chown(char *imyaFajla, int uid, int gid); t.e. "podarit'" fajl drugomu pol'zovatelyu. Zabrat' chuzhoj fajl sebe nevozmozhno. Pri etoj operacii bity S_ISUID i S_ISGID v kodah dostupa k fajlu (sm. nizhe) sbrasyvayutsya, poetomu sozdat' "Troyanskogo konya" i, sdelav ego hozyainom superpol'zovatelya, poluchit' neogranichennye privelegii - ne udastsya! 6.8.3. Kazhdyj fajl imeet svoego vladel'ca (pole di_uid v I-uzle na diske ili pole i_uid v kopii I-uzla v pamyati yadra|-). Kazhdyj process takzhe imeet svoego vladel'ca (polya u_uid i u_ruid v u-area). Kak my vidim, process imeet dva parametra, oboznacha- yushchie vladel'ca. Pole ruid nazyvaetsya "real'nym identifikatorom" pol'zovatelya, a uid - "effektivnym identifikatorom". Pri vyzove exec() zamenyaetsya programma, vypolnyaemaya dannym processom: ____________________ |- Pri otkrytii fajla i voobshche pri lyuboj operacii s fajlom, v tablicah yadra zavo- ditsya kopiya I-uzla (dlya uskoreniya dostupa, chtoby postoyanno ne obrashchat'sya k disku). Esli I-uzel v pamyati budet izmenen, to pri zakrytii fajla (a takzhe periodicheski cherez nekotorye promezhutki vremeni) eta kopiya budet zapisana obratno na disk. Struktura I-uzla v pamyati - struct inode - opisana v fajle <sys/inode.h>, a na diske - struct dinode - v fajle <sys/ino.h>. A. Bogatyrev, 1992-95 - 237 - Si v UNIX staraya programma exec novaya programma ruid -->----------------->---> ruid uid -->--------*-------->---> uid (new) | vypolnyaemyj fajl i_uid (st_uid) Kak vidno iz etoj shemy, real'nyj identifikator hozyaina processa nasleduetsya. |ffek- tivnyj identifikator obychno takzhe nasleduetsya, za isklyucheniem odnogo sluchaya: esli v kodah dostupa fajla (i_mode) vystavlen bit S_ISUID (set-uid bit), to znachenie polya u_uid v novom processe stanet ravno znacheniyu i_uid fajla s programmoj: /* ... vo vremya exec ... */ p_suid = u_uid; /* spasti */ if( i_mode & S_ISUID ) u_uid = i_uid; if( i_mode & S_ISGID ) u_gid = i_gid; t.e. effektivnym vladel'cem processa stanet vladelec fajla. Zdes' gid - eto identi- fikatory gruppy vladel'ca (kotorye tozhe est' i u fajla i u processa, prichem u pro- cessa - real'nyj i effektivnyj). Zachem vse eto nado? Vo-pervyh zatem, chto PRAVA processa na dostup k kakomu-libo fajlu proveryayutsya imenno dlya effektivnogo vladel'ca processa. T.e. naprimer, esli fajl imeet kody dostupa mode = i_mode & 0777; /* rwx rwx rwx */ i vladel'ca i_uid, to process, pytayushchijsya otkryt' etot fajl, budet "proekzamenovan" v takom poryadke: if( u_uid == 0 ) /* super user */ to dostup razreshen; else if( u_uid == i_uid ) proverit' kody (mode & 0700); else if( u_gid == i_gid ) proverit' kody (mode & 0070); else proverit' kody (mode & 0007); Process mozhet uznat' svoi parametry: unsigned short uid = geteuid(); /* u_uid */ unsigned short ruid = getuid(); /* u_ruid */ unsigned short gid = getegid(); /* u_gid */ unsigned short rgid = getuid(); /* u_rgid */ a takzhe ustanovit' ih: setuid(newuid); setgid(newgid); Rassmotrim vyzov setuid. On rabotaet tak (u_uid - otnositsya k processu, izdavshemu etot vyzov): if( u_uid == 0 /* superuser */ ) u_uid = u_ruid = p_suid = newuid; else if( u_ruid == newuid || p_suid == newuid ) u_uid = newuid; else neudacha; Pole p_suid pozvolyaet set-uid-noj programme vosstanovit' effektivnogo vladel'ca, kotoryj byl u nee do exec-a. A. Bogatyrev, 1992-95 - 238 - Si v UNIX Vo-vtoryh, vse eto nado dlya sleduyushchego sluchaya: pust' u menya est' nekotoryj fajl BASE s hranyashchimisya v nem sekretnymi svedeniyami. YA yavlyayus' vladel'cem etogo fajla i ustanavlivayu emu kody dostupa 0600 (chtenie i zapis' razresheny tol'ko mne). Tem ne menee, ya hochu dat' drugim pol'zovatelyam vozmozhnost' rabotat' s etim fajlom, odnako kontroliruya ih deyatel'nost'. Dlya etogo ya pishu programmu, kotoraya vypolnyaet nekotorye dejstviya s fajlom BASE, pri etom proveryaya zakonnost' etih dejstvij, t.e. pozvolyaya delat' ne vse chto popalo, a lish' to, chto ya v nej predusmotrel, i pod zhestkim kontro- lem. Vladel'cem fajla PROG, v kotorom hranitsya eta programma, takzhe yavlyayus' ya, i ya zadayu etomu fajlu kody dostupa 0711 (rwx--x--x) - vsem mozhno vypolnyat' etu programmu. Vse li ya sdelal, chtoby pozvolit' drugim pol'zovat'sya bazoj BASE cherez programmu (i tol'ko nee) PROG? Net! Esli kto-to drugoj zapustit programmu PROG, to effektivnyj identifikator pro- cessa budet raven identifikatoru etogo drugogo pol'zovatelya, i programma ne smozhet otkryt' moj fajl BASE. CHtoby vse rabotalo, process, vypolnyayushchij programmu PROG, dol- zhen rabotat' kak by ot moego imeni. Dlya etogo ya dolzhen vyzovom chmod libo komandoj chmod u+s PROG dobavit' k kodam dostupa fajla PROG bit S_ISUID. Posle etogo, pri zapuske programmy PROG, ona budet poluchat' effektivnyj identi- fikator, ravnyj moemu identifikatoru, i takim obrazom smozhet otkryt' i rabotat' s fajlom BASE. Vyzov getuid pozvolyaet vyyasnit', kto vyzval moyu programmu (i zanesti eto v protokol, esli nado). Programmy takogo tipa - ne redkost' v UNIX, esli vladel'cem programmy (fajla ee soderzhashchego) yavlyaetsya superpol'zovatel'. V takom sluchae programma, imeyushchaya bit dos- tupa S_ISUID rabotaet ot imeni superpol'zovatelya i mozhet vypolnyat' nekotorye dejst- viya, zapreshchennye obychnym pol'zovatelyam. Pri etom programma vnutri sebya delaet vsyaches- kie proverki i periodicheski sprashivaet paroli, to est' pri rabote zashchishchaet sistemu ot durakov i prednamerennyh vreditelej. Prostejshim primerom sluzhit komanda ps, kotoraya schityvaet tablicu processov iz pamyati yadra i raspechatyvaet ee. Dostup k fizicheskoj pamyati mashiny proizvoditsya cherez fajl-psevdoustrojstvo /dev/mem, a k pamyati yadra - /dev/kmem. CHtenie i zapis' v nih pozvoleny tol'ko superpol'zovatelyu, poetomu prog- rammy "obshchego pol'zovaniya", obrashchayushchiesya k etim fajlam, dolzhny imet' bit set-uid. Otkuda zhe iznachal'no berutsya znacheniya uid i ruid (a takzhe gid i rgid) u pro- cessa? Oni berutsya iz processa registracii pol'zovatelya v sisteme: /etc/getty. |tot process zapuskaetsya na kazhdom terminale kak process, prinadlezhashchij superpol'zovatelyu (u_uid==0). Snachala on zaprashivaet imya i parol' pol'zovatelya: #include <stdio.h> /* cc -lc_s */ #include <pwd.h> #include <signal.h> struct passwd *p; char userName[80], *pass, *crpass; extern char *getpass(), *crypt(); ... /* Ne preryvat'sya po signalam s klaviatury */ signal (SIGINT, SIG_IGN); for(;;){ /* Zaprosit' imya pol'zovatelya: */ printf("Login: "); gets(userName); /* Zaprosit' parol' (bez eha): */ pass = getpass("Password: "); /* Proverit' imya: */ if(p = getpwnam(userName)){ /* est' takoj pol'zovatel' */ crpass = (p->pw_passwd[0]) ? /* esli est' parol' */ crypt(pass, p->pw_passwd) : pass; if( !strcmp( crpass, p->pw_passwd)) break; /* vernyj parol' */ } printf("Login incorrect.\a\n"); } signal (SIGINT, SIG_DFL); A. Bogatyrev, 1992-95 - 239 - Si v UNIX Zatem on vypolnyaet: // ... zapis' informacii o vhode pol'zovatelya v sistemu // v fajly /etc/utmp (kto rabotaet v sisteme sejchas) // i /etc/wtmp (spisok vseh vhodov v sistemu) ... setuid( p->pw_uid ); setgid( p->pw_gid ); chdir ( p->pw_dir ); /* GO HOME! */ // eti parametry budut unasledovany // interpretatorom komand. ... // nastrojka nekotoryh peremennyh okruzheniya envp: // HOME = p->pw_dir // SHELL = p->pw_shell // PATH = nechto po umolchaniyu, vrode :/bin:/usr/bin // LOGNAME (USER) = p->pw_name // TERM = schityvaetsya iz fajla // /etc/ttytype po imeni ustrojstva av[1] // Delaetsya eto kak-to podobno // char *envp[MAXENV], buffer[512]; int envc = 0; // ... // sprintf(buffer, "HOME=%s", p->pw_dir); // envp[envc++] = strdup(buffer); // ... // envp[envc] = NULL; ... // nastrojka kodov dostupa k terminalu. Imya ustrojstva // soderzhitsya v parametre av[1] funkcii main. chown (av[1], p->pw_uid, p->pw_gid); chmod (av[1], 0600 ); /* -rw------- */ // teper' dostup k dannomu terminalu imeyut tol'ko // voshedshij v sistemu pol'zovatel' i superpol'zovatel'. // V sluchae smerti interpretatora komand, // kotorym zamenitsya getty, process init sojdet // s sistemnogo vyzova ozhidaniya wait() i vypolnit // chown ( etot_terminal, 2 /*bin*/, 15 /*terminal*/ ); // chmod ( etot_terminal, 0600 ); // i, esli terminal chislitsya v fajle opisaniya linij // svyazi /etc/inittab kak aktivnyj (metka respawn), to // init perezapustit na etom_terminale novyj // process getty pri pomoshchi pary vyzovov fork() i exec(). ... // zapusk interpretatora komand: execle( *p->pw_shell ? p->pw_shell : "/bin/sh", "-", NULL, envp ); V rezul'tate on stanovitsya processom pol'zovatelya, voshedshego v sistemu. Takovym zhe posle exec-a, vypolnyaemogo getty, ostaetsya i interpretator komand p->pw_shell (obychno /bin/sh ili /bin/csh) i vse ego potomki. Na samom dele, v opisanii registracii pol'zovatelya pri vhode v sistemu, sozna- tel'no bylo dopushcheno uproshchenie. Delo v tom, chto vse to, chto my pripisali processu getty, v dejstvitel'nosti vypolnyaetsya dvumya programmami: /etc/getty i /bin/login. Snachala process getty zanimaetsya nastrojkoj parametrov linii svyazi (t.e. termi- nala) v sootvetstvii s ee opisaniem v fajle /etc/gettydefs. Zatem on zaprashivaet imya pol'zovatelya i zamenyaet sebya (pri pomoshchi sisvyzova exec) processom login, peredavaya emu v kachestve odnogo iz argumentov poluchennoe imya pol'zovatelya. Zatem login zaprashivaet parol', nastraivaet okruzhenie, i.t.p., to est' imenno on proizvodit vse operacii, privedennye vyshe na sheme. V konce koncov on zamenyaet sebya interpretatorom komand. Takoe razdelenie delaetsya, v chastnosti, dlya togo, chtoby schitannyj parol' v slu- chae opechatki ne hranilsya by v pamyati processa getty, a unichtozhalsya by pri ochistke A. Bogatyrev, 1992-95 - 240 - Si v UNIX pamyati zavershivshegosya processa login. Takim obrazom parol' v istinnom, nezashifrovan- nom vide hranitsya v sisteme minimal'noe vremya, chto zatrudnyaet ego podsmatrivanie sredstvami elektronnogo ili programmnogo shpionazha. Krome togo, eto pozvolyaet izme- nyat' sistemu proverki parolej ne izmenyaya programmu inicializacii terminala getty. Imya, pod kotorym pol'zovatel' voshel v sistemu na dannom terminale, mozhno uznat' vyzovom standartnoj funkcii char *getlogin(); |ta funkciya ne proveryaet uid processa, a prosto izvlekaet zapis' pro dannyj terminal iz fajla /etc/utmp. Nakonec otmetim, chto vladelec fajla ustanavlivaetsya pri sozdanii etogo fajla (vyzovami creat ili mknod), i polagaetsya ravnym effektivnomu identifikatoru sozdayu- shchego processa. di_uid = u_uid; di_gid = u_gid; 6.8.4. Napishite programmu, uznayushchuyu u sistemy i raspechatyvayushchuyu: nomer processa, nomer i imya svoego vladel'ca, nomer gruppy, nazvanie i tip terminala na kotorom ona rabotaet (iz peremennoj okruzheniya TERM). V bazah dannyh neredko vstrechaetsya situaciya odnovremennogo dostupa k odnim i tem zhe dannym. Dopustim, chto v nekotorom fajle hranyatsya dannye, kotorye mogut chitat'sya i zapisyvat'sya proizvol'nym chislom processov. - Dopustim, chto process A izmenyaet nekotoruyu oblast' fajla, v to vremya kak process B pytaetsya prochest' tu zhe oblast'. Itogom takogo sorevnovaniya mozhet byt' to, chto process B prochtet nevernye dannye. - Dopustim, chto process A izmenyaet nekotoruyu oblast' fajla, v to vremya kak process C takzhe izmenyaet tu zhe samuyu oblast'. V itoge eta oblast' mozhet soderzhat' nevernye dannye (chast' - ot processa A, chast' - ot C). YAsno, chto trebuetsya mehanizm sinhronizacii processov, pozvolyayushchij ne puskat' drugoj process (processy) chitat' i/ili zapisyvat' dannye v ukazannoj oblasti. Meha- nizmov sinhronizacii v UNIX sushchestvuet mnozhestvo: ot semaforov do blokirovok oblastej fajla. O poslednih my i budem tut govorit'. Prezhde vsego otmetim, chto blokirovki fajla nosyat v UNIX neobyazatel'nyj harakter. To est', programma ne ispol'zuyushchaya vyzovov sinhronizacii, budet imet' dostup k dannym bez kakih libo ogranichenij. Uvy. Takim obrazom, programmy, sobirayushchiesya korrektno pol'zovat'sya obshchimi dannymi, dolzhny vse ispol'zovat' - i pri tom odin i tot zhe - mehanizm sinhronizacii: zaklyuchit' mezhdu soboj "dzhentl'menskoe soglashenie". 6.9.1. Blokirovka ustanavlivaetsya pri pomoshchi vyzova flock_t lock; fcntl(fd, operation, &lock); Zdes' operation mozhet byt' odnim iz treh: F_SETLK Ustanavlivaet ili snimaet zamok, opisyvaemyj strukturoj lock. Struktura flock_t imeet takie polya: short l_type; short l_whence; off_t l_start; size_t l_len; long l_sysid; pid_t l_pid; l_type tip blokirovki: A. Bogatyrev, 1992-95 - 241 - Si v UNIX F_RDLCK - na chtenie; F_WRLCK - na zapis'; F_UNLCK - snyat' vse zamki. l_whence, l_start, l_len opisyvayut segment fajla, na kotoryj stavitsya zamok: ot tochki lseek(fd,l_start,l_whence); dlinoj l_len bajt. Zdes' l_whence mozhet byt': SEEK_SET, SEEK_CUR, SEEK_END. l_len ravnoe nulyu oznachaet "do konca fajla". Tak esli vse tri parametra ravny 0, to budet zablokirovan ves' fajl. F_SETLKW Ustanavlivaet ili snimaet zamok, opisyvaemyj strukturoj lock. Pri etom, esli zamok na oblast', peresekayushchuyusya s ukazannoj uzhe kem-to ustanovlen, to sperva dozhdat'sya snyatiya etogo zamka. Pytaemsya | Net Uzhe est' uzhe est' postavit' | chuzhih zamok zamok zamok na | zamkov na READ na WRITE -----------|--------------------------------------------------------------- READ | chitat' chitat' zhdat';zaperet';chitat' WRITE | zapisat' zhdat';zaperet';zapisat' zhdat';zaperet';zapisat' UNLOCK | otperet' otperet' otperet' - Esli kto-to chitaet segment fajla, to drugie tozhe mogut ego chitat' svobodno, ibo chtenie ne izmenyaet fajla. - Esli zhe kto-to zapisyvaet fajl - to vse ostal'nye dolzhny dozhdat'sya okonchaniya zapisi i razblokirovki. - Esli kto-to chitaet segment, a drugoj process sobralsya izmenit' (zapisat') etot segment, to etot drugoj process obyazan dozhdat'sya okonchaniya chteniya pervym. - V moment, oboznachennyj kak otperet' - budyatsya processy, zhdushchie razblokirovki, i rovno odin iz nih poluchaet dostup (mozhet ustanovit' svoyu blokirovku). Poryadok - kto iz nih budet pervym - voobshche govorya ne opredelen. F_GETLK Zaprashivaem vozmozhnost' ustanovit' zamok, opisannyj v lock. - Esli my mozhem ustanovit' takoj zamok (ne zaperto nikem), to v strukture lock pole l_type stanovitsya ravnym F_UNLCK i pole l_whence ravnym SEEK_SET. - Esli zamok uzhe kem-to ustanovlen (i vyzov F_SETLKW zablokiroval by nash process, privel by k ozhidaniyu), my poluchaem informaciyu o chuzhom zamke v strukturu lock. Pri etom v pole l_pid zanositsya identifikator processa, sozdavshego etot zamok, a v pole l_sysid - identifikator mashiny (poskol'ku blokirovka fajlov podderzhiva- etsya cherez setevye fajlovye sistemy). Zamki avtomaticheski snimayutsya pri zakrytii deskriptora fajla. Zamki ne nasledu- yutsya porozhdennym processom pri vyzove fork. #include <stdio.h> #include <sys/types.h> #include <fcntl.h> #include <unistd.h> #include <time.h> #include <signal.h> char DataFile [] = "data.xxx"; char info [] = "abcdefghijklmnopqrstuvwxyz"; #define OFFSET 5 #define SIZE 12 #define PAUSE 2 int trial = 1; int fd, pid; char buffer[120], myname[20]; void writeAccess(), readAccess(); A. Bogatyrev, 1992-95 - 242 - Si v UNIX void fcleanup(int nsig){ unlink(DataFile); printf("cleanup:%s\n", myname); if(nsig) exit(0); } int main(){ int i; fd = creat(DataFile, 0644); write(fd, info, strlen(info)); close(fd); signal(SIGINT, fcleanup); sprintf(myname, fork() ? "B-%06d" : "A-%06d", pid = getpid()); srand(time(NULL)+pid); printf("%s:started\n", myname); fd = open(DataFile, O_RDWR|O_EXCL); printf("%s:opened %s\n", myname, DataFile); for(i=0; i < 30; i++){ if(rand()%2) readAccess(); else writeAccess(); } close(fd); printf("%s:finished\n", myname); wait(NULL); fcleanup(0); return 0; } A. Bogatyrev, 1992-95 - 243 - Si v UNIX void writeAccess(){ flock_t lock; printf("Write:%s #%d\n", myname, trial); lock.l_type = F_WRLCK; lock.l_whence = SEEK_SET; lock.l_start = (off_t) OFFSET; lock.l_len = (size_t) SIZE; if(fcntl(fd, F_SETLKW, &lock) <0) perror("F_SETLKW"); printf("\twrite:%s locked\n", myname); sprintf(buffer, "%s #%02d", myname, trial); printf ("\twrite:%s \"%s\"\n", myname, buffer); lseek (fd, (off_t) OFFSET, SEEK_SET); write (fd, buffer, SIZE); sleep (PAUSE); lock.l_type = F_UNLCK; if(fcntl(fd, F_SETLKW, &lock) <0) perror("F_SETLKW"); printf("\twrite:%s unlocked\n", myname); trial++; } void readAccess(){ flock_t lock; printf("Read:%s #%d\n", myname, trial); lock.l_type = F_RDLCK; lock.l_whence = SEEK_SET; lock.l_start = (off_t) OFFSET; lock.l_len = (size_t) SIZE; if(fcntl(fd, F_SETLKW, &lock) <0) perror("F_SETLKW"); printf("\tread:%s locked\n", myname); lseek(fd, (off_t) OFFSET, SEEK_SET); read (fd, buffer, SIZE); printf("\tcontents:%s \"%*.*s\"\n", myname, SIZE, SIZE, buffer); sleep (PAUSE); lock.l_type = F_UNLCK; if(fcntl(fd, F_SETLKW, &lock) <0) perror("F_SETLKW"); printf("\tread:%s unlocked\n", myname); trial++; } A. Bogatyrev, 1992-95 - 244 - Si v UNIX Issleduya vydachu etoj programmy, vy mozhete obnaruzhit', chto READ-oblasti mogut perekry- vat'sya; no chto nikogda ne perekryvayutsya oblasti READ i WRITE ni v kakoj kombinacii. Esli idet chtenie processom A - to zapis' processom B dozhdetsya razblokirovki A (chtenie - ne budet dozhidat'sya). Esli idet zapis' processom A - to i chtenie processom B i zapis' processom B dozhdutsya razblokirovki A. 6.9.2. UNIX SVR4 imeet eshche odin interfejs dlya blokirovki fajlov: funkciyu lockf. #include <unistd.h> int lockf(int fd, int operation, size_t size); Operaciya operation: F_ULOCK Razblokirovat' ukazannyj segment fajla (eto mozhet snimat' odin ili neskol'ko zamkov). F_LOCK F_TLOCK Ustanovit' zamok. Pri etom, esli uzhe imeetsya chuzhoj zamok na zaprashivaemuyu oblast', F_LOCK blokiruet process, F_TLOCK - prosto vydaet oshibku (funkciya vozv- rashchaet -1, errno ustanavlivaetsya v EAGAIN). - Ozhidanie otpiraniya/zapiraniya zamka mozhet byt' prervano signalom. - Zamok ustanavlivaetsya sleduyushchim obrazom: ot tekushchej pozicii ukazatelya chteniya- zapisi v fajle fd (chto ne pohozhe na fcntl, gde poziciya zadaetsya yavno kak para- metr v strukture); dlinoj size. Otricatel'noe znachenie size oznachaet otschet ot tekushchej pozicii k nachalu fajla. Nulevoe znachenie - oznachaet "ot tekushchej pozicii do konca fajla". Pri etom "konec fajla" ponimaetsya imenno kak konec, a ne kak tekushchij razmer fajla. Esli fajl izmenit razmer, zapertaya oblast' vse ravno budet prostirat'sya do konca fajla (uzhe novogo). - Zamki, ustanovlennye processom, avtomaticheski otpirayutsya pri zavershenii pro- cessa. F_TEST Proverit' nalichie zamka. Funkciya vozvrashchaet 0, esli zamka net; -1 v protivnom sluchae (zaperto). Esli ustanavlivaetsya zamok, perekryvayushchijsya s uzhe ustanovlennym, to zamki ob®edinya- yutsya. bylo: ___________#######____######__________ zaprosheno:______________##########______________ stalo: ___________#################__________ Esli snimaetsya zamok s oblasti, pokryvayushchej tol'ko chast' zablokirovannoj prezhde, ostatok oblasti ostaetsya kak otdel'nyj zamok. bylo: ___________#################__________ zaprosheno:______________XXXXXXXXXX______________ stalo: ___________###__________####__________ Prostranstvo diskovoj pamyati mozhet sostoyat' iz neskol'kih fajlovyh sistem (v dal'nejshem FS), t.e. logicheskih i/ili fizicheskih diskov. Kazhdaya fajlovaya sistema imeet drevovidnuyu logicheskuyu strukturu (katalogi, podkatalogi i fajly) i imeet svoj kornevoj katalog. Fajly v kazhdoj FS imeyut svoi sobstvennye I-uzly i sobstvennuyu ih numeraciyu s 1. V nachale kazhdoj FS zarezervirovany: A. Bogatyrev, 1992-95 - 245 - Si v UNIX - blok dlya zagruzchika - programmy, vyzyvaemoj apparatno pri vklyuchenii mashiny (zag- ruzchik zapisyvaet s diska v pamyat' mashiny programmu /boot, kotoraya v svoyu oche- red' zagruzhaet v pamyat' yadro /unix); - superblok - blok zagolovka fajlovoj sistemy, hranyashchij razmer fajlovoj sistemy (v blokah), razmer bloka (512, 1024, ...), kolichestvo I-uzlov, nachalo spiska svo- bodnyh blokov, i drugie svedeniya ob FS; - nekotoraya nepreryvnaya oblast' diska dlya hraneniya I-uzlov - "I-fajl". Fajlovye sistemy ob®edinyayutsya v edinuyu drevovidnuyu ierarhiyu operaciej montirovaniya - podklyucheniya kornya fajlovoj sistemy k kakomu-to iz katalogov-"list'ev" dereva drugoj FS. Fajly v ob®edinennoj ierarhii adresuyutsya pri pomoshchi dvuh sposobov: - imen, zadayushchih put' v dereve katalogov: /usr/abs/bin/hackIt bin/hackIt ./../../bin/vi (etot sposob prednaznachen dlya programm, pol'zuyushchihsya fajlami, a takzhe pol'zova- telej); - vnutrennih adresov, ispol'zuemyh programmami yadra i nekotorymi sistemnymi prog- rammami. Poskol'ku v kazhdoj FS imeetsya sobstvennaya numeraciya I-uzlov, to fajl v ob®edinennoj ierarhii dolzhen adresovat'sya DVUMYA parametrami: - nomerom (kodom) ustrojstva, soderzhashchego fajlovuyu sistemu, v kotoroj nahoditsya iskomyj fajl: dev_t i_dev; - nomerom I-uzla fajla v etoj fajlovoj sisteme: ino_t i_number; Preobrazovanie imeni fajla v ob®edinennoj fajlovoj ierarhii v takuyu adresnuyu paru vypolnyaet v yadre uzhe upominavshayasya vyshe funkciya namei (pri pomoshchi prosmotra katalo- gov): struct inode *ip = namei(...); Sozdavaemaya eyu kopiya I-uzla v pamyati yadra soderzhit polya i_dev i i_number (kotorye na samom diske ne hranyatsya!). Rassmotrim nekotorye algoritmy raboty yadra s fajlami. Nizhe oni privedeny chisto shematichno i v sil'nom uproshchenii. Formaty vyzova (i oformlenie) funkcij ne soot- vetstvuyut formatam, ispol'zuemym na samom dele v yadre; verny lish' nazvaniya funkcij. Opushcheny proverki na korrektnost', podschet ssylok na struktury file i inode, bloki- rovka I-uzlov i kesh-buferov ot odnovremennogo dostupa, i mnogoe drugoe. Pust' my hotim otkryt' fajl dlya chteniya i prochitat' iz nego nekotoruyu informaciyu. Vyzovy otkrytiya i zakrytiya fajla imeyut shemu (chast' ee budet ob®yasnena pozzhe): #include <sys/types.h> #include <sys/inode.h> #include <sys/file.h> int fd_read = open(imyaFajla, O_RDONLY){ int fd; struct inode *ip; struct file *fp; dev_t dev; u_error = 0; /* errno v programme */ // Najti fajl po imeni. Sozdaetsya kopiya I-uzla v pamyati: ip = namei(imyaFajla, LOOKUP); // namei mozhet vydat' oshibku, esli net takogo fajla if(u_error) return(-1); // oshibka // Vydelyaetsya struktura "otkrytyj fajl": fp = falloc(ip, FREAD); // fp->f_flag = FREAD; otkryt na chtenie A. Bogatyrev, 1992-95 - 246 - Si v UNIX // fp->f_offset = 0; RWptr // fp->f_inode = ip; ssylka na I-uzel // Vydelit' novyj deskriptor for(fd=0; fd < NOFILE; fd++) if(u_ofile[fd] == NULL ) // svoboden goto done; u_error = EMFILE; return (-1); done: u_ofile[fd] = fp; // Esli eto ustrojstvo - inicializirovat' ego. // |to funkciya openi(ip, fp->f_flag); dev = ip->i_rdev; if((ip->i_mode & IFMT) == IFCHR) (*cdevsw[major(dev)].d_open)(minor(dev),fp->f_flag); else if((ip->i_mode & IFMT) == IFBLK) (*bdevsw[major(dev)].d_open)(minor(dev),fp->f_flag); return fd; // cherez u_rval1 } close(fd){ struct file *fp = u_ofile[fd]; struct inode *ip = fp->f_inode; dev_t dev = ip->i_rdev; if((ip->i_mode & IFMT) == IFCHR) (*cdevsw[major(dev)].d_close)(minor(dev),fp->f_flag); else if((ip->i_mode & IFMT) == IFBLK) (*bdevsw[major(dev)].d_close)(minor(dev),fp->f_flag); u_ofile[fd] = NULL; // i udalit' nenuzhnye struktury iz yadra. } Teper' rassmotrim funkciyu preobrazovaniya logicheskih blokov fajla v nomera fizicheskih blokov v fajlovoj sisteme. Dlya etogo preobrazovaniya v I-uzle fajla soderzhitsya tablica adresov blokov. Ona ustroena dovol'no slozhno - ee nachalo nahoditsya v uzle, a prodol- zhenie - v neskol'kih blokah v samoj fajlovoj sisteme (ustrojstvo eto mozhno uvidet' v primere "Fragmentirovannost' fajlovoj sistemy" v prilozhenii). My dlya prostoty budem predpolagat', chto eto prosto linejnyj massiv i_addr[], v kotorom n-omu logicheskomu bloku fajla otvechaet bno-tyj fizicheskij blok fajlovoj sistemy: bno = ip->i_addr[n]; Esli fajl yavlyaetsya interfejsom ustrojstva, to etot fajl ne hranit informacii v logi- cheskoj fajlovoj sisteme. Poetomu u ustrojstv net tablicy adresov blokov. Vmesto etogo, pole i_addr[0] ispol'zuetsya dlya hraneniya koda ustrojstva, k kotoromu privodit etot special'nyj fajl. |to pole nosit nazvanie i_rdev, t.e. kak by sdelano #define i_rdev i_addr[0] (na samom dele ispol'zuetsya union). Ustrojstva byvayut bajto-orientirovannye, obmen s kotorymi proizvoditsya po odnomu bajtu (kak s terminalom ili s kommunikacionnym por- tom); i blochno-orientirovannye, obmen s kotorymi vozmozhen tol'ko bol'shimi porciyami - blokami (primer - disk). To, chto fajl yavlyaetsya ustrojstvom, pomecheno v pole tip fajla ip->i_mode & IFMT A. Bogatyrev, 1992-95 - 247 - Si v UNIX odnim iz znachenij: IFCHR - bajtovoe; ili IFBLK - blochnoe. Algoritm vychisleniya nomera bloka: ushort u_pboff; // smeshchenie ot nachala bloka ushort u_pbsize; // skol'ko bajt nado ispol'zovat' // ushort - eto unsigned short, smotri <sys/types.h> // daddr_t - eto long (disk address) daddr_t bmap(struct inode *ip, off_t offset, unsigned count){ int sz, rem; // vychislit' logicheskij nomer bloka po pozicii RWptr. // BSIZE - eto razmer bloka fajlovoj sistemy, // eta konstanta opredelena v <sys/param.h> daddr_t bno = offset / BSIZE; // esli BSIZE == 1 Kb, to mozhno offset >> 10 u_pboff = offset % BSIZE; // eto mozhno zapisat' kak offset & 01777 sz = BSIZE - u_pboff; // stol'ko bajt nado vzyat' iz etogo bloka, // nachinaya s pozicii u_pboff. if(count < sz) sz = count; u_pbsize = sz; Esli fajl predstavlyaet soboj ustrojstvo, to translyaciya logicheskih blokov v fizicheskie ne proizvoditsya - ustrojstvo predstavlyaet soboj "syroj" disk bez fajlov i katalogov, t.e. obrashchenie proishodit srazu po fizicheskomu nomeru bloka: if((ip->i_mode & IFMT) == IFBLK) // block device return bno; // raw disk // inache provesti pereschet: rem = ip->i_size /*dlina fajla*/ - offset; // eto ostatok fajla. if( rem < 0 ) rem = 0; // fajl koroche, chem zakazano nami: if( rem < sz ) sz = rem; if((u_pbsize = sz) == 0) return (-1); // EOF // i, sobstvenno, zamena logich. nomera na fizich. return ip->i_addr[bno]; } Teper' rassmotrim algoritm read. Parametry, nachinayushchiesya s u_..., na samom dele pere- dayutsya kak staticheskie cherez vspomogatel'nye peremennye v u-area processa. read(int fd, char *u_base, unsigned u_count){ unsigned srccount = u_count; struct file *fp = u_ofile[fd]; struct inode *ip = fp->f_inode; struct buf *bp; daddr_t bno; // ocherednoj blok fajla // dev - ustrojstvo, // interfejsom kotorogo yavlyaetsya fajl-ustrojstvo, // ili na kotorom raspolozhen obychnyj fajl. dev_t dev = (ip->i_mode & (IFCHR|IFBLK)) ? A. Bogatyrev, 1992-95 - 248 - Si v UNIX ip->i_rdev : ip->i_dev; switch( ip->i_mode & IFMT ){ case IFCHR: // bajto-orientirovannoe ustrojstvo (*cdevsw[major(dev)].d_read)(minor(dev)); // prochie parametry peredayutsya cherez u-area break; case IFREG: // obychnyj fajl case IFDIR: // katalog case IFBLK: // blochno-orientirovannoe ustrojstvo do{ bno = bmap(ip, fp->f_offset /*RWptr*/, u_count); if(u_pbsize==0 || (long)bno < 0) break; // EOF bp = bread(dev, bno); // block read iomove(bp->b_addr + u_pboff, u_pbsize, B_READ); Funkciya iomove kopiruet dannye bp->b_addr[ u_pboff..u_pboff+u_pbsize-1 ] iz adresnogo prostranstva yadra (iz bufera v yadre) v adresnoe prostranstvo processa po adresam u_base[ 0..u_pbsize-1 ] to est' peresylaet u_pbsize bajt mezhdu yadrom i processom (u_base popadaet v iomove cherez staticheskuyu peremennuyu). Pri zapisi vyzovom write(), iomove s flagom B_WRITE proizvodit obratnoe kopirovanie - iz pamyati processa v pamyat' yadra. Prodolzhim: // prodvinut' schetchiki i ukazateli: u_count -= u_pbsize; u_base += u_pbsize; fp->f_offset += u_pbsize; // RWptr } while( u_count != 0 ); break; ... return( srccount - u_count ); } // end read Teper' obsudim nekotorye mesta etogo algoritma. Snachala posmotrim, kak proishodit obrashchenie k bajtovomu ustrojstvu. Vmesto adresov blokov my poluchaem kod ustrojstva i_rdev. Kody ustrojstv v UNIX (tip dev_t) predstavlyayut soboj paru dvuh chisel, nazy- vaemyh mazhor i minor, hranimyh v starshem i mladshem bajtah koda ustrojstva: #define major(dev) ((dev >> 8) & 0x7F) #define minor(dev) ( dev & 0xFF) Mazhor oboznachaet tip ustrojstva (disk, terminal, i.t.p.) i privodit k odnomu iz draj- verov (esli u nas est' 8 terminalov, to ih obsluzhivaet odin i tot zhe drajver); a minor oboznachaet nomer ustrojstva dannogo tipa (... kazhdyj iz terminalov imeet minory 0..7). Minory obychno sluzhat indeksami v nekotoroj tablice struktur vnutri vybrannogo drajvera. Mazhor zhe sluzhit indeksom v pereklyuchatel'noj tablice ustrojstv. Pri etom blochno-orientirovannye ustrojstva vybirayutsya v odnoj tablice - bdevsw[], a bajto- orientirovannye - v drugoj - cdevsw[] (sm. <sys/conf.h>; imena tablic oznachayut block/character device switch). Kazhdaya stroka tablicy soderzhit adresa funkcij, vypolnyayushchih nekotorye predopredelennye operacii sposobom, zavisimym ot ustrojstva. Sami eti funkcii realizovany v drajverah ustrojstv. Argumentom dlya etih funkcij obychno sluzhit minor ustrojstva, k kotoromu proizvoditsya obrashchenie. Funkciya v A. Bogatyrev, 1992-95 - 249 - Si v UNIX drajvere ispol'zuet etot minor kak indeks dlya vybora konkretnogo ekzemplyara ust- rojstva dannogo tipa; kak indeks v massive upravlyayushchih struktur (soderzhashchih tekushchee sostoyanie, rezhimy raboty, adresa funkcij preryvanij, adresa ocheredej dannyh i.t.p. kazhdogo konkretnogo ustrojstva) dlya dannogo tipa ustrojstv. |ti upravlyayushchie struktury razlichny dlya raznyh tipov ustrojstv (i ih drajverov). Kazhdaya stroka pereklyuchatel'noj tablicy soderzhit adresa funkcij, vypolnyayushchih ope- racii open, close, read, write, ioctl, select. open sluzhit dlya inicializacii ust- rojstva pri pervom ego otkrytii (++ip->i_count==1) - naprimer, dlya vklyucheniya motora; close - dlya vyklyucheniya pri poslednem zakrytii (--ip->i_count==0). U blochnyh ust- rojstv polya dlya read i write ob®edineny v funkciyu strategy, vyzyvaemuyu s parametrom B_READ ili B_WRITE. Vyzov ioctl prednaznachen dlya upravleniya parametrami raboty ust- rojstva. Operaciya select - dlya oprosa: est' li postupivshie v ustrojstvo dannye (nap- rimer, est' li v clist-e vvoda s klaviatury bajty? sm. glavu "|krannye biblioteki"). Vyzov select primenim tol'ko k nekotorym bajtoorientirovannym ustrojstvam i setevym portam (socket-am). Esli dannoe ustrojstvo ne umeet vypolnyat' takuyu operaciyu, to est' zapros k etoj operacii dolzhen vernut' v programmu oshibku (naprimer, operaciya read neprimenima k printeru), to v pereklyuchatel'noj tablice soderzhitsya special'noe imya funkcii nodev; esli zhe operaciya dopustima, no yavlyaetsya fiktivnoj (kak write dlya /dev/null) - imya nulldev. Obe eti funkcii-zaglushki predstavlyayut soboj "pustyshki": {}. Teper' obratimsya k blochno-orientirovannym ustrojstvam. UNIX ispol'zuet vnutri yadra dopolnitel'nuyu buferizaciyu pri obmenah s takimi ustrojstvami|-. Ispol'zovannaya nami vyshe funkciya bp=bread(dev,bno); proizvodit chtenie fizicheskogo bloka nomer bno s ustrojstva dev. |ta operaciya obrashchaetsya k drajveru konkretnogo ustrojstva i vyzyvaet chtenie bloka v nekotoruyu oblast' pamyati v yadre OS: v odin iz kesh-buferov (cache, "zapasat'"). Zagolovki kesh-buferov (struct buf) organizovany v spisok i imeyut polya (sm. fajl <sys/buf.h>): b_dev kod ustrojstva, s kotorogo prochitan blok; b_blkno nomer fizicheskogo bloka, hranyashchegosya v bufere v dannyj moment; b_flags flagi bloka (sm. nizhe); b_addr adres uchastka pamyati (kak pravilo v samom yadre), v kotorom sobstvenno i hranitsya soderzhimoe bloka. Buferizaciya blokov pozvolyaet sisteme ekonomit' chislo obrashchenij k disku. Pri obrashche- nii k bread() snachala proishodit poisk bloka (dev,bno) v tablice kesh-buferov. Esli blok uzhe byl ranee prochitan v kesh, to obrashcheniya k disku ne proishodit, poskol'ku kopiya soderzhimogo diskovogo bloka uzhe est' v pamyati yadra. Esli zhe bloka eshche net v kesh-buferah, to v yadre vydelyaetsya chistyj bufer, v zagolovke emu propisyvayutsya nuzhnye znacheniya polej b_dev i b_blkno, i blok schityvaetsya v bufer s diska vyzovom funkcii bp->b_flags |= B_READ; // rod raboty: prochitat' (*bdevsw[major(dev)].d_startegy)(bp); // bno i minor - berutsya iz polej *bp iz drajvera konkretnogo ustrojstva. Kogda my chto-to izmenyaem v fajle vyzovom write(), to izmeneniya na samom dele proishodyat v kesh-buferah v pamyati yadra, a ne srazu na diske. Pri zapisi v blok bufer pomechaetsya kak izmenennyj: b_flags |= B_DELWRI; // otlozhennaya zapis' ____________________ |- Sleduet otlichat' etu sistemnuyu buferizaciyu ot buferizacii pri pomoshchi biblioteki stdio. Biblioteka sozdaet bufer v samom processe, togda kak sistemnye vyzovy imeyut bufera vnutri yadra. A. Bogatyrev, 1992-95 - 250 - Si v UNIX i na disk nemedlenno ne zapisyvaetsya. Izmenennye bufera fizicheski zapisyvayutsya na disk v takih sluchayah: - Byl sdelan sistemnyj vyzov sync(); - YAdru ne hvataet kesh-buferov (ih chislo ogranicheno). Togda samyj staryj bufer (k kotoromu dol'she vsego ne bylo obrashchenij) zapisyvaetsya na disk i posle etogo ispol'zuetsya dlya drugogo bloka. - Fajlovaya sistema byla otmontirovana vyzovom umount; Ponyatno, chto ne izmenennye bloki obratno na disk iz buferov ne zapisyvayutsya (t.k. na diske i tak soderzhatsya te zhe samye dannye). Dazhe esli fajl uzhe zakryt close, ego bloki mogut byt' eshche ne zapisany na disk - zapis' proizojdet lish' pri vyzove sync. |to oznachaet, chto izmenennye bloki zapisyvayutsya na disk "massirovanno" - po mnogu blokov, no ne ochen' chasto, chto pozvolyaet optimizirovat' i samu zapis' na disk: sorti- rovkoj blokov mozhno dostich' minimizacii peremeshcheniya magnitnyh golovok nad diskom. Otslezhivanie samyh "staryh" buferov proishodit za schet reorganizacii spiska zagolovkov kesh-buferov. V bol'shom uproshchenii eto mozhno predstavit' tak: kak tol'ko k bloku proishodit obrashchenie, sootvetstvuyushchij zagolovok perestavlyaetsya v nachalo spiska. V itoge samyj "passivnyj" blok okazyvaetsya v hvoste - on to i pereispol'zuetsya pri nuzhde. "Podvisanie" fajlov v pamyati yadra znachitel'no uskoryaet rabotu programm, t.k. rabota s pamyat'yu gorazdo bystree, chem s diskom. Esli blok nado schitat'/zapisat', a on uzhe est' v keshe, to real'nogo obrashcheniya k disku ne proishodit. Zato, esli sluchitsya sboj pitaniya (ili kto-to neakkuratno vyklyuchit mashinu), a nekotorye bufera eshche ne byli sbrosheny na disk - to chast' izmenenij v fajlah budet poteryana. Dlya prinuditel'noj zapisi vseh izmenennyh kesh-buferov na disk sushchestvuet sisvyzov "sinhronizacii" soder- zhimogo diskov i pamyati sync(); // synchronize Vyzov sync delaetsya raz v 30 sekund special'nym sluzhebnym processom /etc/update, zapuskaemym pri zagruzke sistemy. Dlya raboty s fajlami, kotorye dolzhny garantiro- vanno byt' korrektnymi na diske, ispol'zuetsya otkrytie fajla fd = open( imya, O_RDWR | O_SYNC); kotoroe oznachaet, chto pri kazhdom write blok iz kesh-bufera nemedlenno zapisyvaetsya na disk. |to delaet rabotu nadezhnee, no sushchestvenno medlennee. Special'nye fajly ustrojstv ne mogut byt' sozdany vyzovom creat, sozdayushchim tol'ko obychnye fajly. Fajly ustrojstv sozdayutsya vyzovom mknod: #include <sys/sysmacros.h> dev_t dev = makedev(major, minor); /* (major << 8) | minor */ mknod( imyaFajla, kodyDostupa|tip, dev); gde dev - para (mazhor,minor) sozdavaemogo ustrojstva; kodyDostupa - kody dostupa k fajlu (0777)|=; tip - eto odna iz konstant S_IFIFO, S_IFCHR, S_IFBLK iz include-fajla <sys/stat.h>. mknod dostupen dlya vypolneniya tol'ko superpol'zovatelyu (za isklyucheniem sluchaya S_IFIFO). Esli by eto bylo ne tak, to mozhno bylo by sozdat' fajl ustrojstva, svyazan- nyj s sushchestvuyushchim diskom, i chitat' informaciyu s nego napryamuyu, v obhod mehanizmov logicheskoj fajlovoj sistemy i zashchity fajlov kodami dostupa. Mozhno sozdat' fajl ustrojstva s mazhorom i/ili minorom, ne otvechayushchim nikakomu real'nomu ustrojstvu (net takogo drajvera ili minor slishkom velik). Otkrytie takih ____________________ |= Obychno k blochnym ustrojstvam (diskam) dostup razreshaetsya tol'ko superpol'zova- telyu, v protivnom sluchae mozhno prochitat' s "syrogo" diska (v obhod mehanizmov fajlo- voj sistemy) fizicheskie bloki lyubogo fajla i ves' mehanizm zashchity okazhetsya nerabotayu- shchim. A. Bogatyrev, 1992-95 - 251 - Si v UNIX ustrojstv vydaet kod oshibki ENODEV. Iz nashej programmy my mozhem vyzovom stat() uznat' kod ustrojstva, na kotorom raspolozhen fajl. On budet soderzhat'sya v pole dev_t st_dev; a esli fajl yavlyaetsya spe- cial'nym fajlom (interfejsom drajvera ustrojstva), to kod samogo etogo ustrojstva mozhno uznat' iz polya dev_t st_rdev; Rassmotrim primer, kotoryj vyyasnyaet, otnosyatsya li dva imeni k odnomu i tomu zhe fajlu: #include <sys/types.h> #include <sys/stat.h> void main(ac, av) char *av[]; { struct stat st1, st2; int eq; if(ac != 3) exit(13); stat(av[1], &st1); stat(av[2], &st2); if(eq = (st1.st_ino == st2.st_ino && /* nomera I-uzlov */ st1.st_dev == st2.st_dev)) /* kody ustrojstv */ printf("%s i %s - dva imeni odnogo fajla\n",av[1],av[2]); exit( !eq ); } Nakonec, vernemsya k sklejke neskol'kih fajlovyh sistem v odnu ob®edinennuyu ierarhiyu: ino=2 *------ kornevaya fajlovaya sistema / \ /\ na diske /dev/hd0 / /\ /\ \ *-/mnt/hd1 : * ino=2 FS na diske /dev/hd1 / \ (removable FS) /\ \ Dlya togo, chtoby pomestit' kornevoj katalog fajlovoj sistemy, nahodyashchejsya na diske /dev/hd1, vmesto kataloga /mnt/hd1 uzhe "sobrannoj" fajlovoj sistemy, my dolzhny izdat' sisvyzov mount("/dev/hd1", "/mnt/hd1", 0); Dlya otklyucheniya smontirovannoj fajlovoj sistemy my dolzhny vyzvat' umount("/dev/hd1"); (katalog, k kotoromu ona smontirovana, uzhe chislitsya v tablice yadra, poetomu ego zada- vat' ne nado). Pri montirovanii vse soderzhimoe kataloga /mnt/hd1 stanet nedostupnym, zato pri obrashchenii k imeni /mnt/hd1 my na samom dele doberemsya do (bezymyannogo) kor- nevogo kataloga na diske /dev/hd1. Takoj katalog nosit nazvanie mount point i mozhet byt' vyyavlen po tomu priznaku, chto "." i ".." v nem lezhat na raznyh ustrojstvah: struct stat st1, st2; stat("/mnt/hd1/.", &st1); stat("/mnt/hd1/..", &st2); if( st1.st_dev != st2.st_dev) ... ; /*mount point*/ Dlya st1 pole st_dev oznachaet kod ustrojstva /dev/hd1, a dlya st2 - ustrojstva, soder- zhashchego kornevuyu fajlovuyu sistemu. Operacii montirovaniya i otmontirovaniya fajlovyh sistem dostupny tol'ko superpol'zovatelyu. I naposledok - sravnenie struktur I-uzla. na diske v pamyati v vyzove stat <sys/ino.h> <sys/inode.h> <sys/stat.h> A. Bogatyrev, 1992-95 - 252 - Si v UNIX struct dinode struct inode struct stat // kody dostupa i tip fajla ushort di_mode i_mode st_mode // chislo imen fajla short di_nlink i_nlink st_nlink // nomer I-uzla ushort --- i_number st_ino // identifikator vladel'ca ushort di_uid i_uid st_uid // identifikator gruppy vladel'ca ushort di_gid i_gid st_gid // razmer fajla v bajtah off_t di_size i_size st_size // vremya sozdaniya time_t di_ctime i_ctime st_ctime // vremya poslednego izmeneniya (write) time_t di_mtime i_mtime st_mtime // vremya poslednego dostupa (read/write) time_t di_atime i_atime st_atime // ustrojstvo, na kotorom raspolozhen fajl dev_t --- i_dev st_dev // ustrojstvo, k kotoromu privodit spec.fajl dev_t --- i_rdev st_rdev // adresa blokov char di_addr[39] i_addr[] // schetchik ssylok na strukturu v yadre cnt_t i_count // i koe-chto eshche Minusy oznachayut, chto dannoe pole ne hranitsya na diske, a vychislyaetsya yadrom. V sovre- mennyh versiyah UNIX mogut byt' legkie otlichiya ot vyshenapisannoj tablicy. 6.10.1. Napishite programmu pwd, opredelyayushchuyu polnoe imya tekushchego rabochego kataloga. #define U42 opredelyaet fajlovuyu sistemu s dlinnymi imenami, otsutstvie etogo flaga - s korotkimi (14 simvolov). A. Bogatyrev, 1992-95 - 253 - Si v UNIX /* Komanda pwd. * Tekst getwd() vzyat iz ishodnyh tekstov biblioteki yazyka Si. */ #include <stdio.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <dirent.h> #define ediag(e,r) (e) /* * getwd() vozvrashchaet polnoe imya tekushchego rabochego kataloga. * Pri oshibke vozvrashchaetsya NULL, a v pathname kopiruetsya soobshchenie * ob oshibke. */ #ifndef MAXPATHLEN #define MAXPATHLEN 128 #endif #define CURDIR "." /* imya tekushchego kataloga */ #define PARENTDIR ".." /* imya roditel'skogo kataloga */ #define PATHSEP "/" /* razdelitel' komponent puti */ #define ROOTDIR "/" /* kornevoj katalog */ #define GETWDERR(s) strcpy(pathname, (s)); #define CP(to,from) strncpy(to,from.d_name,DIRSIZ),to[DIRSIZ]='\0' char *strcpy(char *, char *); char *strncpy(char *, char *, int); char *getwd(char *pathname); static char *prepend(char *dirname, char *pathname); static int pathsize; /* dlina imeni */ #ifndef U42 char *getwd(char *pathname) { char pathbuf[MAXPATHLEN]; /* temporary pathname buffer */ char *pnptr = &pathbuf[(sizeof pathbuf)-1]; /* pathname pointer */ dev_t rdev; /* root device number */ int fil = (-1); /* directory file descriptor */ ino_t rino; /* root inode number */ struct direct dir; /* directory entry struct */ struct stat d ,dd; /* file status struct */ /* d - "." dd - ".." | dname */ char dname[DIRSIZ+1]; /* an directory entry */ pathsize = 0; *pnptr = '\0'; if (stat(ROOTDIR, &d) < 0) { GETWDERR(ediag("getwd: can't stat /", "getwd: nel'zya vypolnit' stat /")); return (NULL); } rdev = d.st_dev; /* kod ustrojstva, na kotorom razmeshchen koren' */ rino = d.st_ino; /* nomer I-uzla, predstavlyayushchego kornevoj katalog */ A. Bogatyrev, 1992-95 - 254 - Si v UNIX for (;;) { if (stat(CURDIR, &d) < 0) { CantStat: GETWDERR(ediag("getwd: can't stat .", "getwd: nel'zya vypolnit' stat .")); goto fail; } if (d.st_ino == rino && d.st_dev == rdev) break; /* dostigli kornevogo kataloga */ if ((fil = open(PARENTDIR, O_RDONLY)) < 0) { GETWDERR(ediag("getwd: can't open ..", "getwd: nel'zya otkryt' ..")); goto fail; } if (chdir(PARENTDIR) < 0) { GETWDERR(ediag("getwd: can't chdir to ..", "getwd: nel'zya perejti v ..")); goto fail; } if (fstat(fil, &dd) < 0) goto CantStat; if (d.st_dev == dd.st_dev) { /* to zhe ustrojstvo */ if (d.st_ino == dd.st_ino) { /* dostigli kornya ".." == "." */ close(fil); break; } do { if (read(fil, (char *) &dir, sizeof(dir)) < sizeof(dir) ){ ReadErr: close(fil); GETWDERR(ediag("getwd: read error in ..", "getwd: oshibka chteniya ..")); goto fail; } } while (dir.d_ino != d.st_ino); CP(dname,dir); } else /* ".." nahoditsya na drugom diske: mount point */ do { if (read(fil, (char *) &dir, sizeof(dir)) < sizeof(dir)) goto ReadErr; if( dir.d_ino == 0 ) /* fajl stert */ continue; CP(dname,dir); if (stat(dname, &dd) < 0) { sprintf (pathname, "getwd: %s %s", ediag ("can't stat", "nel'zya vypolnit' stat"), dname); goto fail; } } while(dd.st_ino != d.st_ino || dd.st_dev != d.st_dev); close(fil); pnptr = prepend(PATHSEP, prepend(dname, pnptr)); } A. Bogatyrev, 1992-95 - 255 - Si v UNIX if (*pnptr == '\0') /* tekushchij katalog == kornevomu */ strcpy(pathname, ROOTDIR); else { strcpy(pathname, pnptr); if (chdir(pnptr) < 0) { GETWDERR(ediag("getwd: can't change back to .", "getwd: nel'zya vernut'sya v .")); return (NULL); } } return (pathname); fail: close(fil); chdir(prepend(CURDIR, pnptr)); return (NULL); } #else /* U42 */ extern char *strcpy (); extern DIR *opendir(); char *getwd (char *pathname) { char pathbuf[MAXPATHLEN];/* temporary pathname buffer */ char *pnptr = &pathbuf[(sizeof pathbuf) - 1];/* pathname pointer */ char *prepend (); /* prepend dirname to pathname */ dev_t rdev; /* root device number */ DIR * dirp; /* directory stream */ ino_t rino; /* root inode number */ struct dirent *dir; /* directory entry struct */ struct stat d, dd; /* file status struct */ pathsize = 0; *pnptr = '\0'; stat (ROOTDIR, &d); rdev = d.st_dev; rino = d.st_ino; for (;;) { stat (CURDIR, &d); if (d.st_ino == rino && d.st_dev == rdev) break; /* reached root directory */ if ((dirp = opendir (PARENTDIR)) == NULL) { GETWDERR ("getwd: can't open .."); goto fail; } if (chdir (PARENTDIR) < 0) { closedir (dirp); GETWDERR ("getwd: can't chdir to .."); goto fail; } A. Bogatyrev, 1992-95 - 256 - Si v UNIX fstat (dirp -> dd_fd, &dd); if (d.st_dev == dd.st_dev) { if (d.st_ino == dd.st_ino) { /* reached root directory */ closedir (dirp); break; } do { if ((dir = readdir (dirp)) == NULL) { closedir (dirp); GETWDERR ("getwd: read error in .."); goto fail; } } while (dir -> d_ino != d.st_ino); } else do { if ((dir = readdir (dirp)) == NULL) { closedir (dirp); GETWDERR ("getwd: read error in .."); goto fail; } stat (dir -> d_name, &dd); } while (dd.st_ino != d.st_ino || dd.st_dev != d.st_dev); closedir (dirp); pnptr = prepend (PATHSEP, prepend (dir -> d_name, pnptr)); } if (*pnptr == '\0') /* current dir == root dir */ strcpy (pathname, ROOTDIR); else { strcpy (pathname, pnptr); if (chdir (pnptr) < 0) { GETWDERR ("getwd: can't change back to ."); return (NULL); } } return (pathname); fail: chdir (prepend (CURDIR, pnptr)); return (NULL); } #endif A. Bogatyrev, 1992-95 - 257 - Si v UNIX /* * prepend() tacks a directory name onto the front of a pathname. */ static char *prepend ( register char *dirname, /* chto dobavlyat' */ register char *pathname /* k chemu dobavlyat' */ ) { register int i; /* dlina imeni kataloga */ for (i = 0; *dirname != '\0'; i++, dirname++) continue; if ((pathsize += i) < MAXPATHLEN) while (i-- > 0) *--pathname = *--dirname; return (pathname); } #ifndef CWDONLY void main(){ char buffer[MAXPATHLEN+1]; char *cwd = getwd(buffer); printf( "%s%s\n", cwd ? "": "ERROR:", buffer); } #endif 6.10.2. Napishite funkciyu canon(), kanoniziruyushchuyu imya fajla, t.e. prevrashchayushchuyu ego v polnoe imya (ot kornevogo kataloga), ne soderzhashchee komponent "." i "..", a takzhe lish- nih simvolov slesh '/'. Pust', k primeru, tekushchij rabochij katalog est' /usr/abs/C- book. Togda funkciya preobrazuet . -> /usr/abs/C-book .. -> /usr/abs ../.. -> /usr ////.. -> / /aa -> /aa /aa/../bb -> /bb cc//dd/../ee -> /usr/abs/C-book/cc/ee ../a/b/./d -> /usr/abs/a/b/d Otvet: #include <stdio.h> /* slesh, razdelitel' komponent puti */ #define SLASH '/' extern char *strchr (char *, char), *strrchr(char *, char); struct savech{ char *s, c; }; #define SAVE(sv, str) (sv).s = (str); (sv).c = *(str) #define RESTORE(sv) if((sv).s) *(sv).s = (sv).c /* |to struktura dlya ispol'zovaniya v takom kontekste: void main(){ char *d = "hello"; struct savech ss; SAVE(ss, d+3); *(d+3) = '\0'; printf("%s\n", d); RESTORE(ss); printf("%s\n", d); } */ /* OTSECHX POSLEDNYUYU KOMPONENTU PUTI */ struct savech parentdir(char *path){ char *last = strrchr( path, SLASH ); A. Bogatyrev, 1992-95 - 258 - Si v UNIX char *first = strchr ( path, SLASH ); struct savech sp; sp.s = NULL; sp.c = '\0'; if( last == NULL ) return sp; /* ne polnoe imya */ if( last[1] == '\0' ) return sp; /* kornevoj katalog */ if( last == first ) /* edinstvennyj slesh: /DIR */ last++; sp.s = last; sp.c = *last; *last = '\0'; return sp; } #define isfullpath(s) (*s == SLASH) /* KANONIZIROVATX IMYA FAJLA */ void canon( char *where, /* kuda pomestit' otvet */ char *cwd, /* polnoe imya tekushchego kataloga */ char *path /* ishodnoe imya dlya kanonizacii */ ){ char *s, *slash; /* Sformirovat' imya kataloga - tochki otscheta */ if( isfullpath(path)){ s = strchr(path, SLASH); /* @ */ strncpy(where, path, s - path + 1); where[s - path + 1] = '\0'; /* ili dazhe prosto strcpy(where, "/"); */ path = s+1; /* ostatok puti bez '/' v nachale */ } else strcpy(where, cwd); /* Pokomponentnyj prosmotr puti */ do{ if(slash = strchr(path, SLASH)) *slash = '\0'; /* teper' path soderzhit ocherednuyu komponentu puti */ if(*path == '\0' || !strcmp(path, ".")) ; /* to prosto proignorirovat' "." i lishnie "///" */ else if( !strcmp(path, "..")) (void) parentdir(where); else{ int len = strlen(where); /* dobavit' v konec razdelyayushchij slesh */ if( where[len-1] != SLASH ){ where[len] = SLASH; where[len+1] = '\0'; } strcat( where+len, path ); /* +len chisto dlya uskoreniya poiska * konca stroki vnutri strcat(); */ } if(slash){ *slash = SLASH; /* vosstanovit' */ path = slash + 1; } } while (slash != NULL); } char cwd[256], input[256], output[256]; void main(){ /* Uznat' polnoe imya tekushchego kataloga. * getcwd() - standartnaya funkciya, vyzyvayushchaya * cherez popen() komandu pwd (i potomu medlennaya). */ getcwd(cwd, sizeof cwd); while( gets(input)){ canon(output, cwd, input); printf("%-20s -> %s\n", input, output); } } A. Bogatyrev, 1992-95 - 259 - Si v UNIX V etom primere (iznachal'no pisavshemsya dlya MS DOS) est' "strannoe" mesto, pomechennoe /*@*/. Delo v tom, chto v DOS funkciya isfullpath byla sposobna raspoznavat' imena faj- lov vrode C:\aaa\bbb, kotorye ne obyazatel'no nachinayutsya so slesha. Dannaya glava posvyashchena sistemnomu vyzovu select, kotoryj, odnako, my predostav- lyaem vam issledovat' samostoyatel'no. Ego rol' takova: on pozvolyaet oprashivat' nes- kol'ko deskriptorov otkrytyh fajlov (ili ustrojstv) i kak tol'ko v fajle poyavlyaetsya novaya informaciya - soobshchat' ob etom nashej programme. Obychno eto byvaet svyazano s deskriptorami, vedushchimi k setevym ustrojstvam. 6.11.1. /* Primer ispol'zovaniya vyzova select() dlya mul'tipleksirovaniya * neskol'kih kanalov vvoda. |tot vyzov mozhno takzhe ispol'zovat' * dlya polucheniya tajmauta. * Vyzov: vojti na terminalah tty01 tty02 i nabrat' na kazhdom * sleep 30000 * zatem na tty00 skazat' select /dev/tty01 /dev/tty02 * i vvodit' chto-libo na terminalah tty01 i tty02 * Sborka: cc select.c -o select -lsocket */ #include <stdio.h> #include <fcntl.h> #include <sys/types.h> /* fd_set, FD_SET, e.t.c. */ #include <sys/param.h> /* NOFILE */ #include <sys/select.h> #include <sys/time.h> #include <sys/filio.h> /* dlya FIONREAD */ #define max(a,b) ((a) > (b) ? (a) : (b)) char buf[512]; /* bufer chteniya */ int fdin, fdout; /* deskriptory kanalov stdin, stdout */ int nready; /* chislo gotovyh kanalov */ int nopen; /* chislo otkrytyh kanalov */ int maxfd = 0; /* maksimal'nyj deskriptor */ int nfds; /* skol'ko pervyh deskriptorov proveryat' */ int f; /* tekushchij deskriptor */ fd_set set, rset; /* maski */ /* tablica otkrytyh nami fajlov */ struct _fds { int fd; /* deskriptor */ char name[30]; /* imya fajla */ } fds[ NOFILE ] = { /* NOFILE - maks. chislo otkrytyh fajlov na process */ { 0, "stdin" }, { 1, "stdout" }, { 2, "stderr" } /* vse ostal'noe - nuli */ }; struct timeval timeout, rtimeout; /* vydat' imya fajla po deskriptoru */ char *N( int fd ){ register i; for(i=0; i < NOFILE; i++) if(fds[i].fd == fd ) return fds[i].name; return "???"; } A. Bogatyrev, 1992-95 - 260 - Si v UNIX void main( int ac, char **av ){ nopen = 3; /* stdin, stdout, stderr */ for( f = 3; f < NOFILE; f++ ) fds[f].fd = (-1); fdin = fileno(stdin); fdout = fileno(stdout); setbuf(stdout, NULL); /* otmena buferizacii */ FD_ZERO(&set); /* ochistka maski */ for(f=1; f < ac; f++ ) if((fds[nopen].fd = open(av[f], O_RDONLY)) < 0 ){ fprintf(stderr, "Can't read %s\n", av[f] ); continue; } else { FD_SET(fds[nopen].fd, &set ); /* uchest' v maske */ maxfd = max(maxfd, fds[nopen].fd ); strncpy(fds[nopen].name, av[f], sizeof(fds[0].name) - 1); nopen++; } if( nopen == 3 ){ fprintf(stderr, "Nothing is opened\n"); exit(1); } FD_SET(fdin, &set); /* uchest' stdin */ maxfd = max(maxfd, fdin ); nopen -= 2; /* stdout i stderr ne uchastvuyut v select */ timeout.tv_sec = 10; /* sekund */ timeout.tv_usec = 0; /* millisekund */ /* nfds - eto KOLICHESTVO pervyh deskriptorov, kotorye nado * prosmatrivat'. Zdes' mozhno ispol'zovat' * nfds = NOFILE; (kol-vo VSEH deskriptorov ) * ili nfds = maxfd+1; (kol-vo = nomer poslednego+1) * ( +1 t.k. numeraciya fd idet s nomera 0, a kolichestvo - s 1). */ nfds = maxfd + 1; while( nopen ){ rset = set; rtimeout = timeout; /* kopiruem, t.k. izmenyatsya */ /* oprashivat' mozhno FIFO-fajly, terminaly, pty, socket-y, stream-y */ nready = select( nfds, &rset, NULL, NULL, &rtimeout ); /* Esli vmesto &rtimeout napisat' NULL, to ozhidanie budet * beskonechnym (poka ne sob'yut signalom) */ if( nready <= 0 ){ /* nichego ne postupilo */ fprintf(stderr, "Timed out, nopen=%d\n", nopen); continue; } A. Bogatyrev, 1992-95 - 261 - Si v UNIX /* opros gotovyh deskriptorov */ for(f=0; f < nfds; f++ ) if( FD_ISSET(f, &rset)){ /* deskriptor f gotov */ int n; /* Vyzov FIONREAD pozvolyaet zaprosit' * chislo bajt gotovyh k peredache * cherez deskriptor. */ if(ioctl(f, FIONREAD, &n) < 0) perror("FIONREAD"); else printf("%s have %d bytes.\n", N(f), n); if((n = read(f, buf, sizeof buf)) <= 0 ){ eof: FD_CLR(f, &set); /* isklyuchit' */ close(f); nopen--; fprintf(stderr, "EOF in %s\n", N(f)); } else { fprintf(stderr, "\n%d bytes from %s:\n", n, N(f)); write(fdout, buf, n); if( n == 4 && !strncmp(buf, "end\n", 4)) /* ncmp, t.k. buf mozhet ne okanchivat'sya \0 */ goto eof; } } } exit(0); } 6.11.2. V kachestve samostoyatel'noj raboty predlagaem vam primer programmy, vedushchej protokol seansa raboty. Informaciyu o psevdoterminalah izuchite samostoyatel'no. A. Bogatyrev, 1992-95 - 262 - Si v UNIX /* * script.c * Programma polucheniya trassirovki raboty drugih programm. * Ispol'zuetsya sistemnyj vyzov oprosa gotovnosti kanalov * vvoda/vyvoda select() i psevdoterminal (para ttyp+ptyp). */ #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <signal.h> #include <sys/param.h> /* NOFILE */ #include <sys/times.h> #include <sys/wait.h> #include <errno.h> #ifdef TERMIOS # include <termios.h> # define TERMIO struct termios # define GTTY(fd, tadr) tcgetattr(fd, tadr) # define STTY(fd, tadr) tcsetattr(fd, TCSADRAIN, tadr) #else # include <termio.h> # define TERMIO struct termio # define GTTY(fd, tadr) ioctl(fd, TCGETA, tadr) # define STTY(fd, tadr) ioctl(fd, TCSETAW, tadr) #endif A. Bogatyrev, 1992-95 - 263 - Si v UNIX #ifdef __SVR4 # include <stropts.h> /* STREAMS i/o */ extern char *ptsname(); #endif #if defined(ISC2_2) # include <sys/bsdtypes.h> #else # include <sys/select.h> #endif #ifndef BSIZE # define BSIZE 512 #endif #define LOGFILE "/usr/spool/scriptlog" #define max(a,b) ((a) > (b) ? (a) : (b)) extern int errno; TERMIO told, tnew, ttypmodes; FILE *fpscript = NULL; /* fajl s trassirovkoj (esli nado) */ int go = 0; int scriptflg = 0; int halfflag = 0; /* HALF DUPLEX */ int autoecho = 0; char *protocol = "typescript"; #define STDIN 0 /* fileno(stdin) */ #define STDOUT 1 /* fileno(stdout) */ #define STDERR 2 /* fileno(stderr) */ /* kakie kanaly svyazany s terminalom? */ int tty_stdin, tty_stdout, tty_stderr; int TTYFD; void wm_checkttys(){ TERMIO t; tty_stdin = ( GTTY(STDIN, &t) >= 0 ); tty_stdout = ( GTTY(STDOUT, &t) >= 0 ); tty_stderr = ( GTTY(STDERR, &t) >= 0 ); if ( tty_stdin ) TTYFD = STDIN; else if( tty_stdout ) TTYFD = STDOUT; else if( tty_stderr ) TTYFD = STDERR; else { fprintf(stderr, "Cannot access tty\n"); exit(7); } } A. Bogatyrev, 1992-95 - 264 - Si v UNIX /* Opisatel' trassiruemogo processa */ struct ptypair { char line[25]; /* terminal'naya liniya: /dev/ttyp? */ int pfd; /* deskriptor master pty */ long in_bytes; /* prochteno bajt s klaviatury */ long out_bytes; /* poslano bajt na ekran */ int pid; /* identifikator processa */ time_t t_start, t_stop; /* vremya zapuska i okonchaniya */ char *command; /* zapushchennaya komanda */ } PP; /* |ta funkciya vyzyvaetsya pri okonchanii trassiruemogo processa - * po signalu SIGCLD */ char Reason[128]; void ondeath(sig){ int pid; extern void wm_done(); int status; int fd; /* vyyavit' prichinu okonchaniya processa */ while((pid = wait(&status)) > 0 ){ if( WIFEXITED(status)) sprintf( Reason, "Pid %d died with retcode %d", pid, WEXITSTATUS(status)); else if( WIFSIGNALED(status)) { sprintf( Reason, "Pid %d killed by signal #%d", pid, WTERMSIG(status)); #ifdef WCOREDUMP if(WCOREDUMP(status)) strcat( Reason, " Core dumped" ); #endif } else if( WIFSTOPPED(status)) sprintf( Reason, "Pid %d suspended by signal #%d", pid, WSTOPSIG(status)); } wm_done(0); } void wm_init(){ wm_checkttys(); GTTY(TTYFD, &told); /* Skonstruirovat' "syroj" rezhim dlya nashego _bazovogo_ terminala */ tnew = told; tnew.c_cc[VINTR] = '\0'; tnew.c_cc[VQUIT] = '\0'; tnew.c_cc[VERASE] = '\0'; tnew.c_cc[VKILL] = '\0'; #ifdef VSUSP tnew.c_cc[VSUSP] = '\0'; #endif A. Bogatyrev, 1992-95 - 265 - Si v UNIX /* CBREAK */ tnew.c_cc[VMIN] = 1; tnew.c_cc[VTIME] = 0; tnew.c_cflag &= ~(PARENB|CSIZE); tnew.c_cflag |= CS8; tnew.c_iflag &= ~(ISTRIP|ICRNL); tnew.c_lflag &= ~(ICANON|ECHO|ECHOK|ECHOE|XCASE); tnew.c_oflag &= ~OLCUC; /* no ostavit' c_oflag ONLCR i TAB3, esli oni byli */ /* mody dlya psevdoterminala */ ttypmodes = told; /* ne vypolnyat' preobrazovaniya na vyvode: * ONLCR: \n --> \r\n * TAB3: \t --> probely */ ttypmodes.c_oflag &= ~(ONLCR|TAB3); (void) signal(SIGCLD, ondeath); } void wm_fixtty(){ STTY(TTYFD, &tnew); } void wm_resettty(){ STTY(TTYFD, &told); } /* Podobrat' svobodnyj psevdoterminal dlya trassiruemogo processa */ struct ptypair wm_ptypair(){ struct ptypair p; #ifdef __SVR4 p.pfd = (-1); p.pid = 0; p.in_bytes = p.out_bytes = 0; /* Otkryt' master side pary pty (eshche est' slave) */ if((p.pfd = open( "/dev/ptmx", O_RDWR)) < 0 ){ /* |to kloniruemyj STREAMS driver. * Poskol'ku on kloniruemyj, to est' sozdayushchij novoe psevdoustrojstvo * pri kazhdom otkrytii, to na master-storone mozhet byt' tol'ko * edinstvennyj process! */ perror( "Open /dev/ptmx" ); goto err; } A. Bogatyrev, 1992-95 - 266 - Si v UNIX # ifdef notdef /* Sdelat' prava dostupa k slave-storone moimi. */ if( grantpt (p.pfd) < 0 ){ perror( "grantpt"); exit(errno); } # endif /* Razblokirovat' slave-storonu psevdoterminala: pozvolit' pervyj open() dlya nee */ if( unlockpt(p.pfd) < 0 ){ perror( "unlockpt"); exit(errno); } /* Poluchit' i zapisat' imya novogo slave-ustrojstva-fajla. */ strcpy( p.line, ptsname(p.pfd)); #else register i; char c; struct stat st; p.pfd = (-1); p.pid = 0; p.in_bytes = p.out_bytes = 0; strcpy( p.line, "/dev/ptyXX" ); for( c = 'p'; c <= 's'; c++ ){ p.line[ strlen("/dev/pty") ] = c; p.line[ strlen("/dev/ptyp")] = '0'; if( stat(p.line, &st) < 0 ) goto err; for(i=0; i < 16; i++){ p.line[ strlen("/dev/ptyp") ] = "0123456789abcdef" [i] ; if((p.pfd = open( p.line, O_RDWR )) >= 0 ){ p.line[ strlen("/dev/") ] = 't'; return p; } } } #endif err: return p; } A. Bogatyrev, 1992-95 - 267 - Si v UNIX /* Vedenie statistiki po vyzovam script */ void write_stat( in_bytes, out_bytes, time_here , name, line, at ) long in_bytes, out_bytes; time_t time_here; char *name; char *line; char *at; { FILE *fplog; struct flock lock; if((fplog = fopen( LOGFILE, "a" )) == NULL ) return; lock.l_type = F_WRLCK; lock.l_whence = 0; lock.l_start = 0; lock.l_len = 0; /* zablokirovat' ves' fajl */ fcntl ( fileno(fplog), F_SETLKW, &lock ); fprintf( fplog, "%s (%s) %ld bytes_in %ld bytes_out %ld secs %s %s %s", PP.command, Reason, in_bytes, out_bytes, time_here, name, line, at ); fflush ( fplog ); lock.l_type = F_UNLCK; lock.l_whence = 0; lock.l_start = 0; lock.l_len = 0; /* razblokirovat' ves' fajl */ fcntl ( fileno(fplog), F_SETLK, &lock ); fclose ( fplog ); } void wm_done(sig){ char *getlogin(), *getenv(), *logname = getlogin(); time( &PP.t_stop ); /* zapomnit' vremya okonchaniya */ wm_resettty(); /* vosstanovit' rezhim bazovogo terminala */ if( fpscript ) fclose(fpscript); if( PP.pid > 0 ) kill( SIGHUP, PP.pid ); /* "obryv svyazi" */ if( go ) write_stat( PP.in_bytes, PP.out_bytes, PP.t_stop - PP.t_start, logname ? logname : getenv("LOGNAME"), PP.line, ctime(&PP.t_stop) ); printf( "\n" ); exit(0); } A. Bogatyrev, 1992-95 - 268 - Si v UNIX /* Zapusk trassiruemogo processa na psevdoterminale */ void wm_startshell (ac, av) char **av; { int child, fd, sig; if( ac == 0 ){ static char *avshell[] = { "/bin/sh", "-i", NULL }; av = avshell; } if((child = fork()) < 0 ){ perror("fork"); wm_done(errno); } if( child == 0 ){ /* SON */ if( tty_stdin ) setpgrp(); /* otkaz ot upravlyayushchego terminala */ /* poluchit' novyj upravlyayushchij terminal */ if((fd = open( PP.line, O_RDWR )) < 0 ){ exit(errno); } /* zakryt' lishnie kanaly */ if( fpscript ) fclose(fpscript); close( PP.pfd ); #ifdef __SVR4 /* Push pty compatibility modules onto stream */ ioctl(fd, I_PUSH, "ptem"); /* pseudo tty module */ ioctl(fd, I_PUSH, "ldterm"); /* line discipline module */ ioctl(fd, I_PUSH, "ttcompat"); /* BSD ioctls module */ #endif /* perenapravit' kanaly, svyazannye s terminalom */ if( fd != STDIN && tty_stdin ) dup2(fd, STDIN); if( fd != STDOUT && tty_stdout ) dup2(fd, STDOUT); if( fd != STDERR && tty_stderr ) dup2(fd, STDERR); if( fd > STDERR ) (void) close(fd); /* ustanovit' mody terminala */ STTY(TTYFD, &ttypmodes); /* vosstanovit' reakcii na signaly */ for(sig=1; sig < NSIG; sig++) signal( sig, SIG_DFL ); execvp(av[0], av); system( "echo OBLOM > HELP.ME"); perror("execl"); exit(errno); A. Bogatyrev, 1992-95 - 269 - Si v UNIX } else { /* FATHER */ PP.pid = child; PP.command = av[0]; time( &PP.t_start ); PP.t_stop = PP.t_start; signal( SIGHUP, wm_done ); signal( SIGINT, wm_done ); signal( SIGQUIT, wm_done ); signal( SIGTERM, wm_done ); signal( SIGILL, wm_done ); signal( SIGBUS, wm_done ); signal( SIGSEGV, wm_done ); } } char buf[ BSIZE ]; /* bufer dlya peredachi dannyh */ /* /dev/pty? /dev/ttyp? ekran *--------* *--------* /||| | | PP.pfd | | |||||<-STDOUT--| moj |<---------| psevdo |<-STDOUT---| \||| |terminal| |terminal|<-STDERR---|trassiruemyj |(bazovyj) | | |process ------- | | STDIN | | | |.....|-STDIN--> |----------> |--STDIN--->| |_____| | | | | klaviatura *--------* *--------* master slave */ /* Opros deskriptorov */ void wm_select(){ int nready; int nfds; int maxfd; int nopen; /* chislo oprashivaemyh deskriptorov */ register f; fd_set set, rset; /* maski */ struct timeval timeout, rtimeout; FD_ZERO(&set); nopen = 0; /* ochistka maski */ FD_SET (PP.pfd, &set); nopen++; /* uchest' v maske */ FD_SET (STDIN, &set); nopen++; maxfd = max(PP.pfd, STDIN); timeout.tv_sec = 3600; /* sekund */ timeout.tv_usec = 0; /* millisekund */ A. Bogatyrev, 1992-95 - 270 - Si v UNIX nfds = maxfd + 1; while( nopen ){ rset = set; rtimeout = timeout; /* oprosit' deskriptory */ if((nready = select( nfds, &rset, NULL, NULL, &rtimeout )) <= 0) continue; for(f=0; f < nfds; f++ ) if( FD_ISSET(f, &rset)){ /* deskriptor f gotov */ int n; if((n = read(f, buf, sizeof buf)) <= 0 ){ FD_CLR(f, &set); nopen--; /* isklyuchit' */ close(f); } else { int fdout; /* uchet i kontrol' */ if( f == PP.pfd ){ fdout = STDOUT; PP.out_bytes += n; if( fpscript ) fwrite(buf, 1, n, fpscript); } else if( f == STDIN ) { fdout = PP.pfd; PP.in_bytes += n; if( halfflag && fpscript ) fwrite(buf, 1, n, fpscript); if( autoecho ) write(STDOUT, buf, n); } write(fdout, buf, n); } } } } A. Bogatyrev, 1992-95 - 271 - Si v UNIX int main(ac, av) char **av; { while( ac > 1 && *av[1] == '-' ){ switch(av[1][1]){ case 's': scriptflg++; break; case 'f': av++; ac--; protocol = av[1]; scriptflg++; break; case 'h': halfflag++; break; case 'a': autoecho++; break; default: fprintf(stderr, "Bad key %s\n", av[1]); break; } ac--; av++; } if( scriptflg ){ fpscript = fopen( protocol, "w" ); } ac--; av++; wm_init(); PP = wm_ptypair(); if( PP.pfd < 0 ){ fprintf(stderr, "Cannot get pty. Please wait and try again.\n"); return 1; } wm_fixtty(); wm_startshell(ac, av); go++; wm_select(); wm_done(0); /* NOTREACHED */ return 0; } Dannyj razdel prosto privodit ishodnyj tekst prostogo interpretatora komand. Funkciya match opisana v glave "Tekstovaya obrabotka". A. Bogatyrev, 1992-95 - 272 - Si v UNIX /* Primitivnyj interpretator komand. Raspoznaet postrochno * komandy vida: CMD ARG1 ... ARGn <FILE >FILE >>FILE >&FILE >>&FILE * Sborka: cc -U42 -DCWDONLY sh.c match.c pwd.c -o sh */ #include <sys/types.h>/* opredelenie tipov, ispol'zuemyh sistemoj */ #include <stdio.h> /* opisanie biblioteki vvoda/vyvoda */ #include <signal.h> /* opisanie signalov */ #include <fcntl.h> /* opredelenie O_RDONLY */ #include <errno.h> /* kody sistemnyh oshibok */ #include <ctype.h> /* makrosy dlya raboty s simvolami */ #include <dirent.h> /* emulyaciya fajlovoj sistemy BSD 4.2 */ #include <pwd.h> /* rabota s /etc/passwd */ #include <sys/wait.h> /* opisanie formata wait() */ char cmd[256]; /* bufer dlya schityvaniya komandy */ #define MAXARGS 256 /* maks. kolichestvo argumentov */ char *arg[MAXARGS]; /* argumenty komandy */ char *fin, *fout; /* imena dlya perenapravleniya vvoda/vyvoda */ int rout; /* flagi perenapravleniya vyvoda */ char *firstfound; /* imya najdennoj, no nevypolnyaemoj programmy */ #define LIM ':' /* razdelitel' imen katalogov v path */ extern char *malloc(), *getenv(), *strcpy(), *getwd(); extern char *strchr(), *execat(); extern void callshell(), printenv(), setenv(), dowait(), setcwd(); extern struct passwd *getpwuid(); /* Predopredelennye peremennye */ extern char **environ; /* okruzhenie: iznachal'no smotrit na tot zhe * massiv, chto i ev iz main() */ extern int errno; /* kod oshibki sistemnogo vyzova */ char *strdup(s)char *s; { char *p; return(p=malloc(strlen(s)+1), strcpy(p,s)); } /* strcpy() vozvrashchaet svoj pervyj argument */ char *str3spl(s, p, q) char *s, *p, *q; { char *n = malloc(strlen(s)+strlen(p)+strlen(q)+1); strcpy(n, s); strcat(n, p); strcat(n, q); return n; } int cmps(s1, s2) char **s1, **s2; { return strcmp(*s1, *s2); } A. Bogatyrev, 1992-95 - 273 - Si v UNIX /* Perenapravit' vyvod */ #define APPEND 0x01 #define ERRTOO 0x02 int output (name, append, err_too, created) char *name; int *created; { int fd; *created = 0; /* Sozdan li fajl ? */ if( append ){ /* >>file */ /* Fajl name sushchestvuet? Probuem otkryt' na zapis' */ if((fd = open (name, O_WRONLY)) < 0) { if (errno == ENOENT) /* Fajl eshche ne sushchestvoval */ goto CREATE; else return 0; /* Ne imeem prava pisat' v etot fajl */ } /* inache fd == otkrytyj fajl, *created == 0 */ }else{ CREATE: /* Pytaemsya sozdat' (libo opustoshit') fajl "name" */ if((fd = creat (name, 0666)) < 0 ) return 0; /* Ne mogu sozdat' fajl */ else *created = 1; /* Byl sozdan novyj fajl */ } if (append) lseek (fd, 0l, 2); /* na konec fajla */ /* perenapravit' standartnyj vyvod */ dup2(fd, 1); if( err_too ) dup2(fd, 2); /* err_too=1 dlya >& */ close(fd); return 1; } /* Perenapravit' vvod */ int input (name) char *name; { int fd; if((fd = open (name, O_RDONLY)) < 0 ) return 0;/* Ne mogu chitat' */ /* perenapravit' standartnyj vvod */ dup2(fd, 0); close(fd); return 1; } A. Bogatyrev, 1992-95 - 274 - Si v UNIX /* zapusk komandy */ int cmdExec(progr, av, envp, inp, outp, outflg) char *progr; /* imya programmy */ char **av; /* spisok argumentov */ char **envp; /* okruzhenie */ char *inp, *outp; /* fajly vvoda-vyvoda (perenapravleniya) */ int outflg; /* rezhimy perenapravleniya vyvoda */ { void (*del)(), (*quit)(); int pid; int cr = 0; del = signal(SIGINT, SIG_IGN); quit = signal(SIGQUIT, SIG_IGN); if( ! (pid = fork())){ /* vetvlenie */ /* porozhdennyj process (syn) */ signal(SIGINT, SIG_DFL); /* vosstanovit' reakcii */ signal(SIGQUIT,SIG_DFL); /* po umolchaniyu */ /* getpid() vydaet nomer (identifikator) dannogo processa */ printf( "Process pid=%d zapushchen\n", pid = getpid()); /* Perenapravit' vvod-vyvod */ if( inp ) if(!input( inp )){ fprintf(stderr, "Ne mogu <%s\n", inp ); goto Err; } if( outp ) if(!output (outp, outflg & APPEND, outflg & ERRTOO, &cr)){ fprintf(stderr, "Ne mogu >%s\n", outp ); goto Err; } /* Zamenit' programmu: pri uspehe * dannaya programma zavershaetsya, a vmesto nee vyzyvaetsya * funkciya main(ac, av, envp) programmy, hranyashchejsya v fajle progr. * ac vychislyaet sistema. */ execvpe(progr, av, envp); Err: /* pri neudache pechataem prichinu i zavershaem porozhdennyj process */ perror(firstfound ? firstfound: progr); /* My ne delaem free(firstfound),firstfound = NULL * potomu chto dannyj process zavershaetsya (i tem VSYA ego * pamyat' osvobozhdaetsya) : */ if( cr && outp ) /* byl sozdan novyj fajl */ unlink(outp); /* no teper' on nam ne nuzhen */ exit(errno); } /* process - otec */ /* Sejchas signaly ignoriruyutsya, wait ne mozhet byt' oborvan * preryvaniem s klaviatury */ dowait(); /* ozhidat' okonchaniya syna */ /* vosstanovit' reakcii na signaly ot klaviatury */ signal(SIGINT, del); signal(SIGQUIT, quit); return pid; /* vernut' identifikator syna */ } A. Bogatyrev, 1992-95 - 275 - Si v UNIX /* Zapusk programmy s poiskom po peremennoj sredy PATH */ int execvpe(progr, av, envp) char *progr, **av, **envp; { char *path, *cp; int try = 1; register eacces = 0; char fullpath[256]; /* polnoe imya programmy */ firstfound = NULL; if((path = getenv("PATH")) == NULL ) path = ".:/bin:/usr/bin:/etc"; /* imya: korotkoe ili put' uzhe zadan ? */ cp = strchr(progr, '/') ? "" : path; do{ /* probuem raznye varianty */ cp = execat(cp, progr, fullpath); retry: fprintf(stderr, "probuem \"%s\"\n", fullpath ); execve(fullpath, av, envp); /* esli programma zapustilas', to na etom meste dannyj * process zamenilsya novoj programmoj. Inache - oshibka. */ switch( errno ){ /* kakova prichina neudachi ? */ case ENOEXEC: /* eto komandnyj fajl */ callshell(fullpath, av, envp); return (-1); case ETXTBSY: /* fajl zapisyvaetsya */ if( ++try > 5 ) return (-1); sleep(try); goto retry; case EACCES: /* ne imeete prava */ if(firstfound == NULL) firstfound = strdup(fullpath); eacces++; break; case ENOMEM: /* programma ne lezet v pamyat' */ case E2BIG: return (-1); } }while( cp ); if( eacces ) errno = EACCES; return (-1); } /* Sklejka ocherednoj komponenty path i imeni programmy name */ static char *execat(path, name, buf) register char *path, *name; char *buf; /* gde budet rezul'tat */ { register char *s = buf; while(*path && *path != LIM ) *s++ = *path++; /* imya kataloga */ if( s != buf ) *s++ = '/'; while( *name ) *s++ = *name++; /* imya programmy */ *s = '\0'; return ( *path ? ++path /* propustiv LIM */ : NULL ); } A. Bogatyrev, 1992-95 - 276 - Si v UNIX /* Zapusk komandnogo fajla pri pomoshchi vyzova interpretatora */ void callshell(progr, av, envp) char *progr, **av, **envp; { register i; char *sh; char *newav[MAXARGS+2]; int fd; char first = 0; if((fd = open(progr, O_RDONLY)) < 0 ) sh = "/bin/sh"; else{ read(fd, &first, 1); close(fd); sh = (first == '#') ? "/bin/csh" : "/bin/sh"; } newav[0] = "Shellscript"; newav[1] = progr; for(i=1; av[i]; i++) newav[i+1] = av[i]; newav[i+1] = NULL; printf( "Vyzyvaem %s\n", sh ); execve(sh, newav, envp); } /* Ozhidat' okonchaniya vseh processov, vydat' prichiny smerti. */ void dowait(){ int ws; int pid; while((pid = wait( &ws)) > 0 ){ if( WIFEXITED(ws)){ printf( "Process %d umer s kodom %d\n", pid, WEXITSTATUS(ws)); }else if( WIFSIGNALED(ws)){ printf( "Process %d ubit signalom %d\n", pid, WTERMSIG(ws)); if(WCOREDUMP(ws)) printf( "Obrazovalsya core\n" ); /* core - obraz pamyati processa dlya otladchika adb */ }else if( WIFSTOPPED(ws)){ printf( "Process %d ostanovlen signalom %d\n", pid, WSTOPSIG(ws)); } } } A. Bogatyrev, 1992-95 - 277 - Si v UNIX /* Rasshirenie shablonov imen. |to uproshchennaya versiya, kotoraya * rasshiryaet imena tol'ko v tekushchem kataloge. */ void glob(dir, args, indx, str /* chto rasshiryat' */, quote ) char *args[], *dir; int *indx; char *str; char quote; /* kavychki, v kotorye zaklyuchena stroka str */ { static char globchars[] = "*?["; char *p; char **start = &args[ *indx ]; short nglobbed = 0; register struct dirent *dirbuf; DIR *fd; extern DIR *opendir(); /* Zatychka dlya otmeny globbinga: */ if( *str == '\\' ){ str++; goto noGlob; } /* Obrabotka peremennyh $NAME */ if( *str == '$' && quote != '\'' ){ char *s = getenv(str+1); if( s ) str = s; } /* Analiz: trebuetsya li globbing */ if( quote ) goto noGlob; for( p=str; *p; p++ ) /* Est' li simvoly shablona? */ if( strchr(globchars, *p)) goto doGlobbing; noGlob: args[ (*indx)++ ] = strdup(str); return; doGlobbing: if((fd = opendir (dir)) == NULL){ fprintf(stderr, "Can't read %s\n", dir); return; } while ((dirbuf = readdir (fd)) != NULL ) { if (dirbuf->d_ino == 0) continue; if (strcmp (dirbuf->d_name, ".") == 0 || strcmp (dirbuf->d_name, "..") == 0) continue; if( match( dirbuf->d_name, str)){ args[ (*indx)++ ] = strdup(dirbuf->d_name); nglobbed++; } } closedir(fd); if( !nglobbed){ printf( "%s: no match\n", str); goto noGlob; }else{ /* otsortirovat' */ qsort(start, nglobbed, sizeof (char *), cmps); } } A. Bogatyrev, 1992-95 - 278 - Si v UNIX /* Razbor komandnoj stroki */ int parse(s) register char *s; { int i; register char *p; char tmp[80]; /* ocherednoj argument */ char c; /* ochistka staryh argumentov */ for(i=0; arg[i]; i++) free(arg[i]), arg[i] = NULL; if( fin ) free(fin ), fin = NULL; if( fout ) free(fout), fout = NULL; rout = 0; /* razbor stroki */ for( i=0 ;; ){ char quote = '\0'; /* propusk probelov - razdelitelej slov */ while((c = *s) && isspace(c)) s++; if( !c ) break; /* ocherednoe slovo */ p = tmp; if(*s == '\'' || *s == '"' ){ /* argument v kavychkah */ quote = *s++; /* simvol kavychki */ while((c = *s) != '\0' && c != quote){ if( c == '\\' ){ /* zaekranirovano */ c = *++s; if( !c ) break; } *p++ = c; ++s; } if(c == '\0') fprintf(stderr, "Net zakryvayushchej kavychki %c\n", quote); else s++; /* proignorirovat' kavychku na konce */ A. Bogatyrev, 1992-95 - 279 - Si v UNIX } else while((c = *s) && !isspace(c)){ if(c == '\\') /* zaekranirovano */ if( !(c = *++s)) break /* while */; *p++ = c; s++; } *p = '\0'; /* Proverit', ne est' li eto perenapravlenie * vvoda/vyvoda. V otlichie ot sh i csh * zdes' nado pisat' >FAJL <FAJL * >< vplotnuyu k imeni fajla. */ p = tmp; /* ocherednoe slovo */ if( *p == '>'){ /* perenapravlen vyvod */ p++; if( fout ) free(fout), rout = 0; /* uzhe bylo */ if( *p == '>' ){ rout |= APPEND; p++; } if( *p == '&' ){ rout |= ERRTOO; p++; } if( !*p ){ fprintf(stderr, "Net imeni dlya >\n"); fout = NULL; rout = 0; } else fout = strdup(p); } else if( *p == '<' ){ /* perenapravlen vvod */ p++; if( fin ) free(fin); /* uzhe bylo */ if( !*p ){ fprintf(stderr, "Net imeni dlya <\n"); fin = NULL; } else fin = strdup(p); } else /* dobavit' imena k argumentam */ glob( ".", arg, &i, p, quote ); } arg[i] = NULL; return i; } /* Ustanovit' imya pol'zovatelya */ void setuser(){ int uid = getuid(); /* nomer pol'zovatelya, zapustivshego SHell */ char *user = "mr. Nobody"; /* imya pol'zovatelya */ char *home = "/tmp"; /* ego domashnij katalog */ struct passwd *pp = getpwuid( uid ); if( pp != NULL ){ if(pp->pw_name && *pp->pw_name ) user = pp->pw_name; if( *pp->pw_dir ) home = pp->pw_dir; } setenv("USER", user); setenv("HOME", home); } void setcwd(){ /* Ustanovit' imya tekushchego kataloga */ char cwd[512]; getwd(cwd); setenv( "CWD", cwd ); } A. Bogatyrev, 1992-95 - 280 - Si v UNIX void main(ac, av, ev) char *av[], *ev[]; { int argc; /* kolichestvo argumentov */ char *prompt; /* priglashenie */ setuser(); setcwd(); signal(SIGINT, SIG_IGN); setbuf(stdout, NULL); /* otmenit' buferizaciyu */ for(;;){ prompt = getenv( "prompt" ); /* setenv prompt -->\ */ printf( prompt ? prompt : "@ ");/* priglashenie */ if( gets(cmd) == NULL /* at EOF */ ) exit(0); argc = parse(cmd); if( !argc) continue; if( !strcmp(arg[0], "exit" )) exit(0); if( !strcmp(arg[0], "cd" )){ char *d = (argc==1) ? getenv("HOME"):arg[1]; if(chdir(d) < 0) printf( "Ne mogu vojti v %s\n", d ); else setcwd(); continue; } if( !strcmp(arg[0], "echo" )){ register i; FILE *fp; if( fout ){ if((fp = fopen(fout, rout & APPEND ? "a":"w")) == NULL) continue; } else fp = stdout; for(i=1; i < argc; i++ ) fprintf( fp, "%s%s", arg[i], i == argc-1 ? "\n":" "); if( fp != stdout ) fclose(fp); continue; } if( !strcmp(arg[0], "setenv" )){ if( argc == 1 ) printenv(); else if( argc == 2 ) setenv( arg[1], "" ); else setenv( arg[1], arg[2]); continue; } cmdExec(arg[0], (char **) arg, environ, fin, fout, rout); } } A. Bogatyrev, 1992-95 - 281 - Si v UNIX /* -----------------------------------------------------------*/ /* Otsortirovat' i napechatat' okruzhenie */ void printenv(){ char *e[40]; register i = 0; char *p, **q = e; do{ p = e[i] = environ[i]; i++; } while( p ); #ifdef SORT qsort( e, --i /* skol'ko */, sizeof(char *), cmps); #endif while( *q ) printf( "%s\n", *q++ ); } /* Sravnenie imeni peremennoj okruzheniya s name */ static char *envcmp(name, evstr) char *name, *evstr; { char *p; int code; if((p = strchr(evstr, '=')) == NULL ) return NULL; /* error ! */ *p = '\0'; /* vremenno */ code = strcmp(name, evstr); *p = '='; /* vosstanovili */ return code==0 ? p+1 : NULL; } /* Ustanovit' peremennuyu okruzheniya */ void setenv( name, value ) char *name, *value; { static malloced = 0; /* 1, esli environ peremeshchen */ char *s, **p, **newenv; int len, change_at = (-1), i; /* Est' li peremennaya name v environ-e ? */ for(p = environ; *p; p++ ) if(s = envcmp(name, *p)){ /* uzhe est' */ if((len = strlen(s)) >= strlen(value)){ /* dostatochno mesta */ strcpy(s, value); return; } /* Esli eto novyj environ ... */ if( malloced ){ free( *p ); *p = str3spl(name, "=", value); return; } /* inache sozdaem kopiyu environ-a */ change_at = p - environ; /* indeks */ break; } A. Bogatyrev, 1992-95 - 282 - Si v UNIX /* Sozdaem kopiyu environ-a. Esli change_at == (-1), to * rezerviruem novuyu yachejku dlya eshche ne opredelennoj peremennoj */ for(p=environ, len=0; *p; p++, len++ ); /* vychislili kolichestvo peremennyh */ if( change_at < 0 ) len++; if((newenv = (char **) malloc( sizeof(char *) * (len+1))) == (char **) NULL) return; for(i=0; i < len+1; i++ ) newenv[i] = NULL; /* zachistka */ /* Kopiruem staryj environ v novyj */ if( !malloced ) /* ishodnyj environ v steke (dan sistemoj) */ for(i=0; environ[i]; i++ ) newenv[i] = strdup(environ[i]); else for(i=0; environ[i]; i++ ) newenv[i] = environ[i]; /* Vo vtorom sluchae stroki uzhe byli spaseny, kopiruem ssylki */ /* Izmenyaem, esli nado: */ if( change_at >= 0 ){ free( newenv[change_at] ); newenv[change_at] = str3spl(name, "=", value); } else { /* dobavit' v konec novuyu peremennuyu */ newenv[len-1] = str3spl(name, "=", value); } /* podmenit' environ */ if( malloced ) free( environ ); environ = newenv; malloced++; qsort( environ, len, sizeof(char *), cmps); } /* Dopishite komandy: unsetenv imya_peremennoj - udalyaet peremennuyu sredy; exit N - zavershaet interpretator s kodom vozvrata N (eto celoe chislo); */ A. Bogatyrev, 1992-95 - 283 - Si v UNIX Pod "tekstovoj obrabotkoj" (v protivoves "vychislitel'nym zadacham") zdes' ponima- etsya ogromnyj klass zadach obrabotki informacii nechislovogo haraktera, naprimer redak- tirovanie teksta, formatirovanie dokumentov, poisk i sortirovka, bazy dannyh, leksi- cheskij i sintaksicheskij analiz, pechat' na printere, preobrazovanie formata tablic, i.t.p. 7.1. Napishite programmu, "ugadyvayushchuyu" slovo iz zaranee zadannogo spiska po pervym neskol'kim bukvam. Vydajte soobshchenie "neodnoznachno", esli est' neskol'ko pohozhih slov. Uslozhnite programmu tak, chtoby spisok slov schityvalsya v programmu pri ee zapuske iz fajla list.txt 7.2. Napishite programmu, kotoraya udvaivaet probely v tekste s odinochnymi probelami. 7.3. Napishite programmu, kotoraya kopiruet vvod na vyvod, zamenyaya kazhduyu posledova- tel'nost' iz idushchih podryad neskol'kih probelov i/ili tabulyacij na odin probel. Shema ee resheniya shodna s resheniem sleduyushchej zadachi. 7.4. Napishite programmu podscheta slov v fajle. Slovo opredelite kak posledovatel'- nost' simvolov, ne vklyuchayushchuyu simvoly probela, tabulyacii ili novoj stroki. "Kanoni- cheskij" variant resheniya, privedennyj u Kernigana i Ritchi, takov: #include <ctype.h> #include <stdio.h> const int YES=1, NO=0; main(){ register int inWord = NO; /* sostoyanie */ int words = 0, c; while((c = getchar()) != EOF) if(isspace(c) || c == '\n') inWord = NO; else if(inWord == NO){ inWord = YES; ++words; } printf("%d slov\n", words); } Obratite vnimanie na konstrukciyu const. |to ob®yavlenie imen kak konstant. |ta konst- rukciya blizka k #define YES 1 no pozvolyaet kompilyatoru - bolee strogo proveryat' tip, t.k. eto tipizirovannaya konstanta; - sozdavat' bolee ekonomnyj kod; - zapreshchaet izmenyat' eto znachenie. Rassmotrim primer main(){ /* cc 00.c -o 00 -lm */ double sqrt(double); const double sq12 = sqrt(12.0); #define SQRT2 sqrt(2.0) double x; x = sq12 * sq12 * SQRT2 * SQRT2; /* @1 */ sq12 = 3.4641; /* @2 */ printf("%g %g\n", sq12, x); } Ispol'zovanie #define prevratit stroku @1 v x = sq12 * sq12 * sqrt(2.0) * sqrt(2.0); to est' sozdast kod s dvumya vyzovami funkcii sqrt. Konstrukciya zhe const zanosit vychislennoe vyrazhenie v yachejku pamyati i dalee prosto ispol'zuet ee znachenie. Pri etom A. Bogatyrev, 1992-95 - 284 - Si v UNIX kompilyator ne pozvolyaet vposledstvii izmenyat' eto znachenie, poetomu stroka @2 oshi- bochna. Teper' predlozhim eshche odnu programmu podscheta slov, gde slovo opredelyaetsya makro- som isWord, perechislyayushchim bukvy dopustimye v slove. Programma osnovana na pereklyucha- tel'noj tablice funkcij (etot podhod primenim vo mnogih sluchayah): #include <ctype.h> #include <stdio.h> int wordLength, inWord, words; /* = 0 */ char aWord[128], *wrd; void space (c){} void letter (c){ wordLength++; *wrd++ = c; } void begWord(c){ wordLength=0; inWord=1; wrd=aWord; words++; letter(c); } void endWord(c){ inWord=0; *wrd = '\0'; printf("Slovo '%s' dliny %d\n", aWord, wordLength); } void (*sw[2][2])() = { /* !isWord */ { space, endWord }, /* isWord */ { begWord, letter } /* !inWord inWord */ }; #define isWord(c) (isalnum(c) || c=='-' || c=='_') main(){ register c; while((c = getchar()) != EOF) (*sw[isWord(c)][inWord])(c); printf("%d slov\n", words); } 7.5. Napishite programmu, vydayushchuyu gistogrammu dlin strok fajla (t.e. tablicu: strok dliny 0 stol'ko-to, dliny 1 - stol'ko-to, i.t.p., prichem tablicu mozhno izobrazit' graficheski). 7.6. Napishite programmu, kotoraya schityvaet slovo iz fajla in i zapisyvaet eto slovo v konec fajla out. 7.7. Napishite programmu, kotoraya budet pechatat' slova iz fajla vvoda, prichem po odnomu na stroku. 7.8. Napishite programmu, pechatayushchuyu gistogrammu dlin slov iz fajla vvoda. 7.9. Napishite programmu, chitayushchuyu slova iz fajla i razmeshchayushchuyu ih v vide dvunaprav- lennogo spiska slov, otsortirovannogo po alfavitu. Ukazaniya: ispol'zujte dinamicheskuyu pamyat' (malloc) i ukazateli; napishite funkciyu vklyucheniya novogo slova v spisok na nuzh- noe mesto. V konce raboty raspechatajte spisok dvazhdy: v pryamom i v obratnom poryadke. Uslozhnenie: ne hranit' v spiske dublikaty; vmesto etogo vmeste so slovom hranit' schetchik kolichestva ego vhozhdenij v tekst. 7.10. Napishite programmu, kotoraya pechataet slova iz svoego fajla vvoda, raspolozhen- nye v poryadke ubyvaniya chastoty ih poyavleniya. Pered kazhdym slovom napechatajte chislo chastoty ego poyavleniya. 7.11. Napishite programmu, chitayushchuyu fajl postrochno i pechatayushchuyu slova v kazhdoj stroke v obratnom poryadke. A. Bogatyrev, 1992-95 - 285 - Si v UNIX 7.12. Napishite programmu kopirovaniya vvoda na vyvod takim obrazom, chtoby iz kazhdoj gruppy posledovatel'no odinakovyh strok vyvodilas' tol'ko odna stroka. |to analog programmy uniq v sisteme UNIX. Otvet: #include <stdio.h> /* char *gets(); */ char buf1[4096], buf2[4096]; char *this = buf1, *prev = buf2; main(){ long nline =0L; char *tmp; while( gets(this)){ if(nline){ /* sravnit' novuyu i predydushchuyu stroki */ if( strcmp(this, prev)) /* razlichny ? */ puts(prev); } /* obmen buferov: */ tmp=prev; prev=this; this=tmp; nline++; /* nomer stroki */ }/* endwhile */ if( nline ) puts(prev); /* poslednyaya stroka vsegda vydaetsya */ } 7.13. Sostav'te programmu, kotoraya budet udalyat' v konce (i v nachale) kazhdoj stroki fajla probely i tabulyacii, a takzhe udalyat' stroki, celikom sostoyashchie iz probelov i tabulyacij. 7.14. Dlya ekonomii mesta v fajle, redaktory tekstov pri zapisi otredaktirovannogo fajla szhimayut podryad idushchie probely v tabulyaciyu. CHasto eto neudobno dlya programm obrabotki tekstov (poskol'ku trebuet osoboj obrabotki tabulyacij - eto ODIN simvol, kotoryj na ekrane i v tekste zanimaet NESKOLXKO pozicij!), poetomu pri chtenii fajla my dolzhny rasshiryat' tabulyacii v nuzhnoe kolichestvo probelov, naprimer tak: /* zamenyat' tabulyacii na probely */ void untab(s) register char *s; { char newstr[256]; /* novaya stroka */ char *src = s; int n; /* schetchik */ register dstx; /* koordinata x v novoj stroke */ for(dstx = 0; *s != '\0'; s++) if( *s == '\t'){ for(n = 8 - dstx % 8 ; n > 0 ; n--) newstr[dstx++] = ' '; }else newstr[dstx++] = *s; newstr[dstx] = '\0'; strcpy(src, newstr); /* stroku na staroe mesto */ } 7.15. Napishite obratnuyu funkciyu, szhimayushchuyu podryad idushchie probely v tabulyacii. A. Bogatyrev, 1992-95 - 286 - Si v UNIX void tabify(){ int chr; int icol, ocol; /* input/output columns */ for(icol = ocol = 0; ; ){ if((chr = getchar()) == EOF) break; switch(chr){ case ' ': icol++; break; case '\n': case '\r': ocol = icol = 0; putchar(chr); break; case '\t': icol += 8; icol &= ~07; /* icol -= icol % 8; */ break; default: while(((ocol + 8) & ~07) <= icol){ #ifdef NOTDEF if(ocol + 1 == icol) break; /* vzyat' ' ' vmesto '\t' */ #endif putchar('\t'); ocol += 8; ocol &= ~07; } while(ocol < icol){ putchar(' '); ocol++; } putchar(chr); icol++; ocol++; break; } } } 7.16. Sostav'te programmu, ukorachivayushchuyu stroki ishodnogo fajla do zadannoj velichiny i pomeshchayushchuyu rezul'tat v ukazannyj fajl. Uchtite, chto tabulyaciya razvorachivaetsya v nes- kol'ko probelov! 7.17. Razrabotajte programmu, ukorachivayushchuyu stroki vhodnogo fajla do 60 simvolov. Odnako teper' zapreshchaetsya obrubat' slova. A. Bogatyrev, 1992-95 - 287 - Si v UNIX 7.18. Razrabotajte programmu, zapolnyayushchuyu promezhutki mezhdu slovami stroki dopolni- tel'nymi probelami takim obrazom, chtoby dlina stroki byla ravna 60 simvolam. 7.19. Napishite programmu, perenosyashchuyu slishkom dlinnye stroki. Slova razbivat' nel'zya (neumeshayushcheesya slovo sleduet perenesti celikom). SHirinu stroki schitat' ravnoj 60. 7.20. Sostav'te programmu, centriruyushchuyu stroki fajla otnositel'no serediny ekrana, t.e. dobavlyayushchuyu v nachalo stroki takoe kolichestvo probelov, chtoby seredina stroki pechatalas' v 40-oj pozicii (schitaem, chto obychnyj ekran imeet shirinu 80 simvolov). 7.21. Napishite programmu, otsekayushchuyu n probelov v nachale kazhdoj stroki (ili n pervyh lyubyh simvolov). Uchtite, chto v fajle mogut byt' stroki koroche n (naprimer pustye stroki). #include <stdio.h> /* ... tekst funkcii untab(); ... */ void process(char name[], int n, int spacesOnly){ char line[256]; int length, shift, nline = 0; char newname[128]; FILE *fpin, *fpout; if((fpin = fopen(name, "r")) == NULL){ fprintf(stderr, "Ne mogu chitat' %s\n", name); return; } sprintf(newname, "_%s", name); /* naprimer */ if((fpout = fopen(newname, "w")) == NULL){ fprintf(stderr, "Ne mogu sozdat' %s\n", newname); fclose(fpin); return; } while(fgets(line, sizeof line, fpin)){ ++nline; if((length = strlen(line)) && line[length-1] == '\n') line[--length] = '\0'; /* obrubit' '\n' */ untab(line); /* razvernut' tabulyacii */ for(shift=0; line[shift] != '\0' && shift < n ; ++shift) if(spacesOnly && line[shift] != ' ') break; if(*line && shift != n ) /* Preduprezhdenie */ fprintf(stderr, "Nachalo stroki #%d slishkom korotko\n", nline); fprintf(fpout, "%s\n", line+shift); /* nel'zya bylo fputs(line+n, fpout); * t.k. eta poziciya mozhet byt' ZA koncom stroki */ } fclose(fpin); fclose(fpout); } void main(int argc, char **argv){ if( argc != 3 ) exit(1); process(argv[2], atoi(argv[1]) /* 8 */, 1); exit(0); } 7.22. Napishite programmu, razbivayushchuyu fajl na dva po vertikali: v pervyj fajl popa- daet levaya polovina ishodnogo fajla, vo vtoroj - pravaya. SHirinu kolonki zadavajte iz argumentov main(). Esli zhe argument ne ukazan - 40 pozicij. 7.23. Napishite programmu sortirovki strok v alfavitnom poryadke. Uchtite, chto funkciya strcmp() sravnivaet stroki v poryadke kodirovki, prinyatoj na dannoj konkretnoj mashine. Russkie bukvy, kak pravilo, idut ne v alfavitnom poryadke! Sleduet napisat' funkciyu A. Bogatyrev, 1992-95 - 288 - Si v UNIX dlya alfavitnogo sravneniya otdel'nyh simvolov i, pol'zuyas' eyu, perepisat' funkciyu strcmp(). 7.24. Otsortirujte massiv strok po leksikograficheskomu ubyvaniyu, ignoriruya razlichiya mezhdu strochnymi i propisnymi bukvami. 7.25. Sostav'te programmu dihotomicheskogo poiska v otsortirovannom massive strok (metodom deleniya popolam). /* Poisk v tablice metodom polovinnogo deleniya: dihotomia */ #include <stdio.h> struct elem { char *name; /* klyuch poiska */ int value; } table[] = { /* imena strogo po alfavitu */ { "andrew", 17 }, { "bill", 23 }, { "george", 55 }, { "jack", 54 }, { "jaw", 43 }, { "john", 33 }, { "mike", 99 }, { "paul", 21 }, { "sue", 66 }, /* SIZE - 2 */ { NULL, -1 }, /* SIZE - 1 */ /* NULL vveden tol'ko dlya raspechatki tablicy */ }; #define SIZE (sizeof(table) / sizeof(struct elem)) /* Dihotomicheskij poisk po tablice */ struct elem *find(s, table, size) char *s; /* chto najti ? */ struct elem table[]; /* v chem ? */ int size; /* sredi pervyh size elementov */ { register top, bottom, middle; register code; top = 0; /* nachalo */ bottom = size - 1; /* konec: indeks stroki "sue" */ while( top <= bottom ){ middle = (top + bottom) / 2; /* seredina */ /* sravnit' stroki */ code = strcmp( s, table[middle].name ) ; if( code > 0 ){ top = middle + 1; }else if( code < 0 ){ bottom = middle - 1; }else return &table[ middle ]; } return (struct elem *) NULL; /* ne nashel */ } A. Bogatyrev, 1992-95 - 289 - Si v UNIX /* raspechatka tablicy */ void printtable(tbl) register struct elem *tbl; { for( ; tbl->name != NULL ; tbl++ ){ printf( "%-15s %d\n", tbl->name, tbl->value ); } } int main(){ char buf[80]; struct elem *ptr; printtable(table); for(;;){ printf( "-> " ); if( gets( buf ) == NULL) break; /* EOF */ if( ! strcmp( buf, "q" )) exit(0); /* quit: vyhod */ ptr = find( buf, table, SIZE-1 ); if( ptr ) printf( "%d\n", ptr->value ); else { printf( "--- Ne najdeno ---\n" ); printtable(table); } } return 0; } 7.26. Napishem funkciyu, kotoraya preobrazuet stroku tak, chto pri ee pechati bukvy v nej budut podcherknuty, a cifry - vydeleny zhirno. Format teksta s vydeleniyami, kotoryj sozdaetsya etim primerom, yavlyaetsya obshcheprinyatym v UNIX i raspoznaetsya nekotorymi prog- rammami: naprimer, programma prosmotra fajlov less (more) vydelyaet takie bukvy na ekrane special'nymi shriftami ili inversiej fona. #define LEN 9 /* potom napishite 256 */ char input[] = "(xxx+yyy)/123.75=?"; char output[LEN]; void main( void ){ int len=LEN, i; void bi_conv(); char c; bi_conv(input, output, &len); if(len > LEN){ printf("Uvelich' LEN do %d\n", len); len = LEN; /* dostupnyj maksimum */ } for(i=0; i < len && (c = output[i]); ++i) putchar(c); putchar('\n'); } /* Zamet'te, chto include-fajly ne obyazatel'no * dolzhny vklyuchat'sya v samom nachale programmy! */ #include <stdio.h> #include <ctype.h> #define PUT(c) { count++; \ if(put < *len){ *p++ = (c); ++put;}} #define GET() (*s ? *s++ : EOF) void bi_conv( A. Bogatyrev, 1992-95 - 290 - Si v UNIX /*IN*/ char *s, /*OUT*/ char *p, /*INOUT*/ int *len ){ int count, put, c; for(count=put=0; (c=GET()) != EOF; ){ /* zhirnyj: C\bC */ /* podcherknutyj: _\bC */ if(isalpha(c)){ PUT('_'); PUT('\b'); } else if(isdigit(c)){ PUT( c ); PUT('\b'); } PUT(c); } PUT('\0'); /* zakryt' stroku */ *len = count; #undef PUT #undef GET } Napishite programmu dlya podobnoj obrabotki fajla. Zametim, chto dlya etogo ne nuzhny promezhutochnye stroki input i output i postrochnoe chtenie fajla; vse, chto nado sdelat', eto opredelit' #define PUT(c) if(c)putchar(c) #define GET() getchar() Napishite podobnuyu funkciyu, udvaivayushchuyu bukvy v ssttrrookkee. 7.27. Napishite programmu, udalyayushchuyu iz fajla vydeleniya. Dlya etogo nado prosto uda- lyat' posledovatel'nosti vida C\b #include <stdio.h> #define NOPUT (-1) /* ne simvol ASCII */ /* Nazvaniya shriftov - v perechislimom tipe */ typedef enum { NORMAL=1, ITALICS, BOLD, RED=BOLD } font; int ontty; font textfont; /* tekushchee vydelenie */ #define setfont(f) textfont=(f) #define getfont() (textfont) #define SetTtyFont(f) if(ontty) tfont(f) /* Ustanovit' vydelenie na ekrane terminala */ void tfont(font f){ /* tol'ko dlya ANSI terminala */ static font ttyfont = NORMAL; if(ttyfont == f) return; printf("\033[0m"); /* set NORMAL font */ switch(ttyfont = f){ case NORMAL: /* uzhe sdelano vyshe */ break; case BOLD: printf("\033[1m"); break; case ITALICS: /* use reverse video */ printf("\033[7m"); break; } } void put(int c){ /* Vyvod simvola tekushchim cvetom */ if(c == NOPUT) return; /* '\b' */ SetTtyFont(getfont()); putchar(c); setfont(NORMAL); /* Ozhidat' novoj C\b posl-ti */ } void main(){ register int c, cprev = NOPUT; /* Standartnyj vyvod - eto terminal ? */ ontty = isatty(fileno(stdout)); setfont(NORMAL); while((c = getchar()) != EOF){ A. Bogatyrev, 1992-95 - 291 - Si v UNIX if(c == '\b'){ /* vydelenie */ if((c = getchar()) == EOF) break; if(c == cprev) setfont(BOLD); else if(cprev == '_') setfont(ITALICS); else /* nalozhenie A\bB */ setfont(RED); } else put(cprev); cprev = c; } put(cprev); /* poslednyaya bukva fajla */ SetTtyFont(NORMAL); } 7.28. Napishite programmu pechati na printere listinga Si-programm. Klyuchevye slova yazyka vydelyajte dvojnoj nadpechatkoj. Dlya vydachi na terminal napishite programmu, pod- cherkivayushchuyu klyuchevye slova (podcherkivanie - v sleduyushchej stroke). Uproshchenie: vyde- lyajte ne klyuchevye slova, a bol'shie bukvy. Ukazanie: dlya dvojnoj pechati ispol'zujte upravlyayushchij simvol '\r' - vozvrat k nachalu toj zhe stroki; zatem stroka pechataetsya povtorno, pri etom simvoly, kotorye ne dolzhny pechatat'sya zhirno, sleduet zamenit' na probely (ili na tabulyaciyu, esli etot simvol sam est' '\t'). 7.29. Napishite programmu, pechatayushchuyu teksty Si-programm na printere. Vydelyajte klyu- chevye slova yazyka zhirnym shriftom, stroki "stroka", simvoly 'c' i kommentarii - kursi- vom. SHrifty dlya EPSON-FX sovmestimyh printerov (naprimer EP-2424) pereklyuchayutsya takimi upravlyayushchimi posledovatel'nostyami (ESC oznachaet simvol '\033'): VKLYUCHENIE VYKLYUCHENIE zhirnyj shrift (bold) ESC G ESC H utolshchennyj shrift (emphasized) ESC E ESC F kursiv (italics) ESC 4 ESC 5 podcherkivanie (underline) ESC - 1 ESC - 0 povyshennoe kachestvo pechati ESC x 1 ESC x 0 (near letter quality) nlq draft verhnie indeksy (superscript) ESC S 0 ESC T nizhnie indeksy (subscript) ESC S 1 ESC T szhatyj shrift (17 bukv/dyujm) '\017' '\022' (condensed) dvojnaya shirina bukv ESC W 1 ESC W 0 (expanded) proporcional'naya pechat' ESC p 1 ESC p 0 (proportional spacing) Mozhno vklyuchit' odnovremenno neskol'ko iz perechislennyh vyshe rezhimov. V kazhdoj iz sleduyushchih dvuh grupp nado vybrat' odno iz treh: pitch (plotnost' pechati) pica (10 bukv/dyujm) ESC P elite (12 bukv/dyujm) ESC M micron (15 bukv/dyujm) ESC g font (shrift) chernovik (draft (Roman)) ESC k '\0' tekst (text (Sans Serif)) ESC k '\1' kur'er (courier) ESC k '\2' Vsyudu vyshe 0 oznachaet libo '0' libo '\0'; 1 oznachaet libo '1' libo '\1'. Primer: printf( "This is \033Gboldface\033H word\n"); A. Bogatyrev, 1992-95 - 292 - Si v UNIX 7.30. Sostav'te programmu vyvoda nabora fajlov na pechat', nachinayushchuyu kazhdyj ochered- noj fajl s novoj stranicy i pechatayushchuyu pered kazhdym fajlom zagolovok i nomer tekushchej stranicy. Ispol'zujte simvol '\f' (form feed) dlya perevoda lista printera. 7.31. Napishite programmu pechati teksta v dve kolonki. Ispol'zujte bufer dlya formi- rovaniya lista: fajl chitaetsya postrochno (slishkom dlinnye stroki obrubat'), snachala zapolnyaetsya levaya polovina lista (bufera), zatem pravaya. Kogda list polnost'yu zapol- nen ili fajl konchilsya - vydat' list postrochno, raspisat' bufer probelami (ochistit' list) i povtorit' zapolnenie ocherednogo lista. Ukazanie: razmery lista dolzhny pereda- vat'sya kak argumenty main(), dlya bufera ispol'zujte dvumernyj massiv bukv, pamyat' dlya nego zakazyvajte dinamicheski. Uslozhnenie: ne obrubajte, a perenosite slishkom dlinnye stroki (stroka mozhet potrebovat' dazhe perenosa s lista na list). /* PROGRAMMA PECHATI V DVE POLOSY: pr.c */ #include <stdio.h> #include <string.h> #define YES 1 #define NO 0 #define FORMFEED '\f' #define LINEFEED '\n' extern char *malloc(unsigned); extern char *strchr(char *, char); void untab(register char *s); void resetsheet( void ); void addsheet( char *s, FILE *fpout ); void flushsheet( FILE *fpout ); void printline( int y, char *s, char *attr, FILE *fpout ); void doattr( register char *abuf, register char *vbuf ); void printcopy( FILE *fpin, FILE *fpout ); void main(void); char *strdup (const char *s){ char *p = malloc(strlen(s)+1); strcpy(p,s); return p; /* return strcpy((char *) malloc(strlen(s)+1), s); */ } /* ... tekst funkcii untab() ... */ int Sline; /* stroka na liste */ int Shalf; /* polovina lista */ int npage; /* nomer stranicy */ int startpage = 1; /* pechat' nachinaya s 1oj stranicy */ int fline; /* nomer stroki fajla */ int topline = 0; /* smeshchenie do nachala lista */ int halfwidth; /* shirina polulista */ int twocolumns = YES; /* v dve kolonki ? */ int lshift, rshift = 1; /* polya sleva i sprava */ typedef unsigned short ushort; int COLS = 128; /* shirina lista (bukv) */ int LINES = 66; /* dlina lista (strok) */ ushort *mem; /* bufer lista */ #define AT(x,y) mem[ (x) + (y) * COLS ] /* Vydelit' bufer pod list i zachistit' ego */ void resetsheet ( void ){ register x; if( mem == NULL ){ /* vydelit' pamyat' */ A. Bogatyrev, 1992-95 - 293 - Si v UNIX if ((mem = (ushort *) malloc (COLS * LINES * sizeof(ushort))) == NULL ){ fprintf(stderr, "Out of memory.\n"); exit(1); } } /* ochistit' */ for( x= COLS * LINES - 1 ; x >= 0 ; x-- ) mem[x] = ' ' & 0xFF; halfwidth = (twocolumns ? COLS/2 : COLS ) - (lshift + rshift ); Sline = topline; Shalf = 0; } #define NEXT_HALF \ if( twocolumns == YES && Shalf == 0 ){ \ /* zakryt' dannuyu polovinu lista */ \ Shalf = 1; /* perejti k novoj polovine */ \ Sline = topline; \ } else \ flushsheet(fpout) /* napechatat' list */ /* Zapisat' stroku v list */ void addsheet ( char *s, FILE *fpout ) { register x, y; register i; char *rest = NULL; int wrap = NO; /* YES kogda idet perenos slishkom dlinnoj stroki */ /* v kakoe mesto pomestit' stroku? */ x = (Shalf == 0 ? 0 : COLS/2) + lshift; y = Sline; i = 0; /* poziciya v stroke s */ while (*s) { if( *s == '\f' ){ /* vynuzhdennyj form feed */ rest = strdup( s+1 ); /* ostatok stroki */ NEXT_HALF; if( *rest ) addsheet(rest, fpout); free( rest ); return; } if( i >= halfwidth ){ /* perenesti dlinnuyu stroku */ wrap = YES; rest = strdup(s); break; } /* Obrabotka vydelenij teksta */ if( s[1] == '\b' ){ while( s[1] == '\b' ){ AT(x, y) = (s[0] << 8) | (s[2] & 0xFF); /* overstrike */ s += 2; } s++; x++; i++; } else { AT (x, y) = *s++ & 0xFF; A. Bogatyrev, 1992-95 - 294 - Si v UNIX x++; i++; } } /* Uvelichit' stroku/polovinu_lista */ Sline++; if (Sline == LINES) { /* polulist zapolnen */ NEXT_HALF; } if( wrap && rest ) { /* dopisat' ostatok stroki */ addsheet(rest, fpout); free(rest); } } int again; /* nuzhna li povtornaya nadpechatka? */ /* Napechatat' zapolnennyj list */ void flushsheet ( FILE *fpout ){ register x, y, xlast; char *s, *p; static char outbuf[BUFSIZ], attr[BUFSIZ]; /* attr - bufer pod atributy vydelenij */ ushort c; if( npage >= startpage ) for (y = 0; y < LINES; y++) { /* obrezat' koncevye probely */ for (xlast = (-1), x = COLS - 1; x >= 0; x--) if (AT (x, y) != ' ') { xlast = x; break; } again = NO; s = outbuf; p = attr; for (x = 0; x <= xlast; x++){ c = AT(x, y); *s++ = c & 0xFF; /* imeet atributy ? */ c >>= 8; c &= 0xFF; *p++ = c ? c : ' '; if( c ) again = YES; } *s = '\0'; *p = '\0'; printline(y, outbuf, attr, fpout); } npage++; /* next page */ resetsheet(); /* zachistit' novyj list */ } /* Napechatat' odnu stroku lista */ void printline ( int y, char *s, char *attr, FILE *fpout ){ register x; if( again ){ doattr(attr, s); fprintf(fpout, "%s\r", attr ); } fprintf(fpout, "%s", s); /* perevod lista ili stroki */ fputc( y == LINES-1 ? FORMFEED : LINEFEED, fpout ); } /* Proverit' - net li atributov vydelenij */ void doattr ( register char *abuf, register char *vbuf ){ for(; *abuf; abuf++, vbuf++ ) if( !strchr(" _-!|\177", *abuf)) *abuf = *vbuf; } A. Bogatyrev, 1992-95 - 295 - Si v UNIX /* Kopirovanie fajla na printer */ void printcopy ( FILE *fpin, FILE *fpout ) { char inbuf[BUFSIZ]; npage = 1; /* pervaya stranica imeet nomer 1 */ fline = 0; /* tekushchaya stroka fajla - 0 */ resetsheet(); /* zachistit' bufer lista */ while( fgets(inbuf, sizeof inbuf - 1, fpin ) != NULL ){ register l = strlen( inbuf ); if( l && inbuf[l-1] == '\n' ) inbuf[--l] = '\0' ; fline++; untab ( inbuf ); addsheet( inbuf, fpout ); } if( !(Sline == topline && Shalf == 0)) /* esli stranica ne byla tol'ko chto zachishchena ... */ flushsheet(fpout); fprintf(stderr, "%d strok, %d listov.\n", fline, npage-1); } /* Vyzov: pr < fajl > /dev/lp */ void main (){ printcopy(stdin, stdout); } Fajl-printer imeet v UNIX imya /dev/lp ili podobnoe emu, a v MS DOS - imya prn. 7.32. Napishite programmu, kotoraya postrochno schityvaet nebol'shoj fajl v pamyat' i pechataet stroki v obratnom poryadke. Ukazanie: ispol'zujte dinamicheskuyu pamyat' - funkcii malloc() i strcpy(). Ob®yasnim, pochemu zhelatel'no pol'zovat'sya dinamicheskoj pamyat'yu. Pust' my znaem, chto stroki imeyut maksimal'nuyu dlinu 80 simvolov i maksimal'noe kolichestvo strok ravno 50. My mogli by hranit' tekst v dvumernom massive: char text[50][80]; zanimayushchem 50*80 = 4000 bajt pamyati. Pust' teper' okazalos', chto stroki fajla v dejstvitel'nosti imeyut dlinu po 10 bukv. My ispol'zuem 50 * (10 + 1) = 550 bajt ne ispol'zuem 4000 - 50 * (10 + 1) = 3450 bajt (+1 nuzhen dlya simvola '\0' na konce stroki). Pust' my teper' pishem char *text[50]; int i=0; i pri chtenii ocherednoj stroki sohranyaem ee tak: char buffer[81], *malloc(), *gets(); while( gets(buffer) != NULL ){ text[i] = (char *) malloc(strlen(buffer)+1); /* +1 dlya hraneniya \0, kotoryj ne uchten strlen-om */ strcpy(text[i++], buffer); } to est' zakazyvaem rovno stol'ko pamyati, skol'ko nado dlya hraneniya stroki i ni bajtom bol'she. Zdes' my (esli sizeof(char *)==4) ispol'zuem A. Bogatyrev, 1992-95 - 296 - Si v UNIX 50 * 4 + 50 * (10 + 1 + 4) = 950 bajt massiv ukazatelej + zakazannaya malloc pamyat' (+4 - sluzhebnaya informaciya malloc), no zato u nas ne ostaetsya neispol'zuemoj pamyati. Preimushchestvom vydeleniya pamyati v vide massiva yavlyaetsya to, chto eta pamyat' vydelitsya GARANTIROVANNO, togda kak malloc()-u mozhet ne hvatit' pamyati (esli my ee prezhde ochen' mnogo zahvatyvali i ne osvobozhdali free()). Esli malloc ne mozhet vydelit' uchastok pamyati trebuemogo razmera, on vozvrashchaet znachenie NULL: if((text[i] = malloc(....)) == NULL) { fprintf(stderr, "Malo pamyati\n"); break; } Raspechatka strok: for(--i; i >= 0; i-- ){ printf("%s\n", text[i]); free( text[i] ); } Funkciya free(ptr) "osvobozhdaet"|- otvedennuyu ranee malloc()om ili calloc()om oblast' pamyati po adresu ptr tak, chto pri novyh vyzovah malloc() eta oblast' mozhet byt' pere- ispol'zovana. Dannye v osvobozhdennoj pamyati PORTYATSYA posle free(). Oshibochno (i opasno) osvobozhdat' pamyat', kotoraya NE BYLA otvedena malloc()-om! Organizaciya teksta v vide massiva ssylok na stroki ili spiska ssylok na stroki, a ne v vide dvumernogo tekstovogo polya, vygodna eshche tem, chto takie stroki proshche perestavlyat', sortirovat', vstavlyat' stroku v tekst, udalyat' stroku iz teksta. Pri etom perestavlyayutsya lish' ukazateli v linejnom massive, a sami stroki nikuda ne kopi- ruyutsya. V dvumernom zhe bajtovom massive nam prishlos' by dlya teh zhe perestanovok kopirovat' celye massivy bajt - stroki etoj tekstovoj matricy. 7.33. Napishite programmu, pechatayushchuyu stroki fajla v obratnom poryadke. Ne schityvat' fajl celikom v pamyat'! Sleduet ispol'zovat' metod "obratnogo chteniya" libo metod "bystrogo dostupa" k strokam fajla, opisannyj v glave "Rabota s fajlami". ____________________ |- Na samom dele vse osvobozhdennye kuski vklyuchayutsya v spisok svobodnoj pamyati, i skleivayutsya vmeste, esli dva osvobozhdennyh kuska okazalis' ryadom. Pri novyh vyzovah malloc snachala prosmatrivaetsya spisok svobodnoj pamyati - net li tam oblasti dostatoch- nogo razmera? |tot algoritm opisan u Kernigana i Ritchi. A. Bogatyrev, 1992-95 - 297 - Si v UNIX /* Invertirovanie poryadka strok v fajle. * Ispol'zuetsya ta ideya, chto fajl-rezul'tat imeet tot zhe * razmer, chto i ishodnyj */ #include <sys/types.h> #include <sys/stat.h> #include <stdio.h> #define BUFS 4096 /* maksimal'naya dlina stroki */ void main(int argc, char **argv ) { FILE *fp; struct stat st; long len; char buffer[ BUFS+1 ]; FILE *fpnew; /* inversnyj fajl */ int lgt; if( argc != 2 ){ printf("Error: must be filename\n"); exit(1); } if( (fp= fopen( argv[1], "r" )) == NULL ){ printf( "Can not open %s\n", argv[1] ); exit(2); } stat( argv[1], &st ); /* fstat(fileno(fp), &st); */ len = st.st_size; /* dlina fajla v bajtah */ if( (fpnew = fopen( "inv.out", "w" ))== NULL ){ printf("Can not create file\n"); exit(3); } while( fgets( buffer, sizeof buffer, fp ) != NULL ){ lgt = strlen( buffer ); fseek(fpnew, len - lgt , 0); /* Pomnite, chto smeshchenie u lseek i fseek - * eto chislo tipa long, a ne int. * Poetomu luchshe vsegda pisat' * lseek(fd, (long) off, whence); */ len -= lgt; fprintf( fpnew, "%s", buffer ); /* ili luchshe fputs(buffer, fpnew); */ } fclose( fp ); fclose( fpnew ); } 7.34. Napishite programmu, kotoraya chitaet fajl, sostoyashchij iz "blokov" teksta, razde- lennyh pustymi strokami. Razmer "bloka" ogranichen. Programma gotovit fajl dlya pechati na printer tak, chtoby ni odin blok ne razbivalsya na chasti: A. Bogatyrev, 1992-95 - 298 - Si v UNIX ----------- ----------- |###### A | |###### A | list1 |#### A | prevrashchat' |#### A | |##### A | v |##### A | | | | | |###### B | | | ----------- ----------- |#### B | |###### B | list2 | | |#### B | ... | | to est' esli blok ne umeshchaetsya na ostatke lista, on dolzhen byt' perenesen na sleduyu- shchij list. Bloki sleduet razdelyat' odnoj pustoj strokoj (no pervaya stroka lista ne dolzhna byt' pustoj!). Esli blok dlinnee stranicy - ne perenosite ego. /* Reshenie zadachi o perenose blokov teksta, * esli oni ne umeshchayutsya na ostatke lista */ #include <stdio.h> #include <ctype.h> extern void *malloc(unsigned); extern int atoi(char *); FILE *fpin = stdin, *fpout = stdout; /* Spasti stroku v dinamicheski vydelennoj pamyati */ char *strdup (const char *s) { char *ptr = (char *) malloc (strlen (s) + 1); if( ptr ) strcpy (ptr, s); return ptr; } int page_length = 66; /* dlina stranicy */ int current_line; /* tekushchaya stroka na stranice (s nulya) */ int numbered = 0; /* numerovat' stroki lista ? */ #define MAXLINES 256 /* maks. dlina bloka */ int stored = 0; /* zapomneno strok */ char *lines[MAXLINES]; /* zapomnennye stroki */ /* Zapomnit' stroku bloka v bufer strok */ void remember (char *s) { if (stored >= MAXLINES) { fprintf (stderr, "Slishkom dlinnyj blok.\n"); return; } else if((lines[stored++] = strdup (s)) == NULL ){ fprintf (stderr, "Malo pamyati (Out of memory).\n"); exit(13); } } /* Perehod na sleduyushchuyu stranicu */ void newpage () { current_line = 0; putc('\f', fpout); } A. Bogatyrev, 1992-95 - 299 - Si v UNIX /* Perevod stroki ili lista */ void newline (void) { if (current_line == page_length - 1) newpage (); /* nachat' novyj list */ else { current_line++; if( numbered ) fprintf(fpout, "%02d\n", current_line); else putc ('\n', fpout); } } /* Perehod na sleduyushchuyu stranicu vstavkoj pustyh strok */ void nextpage () { while (current_line != 0) newline (); } /* Vydat' spasennyj blok */ void throwout () { register i; for (i = 0; i < stored; i++) { if( numbered ) fprintf(fpout, "%02d %s", current_line, lines[i]); else fputs (lines[i], fpout); newline (); free (lines[i]); } stored = 0; } /* Vydat' blok, perenosya na sleduyushchij list esli nado */ void flush () { int rest_of_page = page_length - current_line; /* ostalos' pustyh strok na stranice */ if ((stored > page_length && rest_of_page < page_length / 4) || rest_of_page < stored) nextpage (); throwout (); if (current_line) /* ne pervaya stroka lista */ newline (); /* razdelitel' blokov */ } /* Obrabotat' vhodnoj fajl */ void process () { char buffer[512]; int l; while (fgets (buffer, sizeof buffer, fpin) != NULL) { if ((l = strlen (buffer)) && buffer[l - 1] == '\n') buffer[ --l] = '\0'; if (l) remember (buffer); /* a po pustoj stroke - vydat' blok */ else if (stored) flush (); } if (stored) flush (); nextpage(); } A. Bogatyrev, 1992-95 - 300 - Si v UNIX void main (int argc, char *argv[]) { argc--; argv++; while (*argv) { if (**argv == '-') { char *key = *argv + 1, *arg; switch (*key) { case 'l': if (! key[1]) { if( argv[1] ){ arg = argv[1]; argv++; argc--; } else arg = ""; } else arg = key+1; if( isdigit(*arg) ){ page_length = atoi(arg); fprintf (stderr, "Dlina stranicy: %d strok\n", page_length); } else fprintf(stderr, "-l CHISLO\n"); break; case 'n': numbered++; break; default: fprintf (stderr, "Neizvestnyj klyuch %s\n", key); break; } } argv++; argc--; } process (); exit(0); } 7.35. Sostav'te programmu vyvoda strok fajla v inversnom otobrazhenii, prichem poryadok simvolov v strokah takzhe sleduet invertirovat'. Naprimer, abcdef ... oklmn 987654321 ..... prevrashchat' v ..... 123456789 nmlko ... fedcba Programma dolzhna byt' sostavlena dvumya sposobami: pri pomoshchi obratnogo chteniya fajla i rekursivnym vyzovom samoj funkcii invertirovaniya. Ukazanie: pri obratnom chtenii nado chitat' fajl bol'shimi kuskami (blokami). 7.36. Napishite programmu, chitayushchuyu fajl postrochno i razmeshchayushchuyu stroki v otsortiro- vannoe dvoichnoe derevo. Po koncu fajla - raspechatajte eto derevo. Ukazanie: ispol'- zujte dinamicheskuyu pamyat' i rekursiyu. A. Bogatyrev, 1992-95 - 301 - Si v UNIX /* Dvoichnaya sortirovka strok pri pomoshchi dereva */ #include <stdio.h> char buf[240]; /* bufer vvoda */ int lines; /* nomer stroki fajla */ typedef struct node{ struct _data{ /* DANNYE */ char *key; /* klyuch - stroka */ int line; /* nomer stroki */ } data; /* SLUZHEBNAYA INFORMACIYA */ struct node *l, /* levoe podderevo */ *r; /* pravoe podderevo */ } Node; Node *root = NULL; /* koren' dereva (ssylka na verhnij uzel) */ /* Otvedenie pamyati i inicializaciya novogo uzla */ Node *newNode(s) char *s; /* stroka */ { Node *tmp; extern char *malloc(); /* vydelitel' pamyati */ tmp = (Node *) malloc(sizeof(Node)); if( tmp == NULL ){ fprintf( stderr, "Net pamyati.\n"); exit(1); } tmp -> l = tmp -> r = NULL; /* net podderev'ev */ tmp -> data.line = lines; /* nomer stroki fajla */ tmp -> data.key = malloc( strlen(s) + 1 ); /* +1 - pod bajt '\0' v konce stroki */ strcpy(tmp -> data.key, s); /* kopiruem klyuch v uzel */ return tmp; } int i; /* Vyneseno v staticheskuyu pamyat', chtoby pri kazhdom * rekursivnom vyzove ne sozdavalas' novaya auto-peremennaya, * a ispol'zovalas' odna i ta zhe staticheskaya */ A. Bogatyrev, 1992-95 - 302 - Si v UNIX /* Rekursivnaya pechat' dereva */ void printtree(root, tree, level, c) Node *root; /* koren' dereva */ Node *tree; /* derevo */ int level; /* uroven' */ char c; /* imya poddereva */ { if( root == NULL ){ printf("Derevo pusto.\n"); return; } if( tree == NULL ) return; /* esli est' - raspechatat' levoe podderevo */ printtree (root, tree -> l, level + 1, '/'); /* 'L' */ /* raspechatat' klyuch uzla */ for( i=0; i < level; i++ ) printf(" "); printf("%c%3d--\"%s\"\n", c, tree-> data.line, tree -> data.key); /* esli est' - raspechatat' pravoe podderevo */ printtree(root, tree -> r, level + 1, '\\'); /* 'R' */ } void prTree(tree) Node *tree; { printtree(tree, tree, 0, '*'); } /* Dobavit' uzel s klyuchom key v derevo tree */ void addnode(tree, key) Node **tree; /* v kakoe derevo dobavlyat': adres peremennoj, * soderzhashchej ssylku na kornevoj uzel */ char *key; /* klyuch uzla */ { #define TREE (*tree) if( TREE == NULL ){ /* derevo poka pusto */ TREE = newNode( key ); return; } /* inache est' hot' odin uzel */ if ( strcmp (key, TREE -> data.key) < 0 ) { /* dobavit' v levoe podderevo */ if ( TREE -> l == NULL ){ /* net levogo dereva */ TREE -> l = newNode(key); return; } else addnode( & TREE ->l , key); } A. Bogatyrev, 1992-95 - 303 - Si v UNIX else{ /* dobavit' v pravoe derevo */ if ( TREE -> r == NULL ){ /* net pravogo poddereva */ TREE -> r = newNode(key); return; } else addnode ( & TREE ->r, key) ; } } /* Procedura udaleniya iz dereva po klyuchu. */ typedef struct node *NodePtr; static NodePtr delNode; /* udalyaemaya vershina */ void delete(key, tree) char *key; /* klyuch udalyaemogo elementa */ NodePtr *tree; /* iz kakogo dereva udalyat' */ { extern void doDelete(); if(*tree == NULL){ printf( "%s ne najdeno\n", key ); return; } /* poisk klyucha */ else if(strcmp(key, (*tree)->data.key) < 0) delete( key, &(*tree)->l ); else if(strcmp(key, (*tree)->data.key) > 0) delete( key, &(*tree)->r ); else{ /* klyuch najden */ delNode = *tree; /* ukazatel' na udalyaemyj uzel */ if(delNode->r == NULL) *tree = delNode->l; else if(delNode->l == NULL) *tree = delNode->r; else doDelete( & delNode->l ); free(delNode); } } static void doDelete(rt) NodePtr *rt; { if( (*rt)->r != NULL ) /* spusk po pravoj vetvi */ doDelete( &(*rt)->r ); else{ /* perenos dannyh v drugoj uzel */ delNode->data = (*rt)->data; delNode = *rt; /* dlya free() */ *rt = (*rt)->l; } } A. Bogatyrev, 1992-95 - 304 - Si v UNIX void main(){ extern char *gets(); char *s; while (gets(buf) != NULL){ /* poka ne konec fajla */ lines++; addnode( & root, buf ); } prTree(root); /* udalim stroku */ freopen("/dev/tty", "r", stdin); do{ printf( "chto udalit' ? " ); if((s = gets(buf)) == NULL) break; delete(buf, &root); prTree( root ); } while( s && root ); printf("Bye-bye.\n"); exit(0); } 7.37. Napishite programmu, kotoraya chitaet so standartnogo vvoda 10 chisel libo slov, a zatem raspechatyvaet ih. Dlya hraneniya vvedennyh dannyh ispol'zujte ob®edinenie. #include <stdio.h> #include <ctype.h> #define INT 'i' #define STR 's' struct data { char tag; /* teg, pometka. Kod tipa dannyh. */ union { int i; char *s; } value; } a[10]; int counter = 0; /* schetchik */ void main(){ char word[128]; int i; char *malloc(unsigned); /* CHtenie: */ for(counter=0; counter < 10; counter++){ if( gets(word) == NULL ) break; if( isdigit((unsigned char) *word)){ a[counter].value.i = atoi(word); a[counter].tag = INT; } else { a[counter].value.s = malloc(strlen(word)+1); strcpy(a[counter].value.s, word); a[counter].tag = STR; } } /* Raspechatka: */ for(i=0; i < counter; i++) switch(a[i].tag){ case INT: printf("chislo %d\n", a[i].value.i); break; case STR: printf("slovo %s\n", a[i].value.s); free(a[i].value.s); break; } A. Bogatyrev, 1992-95 - 305 - Si v UNIX } 7.38. Rassmotrim zadachu napisaniya funkcii, kotoraya obrabatyvaet peremennoe chislo argumentov, naprimer funkciyu-generator menyu. V takuyu funkciyu nado podavat' stroki menyu i adresa funkcij, vyzyvaemyh pri vybore kazhdoj iz strok. Sobstvenno problema, kotoruyu my tut obsuzhdaem - kak peredavat' peremennoe chislo argumentov v podobnye funkcii? My privedem tri programmy ispol'zuyushchie tri razlichnyh podhoda. Predpochtenie ne otdano ni odnomu iz nih - kazhdyj iz nih mozhet okazat'sya effektivnee drugih v opre- delennyh situaciyah. Dumajte sami! 7.38.1. Massiv /* Peredacha argumentov v funkciyu kak MASSIVA. * Sleduet yavno ukazat' chislo argumentov v massive. */ #include <stdio.h> /* printf(), NULL */ #include <string.h> /* strdup() */ #include <stdlib.h> /* malloc() */ #define A_INT 1 #define A_STR 2 #define A_NULL 0 typedef struct arg { int type; union jack { char *s; int d; } data; struct arg *next; } Arg; void doit(Arg args[], int n){ int i; for(i=0; i < n; i++) switch(args[i].type){ case A_INT: printf("%d", args[i].data.d); break; case A_STR: printf("%s", args[i].data.s); break; default: fprintf(stderr, "Unknown type!\n"); break; } } A. Bogatyrev, 1992-95 - 306 - Si v UNIX /* Pri inicializacii union nado ispol'zovat' tip * pervogo iz perechislennyh znachenij. */ Arg sample[] = { { A_INT, (char *) 123 }, { A_STR, (char *) " hello, " }, { A_INT, (char *) 456 }, { A_STR, (char *) " world\n" } }; int main(int ac, char *av[]){ doit(sample, sizeof sample / sizeof sample[0]); return 0; } 7.38.2. Spisok /* Peredacha argumentov v funkciyu kak SPISKA. * Dostoinstvo: spisok mozhno modificirovat' * vo vremya vypolneniya programmy: dobavlyat' i * udalyat' elementy. Nedostatok tot zhe: spisok nado * postroit' dinamicheski vo vremya vypolneniya, * zaranee etogo sdelat' nel'zya. * Nedostatkom dannoj programmy yavlyaetsya takzhe to, * chto spisok ne unichtozhaetsya posle ispol'zovaniya. * V C++ eta problema reshaetsya pri pomoshchi ispol'zovaniya * avtomaticheski vyzyvaemyh destruktorov. */ #include <stdio.h> /* printf(), NULL */ #include <string.h> /* strdup() */ #include <stdlib.h> /* malloc() */ #define A_INT 1 #define A_STR 2 #define A_NULL 0 typedef struct arg { int type; union jack { char *s; int d; } data; struct arg *next; } Arg; A. Bogatyrev, 1992-95 - 307 - Si v UNIX void doit(Arg *arglist){ for( ; arglist; arglist=arglist->next) switch(arglist->type){ case A_INT: printf("%d", arglist->data.d); break; case A_STR: printf("%s", arglist->data.s); break; default: fprintf(stderr, "Unknown type!\n"); break; } } Arg *new_int(int n, Arg *next){ Arg *ptr = (Arg *) malloc(sizeof(Arg)); ptr->type = A_INT; ptr->data.d = n; ptr->next = next; return ptr; } Arg *new_str(char *s, Arg *next){ Arg *ptr = (Arg *) malloc(sizeof(Arg)); ptr->type = A_STR; ptr->data.s = strdup(s); ptr->next = next; return ptr; } int main(int ac, char *av[]){ doit( new_int(123, new_str(" hello, ", new_int(456, new_str(" world\n", NULL)))) ); return 0; } 7.38.3. Funkciya s peremennym chislom parametrov /* Peredacha argumentov v funkciyu kak SPISKA ARGUMENTOV * FUNKCII s priznakom konca spiska. */ #include <stdio.h> /* printf(), NULL */ #include <stdarg.h> /* va_... */ #define A_INT 1 #define A_STR 2 #define A_NULL 0 A. Bogatyrev, 1992-95 - 308 - Si v UNIX void doit(...){ /* peremennoe chislo argumentov */ va_list args; /* vtoroj parametr - argument, predshestvuyushchij ... * Esli takogo net - stavim zapyatuyu i pustoe mesto! */ va_start(args, ); for(;;){ switch(va_arg(args, int)){ case A_INT: printf("%d", va_arg(args, int)); break; case A_STR: printf("%s", va_arg(args, char *)); break; case A_NULL: goto breakloop; default: fprintf(stderr, "Unknown type!\n"); break; } } breakloop: va_end(args); } int main(int ac, char *av[]){ doit( A_INT, 123, A_STR, " hello, ", A_INT, 456, A_STR, " world\n", A_NULL ); return 0; } 7.39. Napishite neskol'ko funkcij dlya raboty s uproshchennoj bazoj dannyh. Zapis' v baze dannyh soderzhit klyuch - celoe, i stroku fiksirovannoj dliny: struct data { int b_key; /* klyuch */ char b_data[ DATALEN ]; /* informaciya */ }; Napishite: - dobavlenie zapisi - unichtozhenie po klyuchu - poisk po klyuchu (i pechat' stroki) - obnovlenie po klyuchu. Fajl organizovan kak nesortirovannyj massiv zapisej bez dublikatov (t.e. klyuchi ne mogut povtoryat'sya). Poisk proizvodit' linejno. Ispol'zujte funkcii fread, fwrite, fseek. Poslednyaya funkciya pozvolyaet vam pozicionirovat'sya k n-oj zapisi fajla: fseek( fp, (long) n * sizeof(struct data), 0 ); Perepishite etu programmu, ob®yaviv klyuch kak stroku, naprimer A. Bogatyrev, 1992-95 - 309 - Si v UNIX char b_key[ KEYLEN ]; Esli stroka-klyuch koroche KEYLEN simvolov, ona dolzhna okanchivat'sya '\0', inache - ispol'zuyutsya vse KEYLEN bukv i '\0' na konce otsutstvuet (tak zhe ustroeno pole d_name v katalogah fajlovoj sistemy). Usovershenstvujte algoritm dostupa, ispol'zuya heshiro- vanie po klyuchu (hash - peremeshivanie, sm. primer v prilozhenii). Vynesite klyuchi v otdel'nyj fajl. |tot fajl klyuchej sostoit iz struktur struct record_header { int b_key ; /* klyuch */ long b_offset; /* adres zapisi v fajle dannyh */ int b_length; /* dlina zapisi (neobyazatel'no) */ }; to est' organizovan analogichno nashej pervoj baze dannyh. Snachala vy ishchete nuzhnyj klyuch v fajle klyuchej. Pole b_offset u najdennogo klyucha zadaet adres dannogo v drugom fajle. CHtoby prochitat' ego, nado sdelat' fseek na rasstoyanie b_offset v fajle dannyh i prochest' b_length bajt. 7.40. Organizujte bazu dannyh v fajle kak spisok zapisej. V kazhdoj zapisi vmesto klyucha dolzhen hranit'sya nomer ocherednoj zapisi (ssylka). Napishite funkcii: poiska dan- nyh v spiske (po znacheniyu), dobavleniya dannyh v spisok v alfavitnom poryadke, (oni prosto pripisyvayutsya k koncu fajla, no v nuzhnyh mestah perestavlyayutsya ssylki), raspe- chatki spiska v poryadke ssylok, udaleniyu elementov iz spiska (iz samogo fajla oni ne udalyayutsya!). Ssylka (nomer) pervoj zapisi (golovy spiska) hranitsya v pervyh dvuh bajtah fajla, rassmatrivaemyh kak short. Vvedite optimizaciyu: napishite funkciyu dlya sortirovki fajla (prevrashcheniyu pereme- shannogo spiska v linejnyj) i vycherkivaniya iz nego udalennyh zapisej. Pri etom fajl budet perezapisan. Esli fajl otsortirovan, to poisk v nem mozhno proizvodit' bolee effektivno, chem proslezhivanie cepochki ssylok: prosto linejnym prosmotrom. Tretij bajt fajla ispol'zujte kak priznak: 1 - fajl byl otsortirovan, 0 - posle sortirovki v nego bylo chto-to dobavleno i linejnyj poryadok narushen. 7.41. Napishite funkciyu match(stroka,shablon); dlya proverki sootvetstviya stroki upro- shchennomu regulyarnomu vyrazheniyu v stile SHell. Metasimvoly shablona: * - lyuboe chislo lyubyh simvolov (0 i bolee); ? - odin lyuboj simvol. Uslozhnenie: [bukvy] - lyubaya iz perechislennyh bukv. [!bukvy] - lyubaya iz bukv, krome perechislennyh. [h-z] - lyubaya iz bukv ot h do z vklyuchitel'no. Ukazanie: dlya proverki "ostatka" stroki ispol'zujte rekursivnyj vyzov etoj zhe funk- cii. Ispol'zuya etu funkciyu, napishite programmu, kotoraya vydelyaet iz fajla SLOVA, udovletvoryayushchie zadannomu shablonu (naprimer, "[Ii]*o*t"). Imeetsya v vidu, chto kazhduyu stroku nado snachala razbit' na slova, a potom proverit' kazhdoe slovo. A. Bogatyrev, 1992-95 - 310 - Si v UNIX #include <stdio.h> #include <string.h> #include <locale.h> #define U(c) ((c) & 0377) /* podavlenie rasshireniya znaka */ #define QUOT '\\' /* ekraniruyushchij simvol */ #ifndef MATCH_ERR # define MATCH_ERR printf("Net ]\n") #endif /* s - sopostavlyaemaya stroka * p - shablon. Simvol \ otmenyaet specznachenie metasimvola. */ int match (register char *s, register char *p) { register int scc; /* tekushchij simvol stroki */ int c, cc, lc; /* lc - predydushchij simvol v [...] spiske */ int ok, notflag; for (;;) { scc = U(*s++); /* ocherednoj simvol stroki */ switch (c = U (*p++)) { /* ocherednoj simvol shablona */ case QUOT: /* a*\*b */ c = U (*p++); if( c == 0 ) return(0); /* oshibka: pattern\ */ else goto def; case '[': /* lyuboj simvol iz spiska */ ok = notflag = 0; lc = 077777; /* dostatochno bol'shoe chislo */ if(*p == '!'){ notflag=1; p++; } while (cc = U (*p++)) { if (cc == ']') { /* konec perechisleniya */ if (ok) break; /* sopostavilos' */ return (0); /* ne sopostavilos' */ } if (cc == '-') { /* interval simvolov */ if (notflag){ /* ne iz diapazona - OK */ if (!syinsy (lc, scc, U (*p++))) ok++; /* iz diapazona - neudacha */ else return (0); } else { /* simvol iz diapazona - OK */ if (syinsy (lc, scc, U (*p++))) ok++; } } else { if (cc == QUOT){ /* [\[\]] */ cc = U(*p++); if(!cc) return(0);/* oshibka */ } if (notflag){ if (scc && scc != (lc = cc)) ok++; /* ne vhodit v spisok */ else return (0); } else { A. Bogatyrev, 1992-95 - 311 - Si v UNIX if (scc == (lc = cc)) /* vhodit v spisok */ ok++; } } } if (cc == 0){ /* konec stroki */ MATCH_ERR; return (0); /* oshibka */ } continue; case '*': /* lyuboe chislo lyubyh simvolov */ if (!*p) return (1); for (s--; *s; s++) if (match (s, p)) return (1); return (0); case 0: return (scc == 0); default: def: if (c != scc) return (0); continue; case '?': /* odin lyuboj simvol */ if (scc == 0) return (0); continue; } } } /* Proverit', chto smy lezhit mezhdu smax i smin */ int syinsy (unsigned smin, unsigned smy, unsigned smax) { char left [2]; char right [2]; char middle [2]; left [0] = smin; left [1] = '\0'; right [0] = smax; right [1] = '\0'; middle[0] = smy; middle[1] = '\0'; return (strcoll(left, middle) <= 0 && strcoll(middle, right) <= 0); } Obratite vnimanie na to, chto v UNIX rasshireniem shablonov imen fajlov, vrode *.c, zanimaetsya ne operacionnaya sistema (kak v MS DOS), a programma-interpretator komand pol'zovatelya (shell: /bin/sh, /bin/csh, /bin/ksh). |to pozvolyaet obrabatyvat' (v principe) raznye stili shablonov imen. 7.42. Izuchite razdel rukovodstva man regexp i include-fajl /usr/include/regexp.h, soderzhashchij ishodnye teksty funkcij compile i step dlya regulyarnogo vyrazheniya v stile programm ed, lex, grep: odna bukva C ili zaekranirovannyj specsimvol \. \[ \* \$ \^ \\ oznachayut sami sebya; A. Bogatyrev, 1992-95 - 312 - Si v UNIX . oznachaet odin lyuboj simvol krome \n; [abc] ili [a-b] oznachaet lyuboj simvol iz perechislennyh (iz intervala); [abc-] minus v konce oznachaet sam simvol -; []abc] vnutri [] skobka ] na pervom meste oznachaet sama sebya; [^a-z] kryshka ^ oznachaet otricanie, t.e. lyuboj simvol krome perechislennyh; [a-z^] kryshka ne na pervom meste oznachaet sama sebya; [\*.] specsimvoly vnutri [] ne nesut special'nogo znacheniya, a predstavlyayut sami sebya; C* lyuboe (0 i bolee) chislo simvolov C; .* lyuboe chislo lyubyh simvolov; vyrazhenie* lyuboe chislo (0 i bolee) povtorenij vyrazheniya, naprimer [0-9]* oznachaet chislo (posledovatel'nost' cifr) ili pustoe mesto. Ishchetsya samoe dlinnoe prizhatoe vlevo podvyrazhenie; vyrazhenie\{n,m\} povtorenie vyrazheniya ot n do m raz (vklyuchitel'no), gde chisla ne prevoshodyat 255; vyrazhenie\{n,\} povtorenie po krajnej mere n raz, naprimer [0-9]\{1,\} oznachaet chislo; vyrazhenie\{n\} povtorenie rovno n raz; vyrazhenie$ stroka, chej konec udovletvoryaet vyrazheniyu, naprimer .*define.*\\$ ^vyrazhenie stroka, ch'e nachalo udovletvoryaet vyrazheniyu; \n simvol perevoda stroki; \(.....\) segment. Sopostavivshayasya s nim podstroka budet zapomnena; \N gde N cifra. Dannyj uchastok obrazca dolzhen sovpadat' s N-ym segmentom (numeraciya s 1). Napishite funkciyu matchReg, ispol'zuyushchuyu etot stil' regulyarnyh vyrazhenij. Sohranyajte shablon, pri vyzove matchReg sravnivajte staryj shablon s novym. Perekompilyaciyu sleduet proizvodit' tol'ko esli shablon izmenilsya: #include <stdio.h> #include <ctype.h> #define INIT register char *sp = instring; #define GETC() (*sp++) #define PEEKC() (*sp) #define UNGETC(c) (--sp) #define RETURN(ptr) return #define ERROR(code) \ {fprintf(stderr,"%s:ERR%d\n",instring,code);exit(177);} # include <regexp.h> #define EOL '\0' /* end of line */ #define ESIZE 512 int matchReg(char *str, char *pattern){ static char oldPattern[256]; static char compiledExpr[ESIZE]; if( strcmp(pattern, oldPattern)){ /* razlichny */ /* compile regular expression */ compile(pattern, compiledExpr, &compiledExpr[ESIZE], EOL); A. Bogatyrev, 1992-95 - 313 - Si v UNIX strcpy(oldPattern, pattern); /* zapomnit' */ } return step(str, compiledExpr); /* sopostavit' */ } /* Primer vyzova: reg '^int' 'int$' char | less */ /* reg 'putchar.*(.*)' < reg.c | more */ void main(int ac, char **av){ char inputline[BUFSIZ]; register i; while(gets(inputline)){ for(i=1; i < ac; i++) if(matchReg(inputline, av[i])){ char *p; extern char *loc1, *loc2; /*printf("%s\n", inputline);*/ /* Napechatat' stroku, * vydelyaya sopostavivshuyusya chast' zhirno */ for(p=inputline; p != loc1; p++) putchar(*p); for( ; p != loc2; p++) if(isspace((unsigned char) *p)) putchar(*p); else printf("%c\b%c", *p, *p); for( ; *p; p++) putchar(*p); putchar('\n'); break; } } } 7.43. Ispol'zuya <regexp.h> napishite programmu, proizvodyashchuyu kontekstnuyu zamenu vo vseh strokah fajla. Esli stroka ne udovletvoryaet regulyarnomu vyrazheniyu - ona ostaetsya neizmennoj. Primery vyzova: $ regsub '\([0-9]\{1,\}\)' '(\1)' $ regsub 'f(\(.*\),\(.*\))' 'f(\2,\1)' < file Vtoraya komanda dolzhna zamenyat' vse vhozhdeniya f(a,b) na f(b,a). Vyrazhenie, oboznachen- noe v obrazce kak \(...\), podstavlyaetsya na mesto sootvetstvuyushchej konstrukcii \N vo vtorom argumente, gde N - cifra, nomer segmenta. CHtoby pomestit' v vyhod sam simvol \, ego nado udvaivat': \\. A. Bogatyrev, 1992-95 - 314 - Si v UNIX /* Kontekstnaya zamena */ #include <stdio.h> #include <ctype.h> #define INIT register char *sp = instring; #define GETC() (*sp++) #define PEEKC() (*sp) #define UNGETC(c) (--sp) #define RETURN(ptr) return #define ERROR(code) regerr(code) void regerr(); # include <regexp.h> #define EOL '\0' /* end of line */ #define ESIZE 512 short all = 0; /* klyuch -a oznachaet, chto v stroke nado zamenit' VSE vhozhdeniya obrazca (global, all): * regsub -a int INT * "aa int bbb int cccc" -> "aa INT bbb INT cccc" * * step() nahodit SAMUYU DLINNUYU podstroku, udovletvoryayushchuyu vyrazheniyu, * poetomu regsub 'f(\(.*\),\(.*\))' 'f(\2,\1)' * zamenit "aa f(1,2) bb f(3,4) cc" -> "aa f(4,1,2) bb f(3) cc' * |___________|_| |_|___________| */ char compiled[ESIZE], line[512]; A. Bogatyrev, 1992-95 - 315 - Si v UNIX void main(int ac, char *av[]){ register char *s, *p; register n; extern int nbra; extern char *braslist[], *braelist[], *loc1, *loc2; if( ac > 1 && !strcmp(av[1], "-a")){ ac--; av++; all++; } if(ac != 3){ fprintf(stderr, "Usage: %s [-a] pattern subst\n", av[0]); exit(1); } compile(av[1], compiled, compiled + sizeof compiled, EOL); while( gets(line) != NULL ){ if( !step(s = line, compiled)){ printf("%s\n", line); continue; } do{ /* Pechataem nachalo stroki */ for( ; s != loc1; s++) putchar(*s); /* Delaem zamenu */ for(s=av[2]; *s; s++) if(*s == '\\'){ if(isdigit(s[1])){ /* segment */ int num = *++s - '1'; if(num < 0 || num >= nbra){ fprintf(stderr, "Bad block number %d\n", num+1); exit(2); } for(p=braslist[num]; p != braelist[num]; ++p) putchar(*p); } else if(s[1] == '&'){ ++s; /* vsya sopostavlennaya stroka */ for(p=loc1; p != loc2; ++p) putchar(*p); } else putchar(*++s); } else putchar(*s); } while(all && step(s = loc2, compiled)); /* Ostatok stroki */ for(s=loc2; *s; s++) putchar(*s); putchar('\n'); } /* endwhile */ } A. Bogatyrev, 1992-95 - 316 - Si v UNIX void regerr(int code){ char *msg; switch(code){ case 11: msg = "Range endpoint too large."; break; case 16: msg = "Bad number."; break; case 25: msg = "\\digit out of range."; break; case 36: msg = "Illegal or missing delimiter."; break; case 41: msg = "No remembered search string."; break; case 42: msg = "\\(~\\) imbalance."; break; case 43: msg = "Too many \\(."; break; case 44: msg = "More than 2 numbers given in \\{~\\\"}."; break; case 45: msg = "} expected after \\."; break; case 46: msg = "First number exceeds second in \\{~\\}."; break; case 49: msg = "[ ] imbalance."; break; case 50: msg = "Regular expression overflow."; break; default: msg = "Unknown error"; break; } fputs(msg, stderr); fputc('\n', stderr); exit(code); } void prfields(){ int i; for(i=0; i < nbra; i++) prfield(i); } void prfield(int n){ char *fbeg = braslist[n], *fend = braelist[n]; printf("\\%d='", n+1); for(; fbeg != fend; fbeg++) putchar(*fbeg); printf("'\n"); } 7.44. Sostav'te funkciyu poiska podstroki v stroke. Ispol'zuya ee, napishite programmu poiska podstroki v tekstovom fajle. Programma dolzhna vyvodit' stroki (libo nomera strok) fajla, v kotoryh vstretilas' dannaya podstroka. Podstroka zadaetsya v kachestve argumenta funkcii main(). /* Algoritm bystrogo poiska podstroki. * Dzh. Mur, R. Bojer, 1976 Texas * Smotri: Communications of the ACM 20, 10 (Oct., 1977), 762-772 * * |tot algoritm vygoden pri mnogokratnom poiske obrazca v * bol'shom kolichestve strok, prichem esli oni ravnoj dliny - * mozhno sekonomit' eshche i na operacii strlen(str). * Algoritm harakteren tem, chto pri neudache proizvodit sdvig ne na * odin, a srazu na neskol'ko simvolov vpravo. * V luchshem sluchae algoritm delaet slen/plen sravnenij. */ char *pattern; /* obrazec (chto iskat') */ static int plen; /* dlina obrazca */ static int d[256]; /* tablica sdvigov; v alfavite ASCII - * 256 bukv. */ /* rasstoyanie ot konca obrazca do pozicii i v nem */ #define DISTANCE(i) ((plen-1) - (i)) A. Bogatyrev, 1992-95 - 317 - Si v UNIX /* Poisk: * vydat' indeks vhozhdeniya pattern v str, * libo -1, esli ne vhodit */ int indexBM( str ) char *str; /* v chem iskat' */ { int slen = strlen(str); /* dlina stroki */ register int pindx; /* indeks sravnivaemoj bukvy v obrazce */ register int cmppos; /* indeks sravnivaemoj bukvy v stroke */ register int endpos; /* poziciya v stroke, k kotoroj "pristavlyaetsya" * poslednyaya bukva obrazca */ /* poka obrazec pomeshchaetsya v ostatok stroki */ for( endpos = plen-1; endpos < slen ; ){ /* Dlya otladki: pr(str, pattern, endpos - (plen-1), 0); /**/ /* prosmotr obrazca ot konca k nachalu */ for( cmppos = endpos, pindx = (plen - 1); pindx >= 0 ; cmppos--, pindx-- ) if( str[cmppos] != pattern[pindx] ){ /* Sdvig, kotoryj stavit samyj pravyj v obrazce * simvol str[endpos] kak raz pod endpos-tuyu * poziciyu stroki. Esli zhe takoj simvol v obrazce ne * soderzhitsya (ili soderzhitsya tol'ko na konce), * to nachalo obrazca ustanavlivaetsya v endpos+1 uyu * poziciyu */ endpos += d[ str[endpos] & 0377 ]; break; /* & 0377 podavlyaet rasshirenie znaka. Eshche */ } /* mozhno sdelat' vse char -> unsigned char */ if( pindx < 0 ) return ( endpos - (plen-1)); /* Nashel: ves' obrazec vlozhilsya */ } return( -1 ); /* Ne najdeno */ } A. Bogatyrev, 1992-95 - 318 - Si v UNIX /* Razmetka tablicy sdvigov */ void compilePatternBM( ptrn ) char *ptrn; { register int c; pattern = ptrn; plen = strlen(ptrn); /* c - nomer bukvy alfavita */ for(c = 0; c < 256; c++) d[c] = plen; /* sdvig na dlinu vsego obrazca */ /* c - poziciya v obrazce */ for(c = 0; c < plen - 1; c++) d[ pattern[c] & 0377 ] = DISTANCE(c); /* Sdvig raven rasstoyaniyu ot samogo pravogo * (krome poslednej bukvy obrazca) * vhozhdeniya bukvy v obrazec do konca obrazca. * Zametim, chto esli bukva vhodit v obrazec neskol'ko raz, * to cikl uchityvaet poslednee (samoe pravoe) vhozhdenie. */ } /* Pechat' najdennyh strok */ void pr(s, p, n, nl) char *s, *p; { register i; printf("%4d\t%s\n", nl, s ); printf(" \t"); for(i = 0; i < n; i++ ) putchar( s[i] == '\t' ? '\t' : ' ' ); printf( "%s\n", p ); } /* Analog programmy fgrep */ #include <stdio.h> char str[ 1024 ]; /* bufer dlya prochitannoj stroki */ void main(ac, av) char **av; { int nline = 0; /* nomer stroki fajla */ int ind; int retcode = 1; if(ac != 2){ fprintf(stderr, "Usage: %s 'pattern'\n", av[0] ); exit(33); } compilePatternBM( av[1] ); while( gets(str) != NULL ){ nline++; if((ind = indexBM(str)) >= 0 ){ retcode = 0; /* O'KAY */ pr(str, pattern, ind, nline); } } exit(retcode); } A. Bogatyrev, 1992-95 - 319 - Si v UNIX /* Primer raboty algoritma: peter piper picked a peck of pickled peppers. peck peter piper picked a peck of pickled peppers. peck peter piper picked a peck of pickled peppers. peck peter piper picked a peck of pickled peppers. peck peter piper picked a peck of pickled peppers. peck peter piper picked a peck of pickled peppers. peck peter piper picked a peck of pickled peppers. peck peter piper picked a peck of pickled peppers. peck */ 7.45. Napishite analogichnuyu programmu, vydayushchuyu vse stroki, udovletvoryayushchie uproshchen- nomu regulyarnomu vyrazheniyu, zadavaemomu kak argument dlya main(). Ispol'zujte funkciyu match, napisannuyu nami ranee. Vy napisali analog programmy grep iz UNIX (no s drugim tipom regulyarnogo vyrazheniya, nezheli v originale). 7.46. Sostav'te funkciyu expand(s1, s2), kotoraya rasshiryaet sokrashchennye oboznacheniya vida a-z stroki s1 v ekvivalentnyj polnyj spisok abcd...xyz v stroke s2. Dopuskayutsya sokrashcheniya dlya strochnyh i propisnyh bukv i cifr. Uchtite sluchai tipa a-b-c, a-z0-9 i -a-g (soglashenie sostoit v tom, chto simvol "-", stoyashchij v nachale ili v konce, vospri- nimaetsya bukval'no). 7.47. Napishite programmu, chitayushchuyu fajl i zamenyayushchuyu stroki vida |<1 i bolee probelov i tabulyacij><tekst> na pary strok |.pp |<tekst> (zdes' | oboznachaet levyj kraj fajla, a <> - metasimvoly). |to - prostejshij prepro- cessor, gotovyashchij tekst v formate nroff (eto formatter tekstov v UNIX). Uslozhneniya: - stroki, nachinayushchiesya s tochki ili s apostrofa, zamenyat' na \&<tekst, nachinayushchijsya s tochki ili '> - stroki, nachinayushchiesya s cifry, zamenyat' na .ip <chislo> <tekst> - simvol \ zamenyat' na posledovatel'nost' \e. - udalyat' probely pered simvolami .,;:!?) i vstavlyat' posle nih probel (znak pre- pinaniya dolzhen byt' prikleen k koncu slova, inache on mozhet byt' perenesen na sleduyushchuyu stroku. Vy kogda-nibud' videli stroku, nachinayushchuyusya s zapyatoj?). - skleivat' perenesennye slova, poskol'ku nroff delaet perenosy sam: ....xxxx nachalo- => ....xxxx nachalokonec konec yyyy...... yyyy................ A. Bogatyrev, 1992-95 - 320 - Si v UNIX Vyzyvajte etot preprocessor razmetki teksta tak: $ prep fajly... | nroff -me > text.lp 7.48. Sostav'te programmu preobrazovaniya propisnyh bukv iz fajla vvoda v strochnye, ispol'zuya pri etom funkciyu, v kotoroj neobhodimo organizovat' analiz simvola (dejst- vitel'no li eto bukva). Strochnye bukvy vydavat' bez izmeneniya. Ukazanie: ispol'zujte makrosy iz <ctype.h>. Otvet: #include <ctype.h> #include <stdio.h> main(){ int c; while( (c = getchar()) != EOF ) putchar( isalpha( c ) ? (isupper( c ) ? tolower( c ) : c) : c); } libo ... putchar( isalpha(c) && isupper(c) ? tolower(c) : c ); libo dazhe putchar( isupper(c) ? tolower(c) : c ); V poslednem sluchae pod isupper i islower dolzhny ponimat'sya tol'ko bukvy (uvy, ne vo vseh realizaciyah eto tak!). 7.49. Obratite vnimanie, chto esli my vydelyaem klass simvolov pri pomoshchi sravneniya, naprimer: char ch; if( 0300 <= ch && ch < 0340 ) ...; (v kodirovke KOI-8 eto malen'kie russkie bukvy), to my mozhem natolknut'sya na sleduyu- shchij syurpriz: pered sravneniem s celym znachenie ch privoditsya k tipu int (privedenie takzhe delaetsya pri ispol'zovanii char v kachestve argumenta funkcii). Pri etom, esli u ch byl ustanovlen starshij bit (0200), proizojdet rasshirenie ego vo ves' starshij bajt (rasshirenie znakovogo bita). Rezul'tatom budet otricatel'noe celoe chislo! Opyt: char c = '\201'; /* = 129 */ printf( "%d\n", c ); pechataetsya -127. Takim obrazom, nashe sravnenie ne srabotaet, t.k. okazyvaetsya chto ch < 0. Sleduet podavlyat' rasshirenie znaka: if( 0300 <= (ch & 0377) && (ch & 0377) < 0340) ...; (0377 - maska iz 8 bit, ona zhe 0xFF, ves' bajt), libo ob®yavit' unsigned char ch; chto oznachaet, chto pri privedenii k int znakovyj bit ne rasshiryaetsya. 7.50. Rassmotrim eshche odin primer: A. Bogatyrev, 1992-95 - 321 - Si v UNIX main(){ char ch; /* 0377 - kod poslednego simvola alfavita ASCII */ for (ch = 0100; ch <= 0377; ch++ ) printf( "%03o %s\n", ch & 0377, ch >= 0300 && ch < 0340 ? "yes" : "no" ); } Kakie nepriyatnosti zhdut nas zdes'? - vo-pervyh, kogda bit 0200 u ch ustanovlen, v sravnenii ch vystupaet kak otrica- tel'noe celoe chislo (t.k. privedenie k int delaetsya rasshireniem znakovogo bita), to est' u nas vsegda pechataetsya "no". |to my mozhem ispravit', napisav unsigned char ch, libo ispol'zuya ch v vide (ch & 0377) ili ((unsigned) ch) - vo-vtoryh, rassmotrim sam cikl. Pust' sejchas ch =='\377'. Uslovie ch <= 0377 istinno. Vypolnyaetsya operator ch++. No ch - eto bajt, poetomu operacii nad nim proizvodyatsya po modulyu 0400 (0377 - eto maksimal'noe znachenie, kotoroe mozhno hranit' v bajte - vse bity edinicy). To est' teper' znacheniem ch stanet 0. No 0 < 0377 i uslovie cikla verno! Cikl prodolzhaetsya; t.e. proishodit zacikliva- nie. Izbezhat' etogo mozhno tol'ko opisav int ch; chtoby 0377+1 bylo ravno 0400, a ne 0 (ili unsigned int, lish' by dliny peremennoj hvatalo, chtoby vmestit' chislo bol'she 0377). 7.51. Sostav'te programmu, preobrazuyushchuyu tekst, sostoyashchij tol'ko iz strochnyh bukv v tekst, sostoyashchij iz propisnyh i strochnyh bukv. Pervaya bukva i bukva posle kazhdoj tochki - propisnye, ostal'nye - strochnye. slovo odin. slovo dva. --> Slovo odin. Slovo dva. |ta programma mozhet okazat'sya poleznoj dlya preobrazovaniya teksta, nabrannogo v odnom registre, v tekst, soderzhashchij bukvy oboih registrov. 7.52. Napishite programmu, ispravlyayushchuyu opechatki v slovah (spell check): programme zadan spisok slov; ona proveryaet - yavlyaetsya li vvedennoe vami slovo slovom iz spiska. Esli net - pytaetsya najti naibolee pohozhee slovo iz spiska, prichem esli est' nes- kol'ko pohozhih - vydaet vse varianty. Otlavlivajte sluchai: - dve sosednie bukvy perestavleny mestami: nozhincy=>nozhnicy; - udvoennaya bukva (bukvy): kkarrandash=>karandash; - poteryana bukva: bot=>bolt; - izmenennaya bukva: bint=>bant; - lishnyaya bukva: morda=>moda; - bukvy ne v tom registre - sravnite s kazhdym slovom iz spiska, privodya vse bukvy k malen'kim: sOVOk=>sovok; Nado proveryat' kazhduyu bukvu slova. Vozmozhno vam budet udobno ispol'zovat' rekursiyu. Podskazka: dlya nekotoryh proverok vam mozhet pomoch' funkciya match: slovo_tablicy = "dom"; if(strlen(vhodnoe_slovo) <= strlen(slovo_tablicy)+1 && match(vhodnoe_slovo, "*d*o*m*") ... /* pohozhe */ *o*m* ?dom dom? *d*m* d?om *d*o* do?m Privedem variant resheniya etoj zadachi: A. Bogatyrev, 1992-95 - 322 - Si v UNIX #include <stdio.h> #include <ctype.h> #include <locale.h> typedef unsigned char uchar; #define ANYCHAR '*' /* simvol, sopostavlyayushchijsya s odnoj lyuboj bukvoj */ static uchar version[120]; /* bufer dlya generacii variantov */ static uchar vv; /* bukva, sopostavivshayasya s ANYCHAR */ /* privesti vse bukvy k odnomu registru */ static uchar icase(uchar c){ return isupper(c) ? tolower(c) : c; } /* sravnenie strok s ignorirovaniem registra */ static int eqi(uchar *s1, uchar *s2 ) { while( *s1 && *s2 ){ if( icase( *s1 ) != icase( *s2 )) break; s1++; s2++; } return ( ! *s1 && ! *s2 ) ? 1 : 0 ; /* OK : FAIL */ } /* sravnenie strok s ignorirovaniem ANYCHAR */ static strok(register uchar *word, register uchar *pat) { while( *word && *pat ){ if( *word == ANYCHAR){ /* Nevazhno, chto est' *pat, no zapomnim */ vv= *pat; } else { if( icase(*pat) != icase(*word) ) break; } word++; pat++; } /* esli slova konchilis' odnovremenno ... */ return ( !*word && !*pat) ? 1 : 0; /* OK : FAIL */ } A. Bogatyrev, 1992-95 - 323 - Si v UNIX /* LISHNYAYA BUKVA */ static int superfluous( uchar *word /* slovo dlya korrekcii */ , uchar *s /* etalon */ ){ register int i,j,k; int reply; register len = strlen(word); for(i=0 ; i < len ; i++){ /* generim slova , poluchayushchiesya udaleniem odnoj bukvy */ k=0; for(j=0 ; j < i ; j++) version[k++]=word[j]; for(j=i+1 ; j < len ; j++) version[k++]=word[j]; version[k]='\0'; if( eqi( version, s )) return 1; /* OK */ } return 0; /* FAIL */ } /* POTERYANA BUKVA */ static int hole; /* mesto, gde vstavlena ANYCHAR */ static int lost(uchar *word, uchar *s) { register int i,j,k; register len = strlen(word); hole= (-1); for(i=0 ; i < len+1 ; i++){ k=0; for(j=0 ; j < i ; j++) version[k++]=word[j]; version[k++]=ANYCHAR; for(j=i ; j < len ; j++) version[k++]=word[j]; version[k]='\0'; if( strok( version, s )){ hole=i; return 1; /* OK */ } } return 0; /* FAIL */ } A. Bogatyrev, 1992-95 - 324 - Si v UNIX /* IZMENILASX ODNA BUKVA (vklyuchaet sluchaj oshibki registra) */ static int changed(uchar *word, uchar *s) { register int i,j,k; register len = strlen(word); hole = (-1); for(i=0 ; i < len ; i++){ k=0; for( j=0 ; j < i ; j++) version[k++]=word[j]; version[k++]=ANYCHAR; for( j=i+1 ; j < len ; j++) version[k++]=word[j]; version[k]='\0'; if( strok( version,s)){ hole=i; return 1; /* OK */ } } return 0; /* FAIL */ } /* UDVOENNAYA BUKVA */ static int duplicates(uchar *word, uchar *s, int leng) { register int i,j,k; uchar tmp[80]; if( eqi( word, s )) return 1; /* OK */ for(i=0;i < leng - 1; i++) /* ishchem parnye bukvy */ if( word[i]==word[i+1]){ k=0; for(j=0 ; j < i ; j++) tmp[k++]=word[j]; for(j=i+1 ; j < leng ; j++) tmp[k++]=word[j]; tmp[k]='\0'; if( duplicates( tmp, s, leng-1) == 1) return 1; /* OK */ } return 0; /* FAIL */ } A. Bogatyrev, 1992-95 - 325 - Si v UNIX /* PERESTAVLENY SOSEDNIE BUKVY */ static int swapped(uchar *word, uchar *s) { register int i,j,k; register len = strlen(word); for(i=0;i < len-1;i++){ k=0; for(j=0 ; j < i ; j++) version[k++]=word[j]; version[k++]=word[i+1]; version[k++]=word[i]; for(j=i+2 ; j < len ; j++) version[k++]=word[j]; version[k]='\0'; if( eqi( version, s)) return 1; /* OK */ } return 0; /* FAIL */ } uchar *words[] = { (uchar *) "bag", (uchar *) "bags", (uchar *) "cook", (uchar *) "cool", (uchar *) "bug", (uchar *) "buy", (uchar *) "cock", NULL }; #define Bcase(x, operators) case x: { operators; } break; char *cname[5] = { "perestavleny bukvy", "udvoeny bukvy ", "poteryana bukva ", "oshibochnaya bukva ", "lishnyaya bukva " }; A. Bogatyrev, 1992-95 - 326 - Si v UNIX static int spellmatch( uchar *word /* IN slovo dlya korrekcii */ , uchar *words[] /* IN tablica dopustimyh slov */ , uchar **indx /* OUT otvet */ ){ int i, code, total = (-1); uchar **ptr; if(!*word) return -1; for(ptr = words; *ptr; ++ptr) if(eqi(word, *ptr)){ if(indx) *indx = *ptr; return 0; } /* Net v tablice, nuzhen podbor pohozhih */ for(ptr = words; *ptr; ++ptr){ uchar *s = *ptr; int max = 5; for(i=0; i < max; i++){ switch( i ){ Bcase(0,code = swapped(word, s) ) Bcase(1,code = duplicates(word, s, strlen(word)) ) Bcase(2,code = lost(word, s) ) Bcase(3,code = changed(word, s) ) Bcase(4,code = superfluous(word, s) ) } if(code){ total++; printf("?\t%s\t%s\n", cname[i], s); if(indx) *indx = s; /* V sluchae s dublikatami ne rassmatrivat' * na nalichie lishnih bukv */ if(i==1) max = 4; } } } return total; } A. Bogatyrev, 1992-95 - 327 - Si v UNIX void main(){ uchar inpbuf[BUFSIZ]; int n; uchar *reply, **ptr; setlocale(LC_ALL, ""); for(ptr = words; *ptr; ptr++) printf("#\t%s\n", *ptr); do{ printf("> "); fflush(stdout); if(gets((char *)inpbuf) == NULL) break; switch(spellmatch(inpbuf, words, &reply)){ case -1: printf("Net takogo slova\n"); break; case 0: printf("Slovo '%s'\n", reply); break; default: printf("Neodnoznachno\n"); } } while(1); } 7.53. Poka ya sam pisal etu programmu, ya sdelal dve oshibki, kotorye dolzhny byt' ves'ma harakterny dlya novichkov. Pro nih nado by govorit' ran'she, v glave pro stroki i v samoj pervoj glave, no tut oni prishlis' kak raz k mestu. Vopros: chto pechataet sle- duyushchaya programma? #include <stdio.h> char *strings[] = { "Pervaya stroka" "Vtoraya stroka" "Tretyaya stroka", "CHetvertaya stroka", NULL }; void main(){ char **p; for(p=strings;*p;++p) printf("%s\n", *p); } A pechataet ona vot chto: Pervaya strokaVtoraya strokaTretyaya stroka CHetvertaya stroka Delo v tom, chto ANSI kompilyator Si skleivaet stroki: "nachalo stroki" "i ee konec" esli oni razdeleny probelami v smysle isspace, v tom chisle i pustymi strokami. A v nashem ob®yavlenii massiva strok strings my poteryali neskol'ko razdelitel'nyh zapyatyh! Vtoraya oshibka kasaetsya togo, chto mozhno zabyt' postavit' slovo break v operatore switch, i dolgo posle etogo gadat' o nepredskazuemom povedenii lyubogo postupayushchego na vhod znacheniya. Delo prosto: probegayutsya vse sluchai, upravlenie provalivaetsya iz case v sleduyushchij case, i tak mnogo raz podryad! |to i est' prichina togo, chto v predydushchem A. Bogatyrev, 1992-95 - 328 - Si v UNIX primere vse case oformleny netrivial'nym makrosom Bcase. 7.54. Sostav'te programmu kodirovki i raskodirovki fajlov po zadannomu klyuchu (stroke simvolov). 7.55. Sostav'te programmu, kotoraya zaprashivaet anketnye dannye tipa familii, imeni, otchestva, daty rozhdeniya i formiruet fajl. Programma dolzhna otlavlivat' oshibki vvoda nesimvol'noj i necifrovoj informacii, vyhoda sostavlyayushchih daty rozhdeniya za dopustimye granicy s vydachej soobshchenij ob oshibkah. Programma dolzhna davat' vozmozhnost' korrekti- rovat' vvodimye dannye. Vse dannye ob odnom cheloveke zapisyvayutsya v odnu stroku fajla cherez probel. Vot vozmozhnyj primer chasti dialoga (otvety pol'zovatelya vydeleny zhirno): Vvedite mesyac rozhdeniya [1-12]: 14 <ENTER> *** Nepravil'nyj nomer mesyaca (14). Vvedite mesyac rozhdeniya [1-12]: mart <ENTER> *** Nomer mesyaca soderzhit bukvu 'm'. Vvedite mesyac rozhdeniya [1-12]: <ENTER> Vy hotite zakonchit' vvod ? n Vvedite mesyac rozhdeniya [1-12]: 11 <ENTER> Noyabr' Vvedite datu rozhdeniya [1-30]: _ V takih programmah obychno otvet pol'zovatelya vvoditsya kak stroka: printf("Vvedite mesyac rozhdeniya [1-12]: "); fflush(stdout); gets(input_string); zatem (esli nado) otbrasyvayutsya lishnie probely v nachale i v konce stroki, zatem vve- dennyj tekst input_string analiziruetsya na dopustimost' simvolov (net li v nem ne cifr?), zatem stroka preobrazuetsya k nuzhnomu tipu (naprimer, pri pomoshchi funkcii atoi perevoditsya v celoe) i proveryaetsya dopustimost' poluchennogo znacheniya, i.t.d. Vvodimuyu informaciyu snachala zanosite v strukturu; zatem zapisyvajte soderzhimoe polej struktury v fajl v tekstovom vide (ispol'zujte funkciyu fprintf, a ne fwrite). 7.56. Sostav'te programmu, osushchestvlyayushchuyu vyborku informacii iz fajla, sformirovan- nogo v predydushchej zadache, i ee raspechatku v tablichnom vide. Vyborka dolzhna osushchestv- lyat'sya po znacheniyu lyubogo zadannogo polya (t.e. vy vybiraete pole, zadaete ego znache- nie i poluchaete te stroki, v kotoryh znachenie ukazannogo polya sovpadaet s zakazannym vami znacheniem). Uslozhnenie: ispol'zujte funkciyu sravneniya stroki s regulyarnym vyra- zheniem dlya vyborki po shablonu polya (t.e. otbirayutsya tol'ko te stroki, v kotoryh zna- chenie zadannogo polya udovletvoryaet shablonu). Dlya chteniya fajla ispol'zujte fscanf, libo fgets i zatem sscanf. Vtoroj sposob luchshe tem, chto pozvolyaet proverit' po shab- lonu znachenie lyubogo polya - ne tol'ko tekstovogo, no i chislovogo: tak 1234 (stroka - izobrazhenie chisla) udovletvoryaet shablonu "12*". 7.57. Sostav'te variant programmy podscheta sluzhebnyh slov yazyka Si, ne uchityvayushchij poyavlenie etih slov, zaklyuchennyh v kavychki. 7.58. Sostav'te programmu udaleniya iz programmy na yazyke Si vseh kommentariev. Obra- tite vnimanie na osobye sluchai so strokami v kavychkah i simvol'nymi konstantami; tak stroka char s[] = "/*"; ne yavlyaetsya nachalom kommentariya! Kommentarii zapisyvajte v otdel'nyj fajl. 7.59. Sostav'te programmu vydachi perekrestnyh ssylok, t.e. programmu, kotoraya vyvo- dit spisok vseh identifikatorov peremennyh, ispol'zuemyh v programme, i dlya kazhdogo iz identifikatorov vyvodit spisok nomerov strok, v kotorye on vhodit. A. Bogatyrev, 1992-95 - 329 - Si v UNIX 7.60. Razrabotajte prostuyu versiyu preprocessora dlya obrabotki operatorov #include. V kachestve prototipa takoj programmy mozhno rassmatrivat' takuyu (ona ponimaet direk- tivy vida #include imyafajla - bez <> ili ""). #include <stdio.h> #include <string.h> #include <errno.h> char KEYWORD[] = "#include "; /* with a trailing space char */ void process(char *name, char *from){ FILE *fp; char buf[4096]; if((fp = fopen(name, "r")) == NULL){ fprintf(stderr, "%s: cannot read \"%s\", %s\n", from, name, strerror(errno)); return; } while(fgets(buf, sizeof buf, fp) != NULL){ if(!strncmp(buf, KEYWORD, sizeof KEYWORD - 1)){ char *s; if((s = strchr(buf, '\n')) != NULL) *s = '\0'; fprintf(stderr, "%s: including %s\n", name, s = buf + sizeof KEYWORD - 1); process(s, name); } else fputs(buf, stdout); } fclose(fp); } int main(int ac, char *av[]){ int i; for(i=1; i < ac; i++) process(av[i], "MAIN"); return 0; } 7.61. Razrabotajte prostuyu versiyu preprocessora dlya obrabotki operatorov #define. Snachala realizujte makrosy bez argumentov. Napishite obrabotchik makrosov vida #macro imya(argu,menty) telo makrosa - mozhno neskol'ko strok #endm 7.62. Napishite programmu, obrabatyvayushchuyu opredeleniya #ifdef, #else, #endif. Uchtite, chto eti direktivy mogut byt' vlozhennymi: #ifdef A # ifdef B ... /* defined(A) && defined(B) */ # endif /*B*/ ... /* defined(A) */ #else /*not A*/ ... /* !defined(A) */ # ifdef C ... /* !defined(A) && defined(C) */ # endif /*C*/ A. Bogatyrev, 1992-95 - 330 - Si v UNIX #endif /*A*/ 7.63. Sostav'te programmu modelirovaniya prostejshego kal'kulyatora, kotoryj schityvaet v kazhdoj strochke po odnomu chislu (vozmozhno so znakom) ili po odnoj operacii slozheniya ili umnozheniya, osushchestvlyaet operaciyu i vydaet rezul'tat. 7.64. Sostav'te programmu-kal'kulyator, kotoraya proizvodit operacii slozheniya, vychita- niya, umnozheniya, deleniya; operandy i znak arifmeticheskoj operacii yavlyayutsya strokovymi argumentami funkcii main. 7.65. Sostav'te programmu, vychislyayushchuyu znachenie komandnoj stroki, predstavlyayushchej soboj obratnuyu pol'skuyu zapis' arifmeticheskogo vyrazheniya. Naprimer, 20 10 5 + * vychislyaetsya kak 20 * (10 + 5) . 7.66. Sostav'te funkcii raboty so stekom: - dobavlenie v stek - udalenie vershiny steka (s vozvratom udalennogo znacheniya) Ispol'zujte dva varianta: stek-massiv i stek-spisok. 7.67. Sostav'te programmu, kotoraya ispol'zuet funkcii raboty so stekom dlya perevoda arifmeticheskih vyrazhenij yazyka Si v obratnuyu pol'skuyu zapis'. /*#!/bin/cc $* -lm * Kal'kulyator. Illyustraciya algoritma prevrashcheniya vyrazhenij * v pol'skuyu zapis' po metodu prioritetov. */ #include <stdio.h> #include <stdlib.h> /* extern double atof(); */ #include <math.h> /* extern double sin(), ... */ #include <ctype.h> /* isdigit(), isalpha(), ... */ #include <setjmp.h> /* jmp_buf */ jmp_buf AGAIN; /* kontrol'naya tochka */ err(n){ longjmp(AGAIN,n);} /* prygnut' v kontrol'nuyu tochku */ A. Bogatyrev, 1992-95 - 331 - Si v UNIX /* VYCHISLITELX --------------------------------------- */ /* Esli vmesto pomeshcheniya operandov v stek stk[] prosto * pechatat' operandy, a vmesto vypolneniya operacij nad * stekom prosto pechatat' operacii, my poluchim "pol'skuyu" * zapis' vyrazheniya: * a+b -> a b + * (a+b)*c -> a b + c * * a + b*c -> a b c * + */ /* stek vychislenij */ #define MAXDEPTH 20 /* glubina stekov */ int sp; /* ukazatel' steka (stack pointer) */ double stk[MAXDEPTH]; double dpush(d) double d; /* zanesti chislo v stek */ { if( sp == MAXDEPTH ){ printf("Stek operandov polon\n");err(1);} else return( stk[sp++] = d ); } double dpop(){ /* vzyat' vershinu steka */ if( !sp ){ printf("Stek operandov pust\n"); err(2); } else return stk[--sp]; } static double r,p; /* vspomogatel'nye registry */ void add() { dpush( dpop() + dpop()); } void mult() { dpush( dpop() * dpop()); } void sub() { r = dpop(); dpush( dpop() - r); } void divide() { r = dpop(); if(r == 0.0){ printf("Delenie na 0\n"); err(3); } dpush( dpop() / r ); } void pwr() { r = dpop(); dpush( pow( dpop(), r )); } void dup() { dpush( dpush( dpop())); } void xchg(){ r = dpop(); p = dpop(); dpush(r); dpush(p); } void neg() { dpush( - dpop()); } void dsin(){ dpush( sin( dpop())); } void dcos(){ dpush( cos( dpop())); } void dexp(){ dpush( exp( dpop())); } void dlog(){ dpush( log( dpop())); } void dsqrt(){ dpush( sqrt( dpop())); } void dsqr(){ dup(); mult(); } /* M_PI i M_E opredeleny v <math.h> */ void pi() { dpush( M_PI /* chislo pi */ ); } void e() { dpush( M_E /* chislo e */ ); } void prn() { printf("%g\n", dpush( dpop())); } void printstk(){ if( !sp ){ printf("Stek operandov pust\n"); err(4);} while(sp) printf("%g ", dpop()); putchar('\n'); } A. Bogatyrev, 1992-95 - 332 - Si v UNIX /* KOMPILYATOR ---------------------------------------- */ /* nomera leksem */ #define END (-3) /* = */ #define NUMBER (-2) /* chislo */ #define BOTTOM 0 /* psevdoleksema "dno steka" */ #define OPENBRACKET 1 /* ( */ #define FUNC 2 /* f( */ #define CLOSEBRACKET 3 /* ) */ #define COMMA 4 /* , */ #define PLUS 5 /* + */ #define MINUS 6 /* - */ #define MULT 7 /* * */ #define DIV 8 /* / */ #define POWER 9 /* ** */ /* Prioritety */ #define NOTDEF 333 /* ne opredelen */ #define INFINITY 3000 /* beskonechnost' */ /* Stek translyatora */ typedef struct _opstack { int cop; /* kod operacii */ void (*f)(); /* "otlozhennoe" dejstvie */ } opstack; int osp; /* operations stack pointer */ opstack ost[MAXDEPTH]; void push(n, func) void (*func)(); { if(osp == MAXDEPTH){ printf("Stek operacij polon\n");err(5);} ost[osp].cop = n; ost[osp++].f = func; } int pop(){ if( !osp ){ printf("Stek operacij pust\n"); err(6); } return ost[--osp].cop; } int top(){ if( !osp ){ printf("Stek operacij pust\n"); err(7); } return ost[osp-1].cop; } void (*topf())(){ return ost[osp-1].f; } #define drop() (void)pop() void nop(){ printf( "???\n" ); } /* no operation */ void obr_err(){ printf( "Ne hvataet )\n" ); err(8); } A. Bogatyrev, 1992-95 - 333 - Si v UNIX /* Tablica prioritetov */ struct synt{ int inp_prt; /* vhodnoj prioritet */ int stk_prt; /* stekovyj prioritet */ void (*op)(); /* dejstvie nad stekom vychislenij */ } ops[] = { /* BOTTOM */ {NOTDEF, -1, nop }, /* OPENBRACKET */ {INFINITY, 0, obr_err}, /* FUNC */ {INFINITY, 0, obr_err}, /* CLOSEBRACKET */ {1, NOTDEF, nop }, /* NOPUSH */ /* COMMA */ {1, NOTDEF, nop }, /* NOPUSH */ /* PLUS */ {1, 1, add }, /* MINUS */ {1, 1, sub }, /* MULT */ {2, 2, mult }, /* DIV */ {2, 2, divide }, /* POWER */ {3, 3, pwr } }; #define stkprt(i) ops[i].stk_prt #define inpprt(i) ops[i].inp_prt #define perform(i) (*ops[i].op)() /* znacheniya, zapolnyaemye leksicheskim analizatorom */ double value; void (*fvalue)(); int tprev; /* predydushchaya leksema */ A. Bogatyrev, 1992-95 - 334 - Si v UNIX /* Translyator v pol'skuyu zapis' + interpretator */ void reset(){ sp = osp = 0; push(BOTTOM, NULL); tprev = END;} void calc(){ int t; do{ if( setjmp(AGAIN)) printf( "Steki posle oshibki sbrosheny\n" ); reset(); while((t = token()) != EOF && t != END){ if(t == NUMBER){ if(tprev == NUMBER){ printf("%g:Dva chisla podryad\n",value); err(9); } /* lyuboe chislo prosto zanositsya v stek */ tprev = t; dpush(value); continue; } /* inache - operator */ tprev = t; /* Vytalkivanie i vypolnenie operacij so steka */ while(inpprt(t) <= stkprt( top()) ) perform( pop()); /* Sokrashchenie ili podmena skobok */ if(t == CLOSEBRACKET){ if( top() == OPENBRACKET || top() == FUNC ){ void (*ff)() = topf(); drop(); /* shlopnut' skobki */ /* obrabotka funkcii */ if(ff) (*ff)(); }else{ printf( "Ne hvataet (\n"); err(10); } } /* Zanesenie operacij v stek (krome NOPUSH-operacij) */ if(t != CLOSEBRACKET && t != COMMA) push(t, t == FUNC ? fvalue : NULL ); } if( t != EOF ){ /* Dovypolnit' ostavshiesya operacii */ while( top() != BOTTOM ) perform( pop()); printstk(); /* pechat' steka vychislenij (otvet) */ } } while (t != EOF); } /* Leksicheskij analizator ---------------------------- */ extern void getn(), getid(), getbrack(); int token(){ /* prochest' leksemu */ int c; while((c = getchar())!= EOF && (isspace(c) || c == '\n')); if(c == EOF) return EOF; ungetc(c, stdin); if(isdigit(c)){ getn(); return NUMBER; } if(isalpha(c)){ getid(); getbrack(); return FUNC; } return getop(); } A. Bogatyrev, 1992-95 - 335 - Si v UNIX /* Prochest' chislo (s tochkoj) */ void getn(){ int c, i; char s[80]; s[0] = getchar(); for(i=1; isdigit(c = getchar()); i++ ) s[i] = c; if(c == '.'){ /* drobnaya chast' */ s[i] = c; for(i++; isdigit(c = getchar()); i++) s[i] = c; } s[i] = '\0'; ungetc(c, stdin); value = atof(s); } /* Prochest' operaciyu */ int getop(){ int c; switch( c = getchar()){ case EOF: return EOF; case '=': return END; case '+': return PLUS; case '-': return MINUS; case '/': return DIV; case '*': c = getchar(); if(c == '*') return POWER; else{ ungetc(c, stdin); return MULT; } case '(': return OPENBRACKET; case ')': return CLOSEBRACKET; case ',': return COMMA; default: printf( "Oshibochnaya operaciya %c\n", c); return token(); } } struct funcs{ /* Tablica imen funkcij */ char *fname; void (*fcall)(); } tbl[] = { { "sin", dsin }, { "cos", dcos }, { "exp", dexp }, { "sqrt", dsqrt }, { "sqr", dsqr }, { "pi", pi }, { "sum", add }, { "ln", dlog }, { "e", e }, { NULL, NULL } }; char *lastf; /* imya najdennoj funkcii */ /* Prochest' imya funkcii */ void getid(){ struct funcs *ptr = tbl; char name[80]; int c, i; *name = getchar(); for(i=1; isalpha(c = getchar()); i++) name[i] = c; name[i] = '\0'; ungetc(c, stdin); /* poisk v tablice */ for( ; ptr->fname; ptr++ ) if( !strcmp(ptr->fname, name)){ fvalue = ptr->fcall; lastf = ptr->fname; return; } printf( "Funkciya \"%s\" neizvestna\n", name ); err(11); } A. Bogatyrev, 1992-95 - 336 - Si v UNIX /* prochest' otkryvayushchuyu skobku posle imeni funkcii */ void getbrack(){ int c; while((c = getchar()) != EOF && c != '(' ) if( !isspace(c) && c != '\n' ){ printf("Mezhdu imenem funkcii %s i ( simvol %c\n", lastf, c); ungetc(c, stdin); err(12); } } void main(){ calc();} /* Primery: ( sin( pi() / 4 + 0.1 ) + sum(2, 4 + 1)) * (5 - 4/2) = otvet: 23.3225 (14 + 2 ** 3 * 7 + 2 * cos(0)) / ( 7 - 4 ) = otvet: 24 */ 7.68. Privedem eshche odin arifmeticheskij vychislitel', ispol'zuyushchij klassicheskij rekur- sivnyj podhod: /* Kal'kulyator na osnove rekursivnogo grammaticheskogo razbora. * Po motivam arifmeticheskoj chasti programmy csh (SiSHell). * csh napisan Billom Dzhoem (Bill Joy). : var1 = (x = 1+3) * (y=x + x++) 36 : s = s + 1 oshibka : y 9 : s = (1 + 1 << 2) == 1 + (1<<2) 0 : var1 + 3 + -77 -38 : a1 = 3; a2 = (a4=a3 = 2; a1++)+a4+2 8 : sum(a=2;b=3, a++, a*3-b) 12 */ #include <stdio.h> #include <ctype.h> #include <setjmp.h> typedef enum { NUM, ID, OP, OPEN, CLOSE, UNKNOWN, COMMA, SMC } TokenType; char *toknames[] = { "number", "identifier", "operation", "open_paren", "close_paren", "unknown", "comma", "semicolon" }; typedef struct _Token { char *token; /* leksema (slovo) */ struct _Token *next; /* ssylka na sleduyushchuyu */ TokenType type; /* tip leksemy */ } Token; extern void *malloc(unsigned); extern char *strchr(char *, char); char *strdup(const char *s){ char *p = (char *)malloc(strlen(s)+1); if(p) strcpy(p,s); return p; } A. Bogatyrev, 1992-95 - 337 - Si v UNIX /* Leksicheskij razbor ------------------------------------------*/ /* Ochistit' cepochku tokenov */ void freelex(Token **p){ Token *thisTok = *p; while( thisTok ){ Token *nextTok = thisTok->next; free((char *) thisTok->token); free((char *) thisTok); thisTok = nextTok; } *p = NULL; } /* Dobavit' token v hvost spiska */ void addtoken(Token **hd, Token **tl, char s[], TokenType t){ Token *newTok = (Token *) malloc(sizeof(Token)); newTok->next = (Token *) NULL; newTok->token = strdup(s); newTok->type = t; if(*hd == NULL) *hd = *tl = newTok; else{ (*tl)->next = newTok; *tl = newTok; } } /* Razobrat' stroku v spisok leksem (tokenov) */ #define opsym(c) ((c) && strchr("+-=!~^|&*/%<>", (c))) #define is_alpha(c) (isalpha(c) || (c) == '_') #define is_alnum(c) (isalnum(c) || (c) == '_') void lex(Token **hd, Token **tl, register char *s){ char *p, csave; TokenType type; while(*s){ while( isspace(*s)) ++s; p = s; if( !*s ) break; if(isdigit (*s)){ type = NUM; while(isdigit (*s))s++; } else if(is_alpha(*s)){ type = ID; while(is_alnum(*s))s++; } else if(*s == '('){ type = OPEN; s++; } else if(*s == ')'){ type = CLOSE; s++; } else if(*s == ','){ type = COMMA; s++; } else if(*s == ';'){ type = SMC; s++; } else if(opsym(*s)){ type = OP; while(opsym(*s)) s++; } else { type = UNKNOWN; s++; } csave = *s; *s = '\0'; addtoken(hd, tl, p, type); *s = csave; } } /* Raspechatka spiska leksem */ void printlex(char *msg, Token *t){ if(msg && *msg) printf("%s: ", msg); for(; t != NULL; t = t->next) printf("%s`%s' ", toknames[(int)t->type], t->token); putchar('\n'); } A. Bogatyrev, 1992-95 - 338 - Si v UNIX /* Sistema peremennyh ----------------------------------------- */ #define NEXT(v) *v = (*v)->next #define TOKEN(v) (*v)->token #define TYPE(v) (*v)->type #define eq(str1, str2) (!strcmp(str1, str2)) jmp_buf breakpoint; #define ERR(msg,val) { printf("%s\n", msg);longjmp(breakpoint, val+1);} typedef struct { char *name; /* Imya peremennoj */ int value; /* Znachenie peremennoj */ int isset; /* Poluchila li znachenie ? */ } Var; #define MAXV 40 Var vars[MAXV]; /* Poluchit' znachenie peremennoj */ int getVar(char *name){ Var *ptr; for(ptr=vars; ptr->name; ptr++) if(eq(name, ptr->name)){ if(ptr->isset) return ptr->value; printf("%s: ", name); ERR("variable is unbound yet", 0); } printf("%s: ", name); ERR("undefined variable", 0); } /* Sozdat' novuyu peremennuyu */ Var *internVar(char *name){ Var *ptr; for(ptr=vars; ptr->name; ptr++) if(eq(name, ptr->name)) return ptr; ptr->name = strdup(name); ptr->isset = 0; ptr->value = 0; return ptr; } /* Ustanovit' znachenie peremennoj */ void setVar(Var *ptr, int val){ ptr->isset = 1; ptr->value = val; } /* Raspechatat' znacheniya peremennyh */ void printVars(){ Var *ptr; for(ptr=vars; ptr->name; ++ptr) printf("\t%s %s %d\n", ptr->isset ? "BOUND ":"UNBOUND", ptr->name, ptr->value); } A. Bogatyrev, 1992-95 - 339 - Si v UNIX /* Sintaksicheskij razbor i odnovremennoe vychislenie ----------- */ /* Vychislenie vstroennyh funkcij */ int apply(char *name, int args[], int nargs){ if(eq(name, "power2")){ if(nargs != 1) ERR("power2: wrong argument count", 0); return (1 << args[0]); } else if(eq(name, "min")){ if(nargs != 2) ERR("min: wrong argument count", 0); return (args[0] < args[1] ? args[0] : args[1]); } else if(eq(name, "max")){ if(nargs != 2) ERR("max: wrong argument count", 0); return (args[0] < args[1] ? args[1] : args[0]); } else if(eq(name, "sum")){ register i, sum; for(i=0, sum=0; i < nargs; sum += args[i++]); return sum; } else if(eq(name, "rand")){ switch(nargs){ case 0: return rand(); case 1: return rand() % args[0]; case 2: return args[0] + rand() % (args[1] - args[0] + 1); default: ERR("rand: wrong argument count", 0); } } ERR("Unknown function", args[0]); } /* Vychislit' vyrazhenie iz spiska leksem. */ /* Sintaksis zadan pravorekursivnoj grammatikoj */ int expr(Token *t){ int val = 0; if(val = setjmp(breakpoint)) return val - 1; val = expression(&t); if(t){ printlex(NULL, t); ERR("Extra tokens", val); } return val; } /* <EXPRESSION> = <EXPASS> | <EXPASS> ";" <EXPRESSION> */ int expression(Token **v){ int arg = expass(v); if(*v && TYPE(v) == SMC ){ NEXT(v); return expression(v); } else return arg; } /* <EXPASS> = <PEREMENNAYA> "=" <EXPASS> | <EXP0> */ int expass(Token **v){ int arg; if(*v && (*v)->next && (*v)->next->type == OP && eq((*v)->next->token, "=")){ Var *ptr; /* prisvaivanie (assignment) */ if( TYPE(v) != ID ) /* sleva nuzhna peremennaya */ ERR("Lvalue needed", 0); ptr = internVar(TOKEN(v)); NEXT(v); NEXT(v); setVar(ptr, arg = expass(v)); return arg; } return exp0(v); } A. Bogatyrev, 1992-95 - 340 - Si v UNIX /* <EXP0> = <EXP1> | <EXP1> "||" <EXP0> */ int exp0(Token **v){ int arg = exp1(v); if(*v && TYPE(v) == OP && eq(TOKEN(v), "||")){ NEXT(v); return(exp0(v) || arg ); /* pomeshchaem arg VTORYM, chtoby vtoroj operand vychislyalsya * VSEGDA (inache ne budet ischerpan spisok tokenov i * vozniknet oshibka v expr(); |to ne sovsem po pravilam Si. */ } else return arg; } /* <EXP1> = <EXP2> | <EXP2> "&&" <EXP1> */ int exp1(Token **v){ int arg = exp2(v); if(*v && TYPE(v) == OP && eq(TOKEN(v), "&&")){ NEXT(v); return(exp1(v) && arg); } else return arg; } /* <EXP2> = <EXP2A> | <EXP2A> "|" <EXP2> */ int exp2(Token **v){ int arg = exp2a(v); if(*v && TYPE(v) == OP && eq(TOKEN(v), "|")){ NEXT(v); return( arg | exp2(v)); } else return arg; } /* <EXP2A> = <EXP2B> | <EXP2B> "^" <EXP2A> */ int exp2a(Token **v){ int arg = exp2b(v); if(*v && TYPE(v) == OP && eq(TOKEN(v), "^")){ NEXT(v); return( arg ^ exp2a(v)); } else return arg; } /* <EXP2B> = <EXP2C> | <EXP2C> "&" <EXP2B> */ int exp2b(Token **v){ int arg = exp2c(v); if(*v && TYPE(v) == OP && eq(TOKEN(v), "&")){ NEXT(v); return( arg & exp2b(v)); } else return arg; } /* <EXP2C> = <EXP3> | <EXP3> "==" <EXP3> | <EXP3> "!=" <EXP3> */ int exp2c(Token **v){ int arg = exp3(v); if(*v && TYPE(v) == OP && eq(TOKEN(v), "==")){ NEXT(v); return( arg == exp3(v)); } else if(*v && TYPE(v) == OP && eq(TOKEN(v), "!=")){ NEXT(v); return( arg != exp3(v)); } else return arg; } A. Bogatyrev, 1992-95 - 341 - Si v UNIX /* <EXP3> = <EXP3A> | <EXP3A> ">" <EXP3> | <EXP3A> "<" <EXP3> | <EXP3A> ">=" <EXP3> | <EXP3A> "<=" <EXP3> */ int exp3(Token **v){ int arg = exp3a(v); if(*v && TYPE(v) == OP && eq(TOKEN(v), ">")){ NEXT(v); return( arg && exp3(v)); }else if(*v && TYPE(v) == OP && eq(TOKEN(v), "<")){ NEXT(v); return( arg && exp3(v)); }else if(*v && TYPE(v) == OP && eq(TOKEN(v), ">=")){ NEXT(v); return( arg && exp3(v)); }else if(*v && TYPE(v) == OP && eq(TOKEN(v), "<=")){ NEXT(v); return( arg && exp3(v)); } else return arg; } /* <EXP3A> = <EXP4> | <EXP4> "<<" <EXP3A> | <EXP4> ">>" <EXP3A> */ int exp3a(Token **v){ int arg = exp4(v); if(*v && TYPE(v) == OP && eq(TOKEN(v), "<<")){ NEXT(v); return( arg << exp3a(v)); }else if(*v && TYPE(v) == OP && eq(TOKEN(v), ">>")){ NEXT(v); return( arg && exp3a(v)); } else return arg; } /* <EXP4> = <EXP5> | <EXP5> "+" <EXP4> | <EXP5> "-" <EXP4> */ int exp4(Token **v){ int arg = exp5(v); if(*v && TYPE(v) == OP && eq(TOKEN(v), "+")){ NEXT(v); return( arg + exp4(v)); }else if(*v && TYPE(v) == OP && eq(TOKEN(v), "-")){ NEXT(v); return( arg - exp4(v)); } else return arg; } /* <EXP5> = <EXP6> | <EXP6> "*" <EXP5> | <EXP6> "/" <EXP5> | <EXP6> "%" <EXP5> */ int exp5(Token **v){ int arg = exp6(v), arg1; if(*v && TYPE(v) == OP && eq(TOKEN(v), "*")){ NEXT(v); return( arg * exp5(v)); }else if(*v && TYPE(v) == OP && eq(TOKEN(v), "/")){ NEXT(v); if((arg1 = exp5(v)) == 0) ERR("Zero divide", arg); return( arg / arg1); }else if(*v && TYPE(v) == OP && eq(TOKEN(v), "%")){ NEXT(v); if((arg1 = exp5(v)) == 0) ERR("Zero module", arg); return( arg % arg1); } else return arg; } A. Bogatyrev, 1992-95 - 342 - Si v UNIX /* <EXP6> = "!"<EXP6> | "~"<EXP6> | "-"<EXP6> | "(" <EXPRESSION> ")" | <IMYAFUNKCII> "(" [ <EXPRESSION> [ "," <EXPRESSION> ]... ] ")" | <CHISLO> | <CH_PEREMENNAYA> */ int exp6(Token **v){ int arg; if( !*v) ERR("Lost token", 0); if(TYPE(v) == OP && eq(TOKEN(v), "!")){ NEXT(v); return !exp6(v); } if(TYPE(v) == OP && eq(TOKEN(v), "~")){ NEXT(v); return ~exp6(v); } if(TYPE(v) == OP && eq(TOKEN(v), "-")){ NEXT(v); return -exp6(v); /* unarnyj minus */ } if(TYPE(v) == OPEN){ NEXT(v); arg = expression(v); if( !*v || TYPE(v) != CLOSE) ERR("Lost ')'", arg); NEXT(v); return arg; } if(TYPE(v) == NUM){ /* izobrazhenie chisla */ arg = atoi(TOKEN(v)); NEXT(v); return arg; } if(TYPE(v) == ID){ char *name = (*v)->token; int args[20], nargs = 0; NEXT(v); if(! (*v && TYPE(v) == OPEN)){ /* Peremennaya */ return expvar(v, name); } /* Funkciya */ args[0] = 0; do{ NEXT(v); if( *v && TYPE(v) == CLOSE ) break; /* f() */ args[nargs++] = expression(v); } while( *v && TYPE(v) == COMMA); if(! (*v && TYPE(v) == CLOSE)) ERR("Error in '()'", args[0]); NEXT(v); return apply(name, args, nargs); } printlex(TOKEN(v), *v); ERR("Unknown token type", 0); } /* <CH_PEREMENNAYA> = <PEREMENNAYA> | <PEREMENNAYA> "++" | <PEREMENNAYA> "--" Nashi operacii ++ i -- sootvetstvuyut ++x i --x iz Si */ int expvar(Token **v, char *name){ int arg = getVar(name); Var *ptr = internVar(name); if(*v && TYPE(v) == OP){ if(eq(TOKEN(v), "++")){ NEXT(v); setVar(ptr, ++arg); return arg; } if(eq(TOKEN(v), "--")){ NEXT(v); setVar(ptr, --arg); return arg; } } return arg; } A. Bogatyrev, 1992-95 - 343 - Si v UNIX /* Golovnaya funkciya ------------------------------------------- */ char input[256]; Token *head, *tail; void main(){ do{ printf(": "); fflush(stdout); if( !gets(input)) break; if(!*input){ printVars(); continue; } if(eq(input, "!!")) ; /* nichego ne delat', t.e. povtorit' */ else{ if(head) freelex(&head); lex(&head, &tail, input); } printf("Result: %d\n", expr(head)); } while(1); putchar('\n'); } 7.69. Napishite programmu, vydelyayushchuyu n-oe pole iz kazhdoj stroki fajla. Polya razdelya- yutsya dvoetochiyami. Predusmotrite zadanie simvola-razdelitelya iz argumentov programmy. Ispol'zujte etu programmu dlya vydeleniya polya "domashnij katalog" iz fajla /etc/passwd. Dlya vydeleniya ocherednogo polya mozhno ispol'zovat' sleduyushchuyu proceduru: main(){ char c, *next, *strchr(); int nfield; char *s = "11111:222222222:333333:444444"; for(nfield=0;;nfield++){ if(next = strchr(s, ':')){ c= *next; *next= '\0'; } printf( "Pole #%d: '%s'\n", nfield, s); /* mozhno sdelat' s polem s chto-to eshche */ if(next){ *next= c; s= next+1; continue; } else { break; /* poslednee pole */ } } } 7.70. Razrabotajte arhitekturu i sistemu komand uchebnoj mashiny i napishite interpre- tator uchebnogo assemblera, otrabatyvayushchego po krajnej mere takie komandy: mov peresylka (:=) add slozhenie sub vychitanie cmp sravnenie i vyrabotka priznaka jmp perehod jeq perehod, esli == jlt perehod, esli < jle perehod, esli <= neg izmenenie znaka not invertirovanie priznaka 7.71. Napishite programmu, preobrazuyushchuyu opredeleniya funkcij Si v "starom" stile v "novyj" stil' standarta ANSI ("prototipy" funkcij). f(x, y, s, v) int x; char *s; struct elem *v; { ... } preobrazuetsya v int f(int x, int y, char *s, struct elem *v) { ... } (obratite vnimanie, chto peremennaya y i sama funkciya f opisany po umolchaniyu kak int). A. Bogatyrev, 1992-95 - 344 - Si v UNIX Eshche primer: char *ff() { ... } zamenyaetsya na char *ff(void){ ... } V dannoj zadache vam vozmozhno pridetsya ispol'zovat' programmu lex. V spiske argumentov prototipa dolzhny byt' yavno ukazany tipy vseh argumentov - opisatel' int nel'zya opuskat'. Tak q(x, s) char *s; { ... } // ne prototip, dopustimo. // x - int po umolchaniyu. q(x, char *s); // nedopustimo. q(int x, char *s); // verno. Sobstvenno pod "prototipom" ponimayut predvaritel'noe opisanie funkcii v novom stile - gde vmesto tela {...} srazu posle zagolovka stoit tochka s zapyatoj. long f(long x, long y); /* prototip */ ... long f(long x, long y){ return x+y; } /* realizaciya */ V prototipe imena argumentov mozhno opuskat': long f(long, long); /* prototip */ char *strchr(char *, char); |to predvaritel'noe opisanie pomeshchayut gde-nibud' v nachale programmy, do pervogo vyzova funkcii. V sovremennom Si prototipy zamenyayut opisaniya vida extern long f(); o kotoryh my govorili ran'she. Prototipy predostavlyayut programmistu mehanizm dlya avtomaticheskogo kontrolya formata vyzova funkcii. Tak, esli funkciya imeet prototip double f( double ); i vyzyvaetsya kak double x = f( 12 ); to kompilyator avtomaticheski prevratit eto v double x = f( (double) 12 ); (poskol'ku sushchestvuet privedenie tipa ot int k double); esli zhe napisano f( "privet" ); to kompilyator soobshchit ob oshibke (tak kak net preobrazovaniya tipa (char *) v double). Prototip prinuzhdaet kompilyator proveryat': a) sootvetstvie TIPOV fakticheskih parametrov (pri vyzove) tipam formal'nyh paramet- rov (v prototipe); b) sootvetstvie KOLICHESTVA fakticheskih i formal'nyh parametrov; c) tip vozvrashchaemogo funkciej znacheniya. Prototipy obychno pomeshchayut v include-fajly. Tak v ANSI standarte Si predusmotren fajl, podklyuchaemyj #include <stdlib.h> A. Bogatyrev, 1992-95 - 345 - Si v UNIX v kotorom opredeleny prototipy funkcij iz standartnoj biblioteki yazyka Si. CHerezvy- chajno polezno pisat' etu direktivu include, chtoby kompilyator proveryal, verno li vy vyzyvaete standartnye funkcii. Zametim, chto esli vy opredelili prototipy kakih-to funkcij, no v svoej programme ispol'zuete ne vse iz etih funkcij, to funkcii, sootvetstvuyushchie "lishnim" prototipam, NE budut dobavlyat'sya k vashej programme iz biblioteki. T.e. prototipy - eto ukazanie kompilyatoru; ni v kakie mashinnye komandy oni ne transliruyutsya. To zhe samoe kasaetsya opisanij vneshnih peremennyh i funkcij v vide extern int x; extern char *func(); Esli vy ne ispol'zuete peremennuyu ili funkciyu s takim imenem, to eti stroki ne imeyut nikakogo effekta (kak by voobshche otsutstvuyut). 7.72. Obratnaya zadacha: napishite preobrazovatel' iz novogo stilya v staryj. int f( int x, char *y ){ ... } perevodit' v int f( x, y ) int x; char *y; { ... } 7.73. Dovol'no legko ispol'zovat' prototipy takim obrazom, chto oni poteryayut vsyakij smysl. Dlya etogo nado napisat' programmu, sostoyashchuyu iz neskol'kih fajlov, i v kazhdom fajle ispol'zovat' svoi prototipy dlya odnoj i toj zhe funkcii. Tak byvaet, kogda vy pomenyali funkciyu i prototip v odnom fajle, byt' mozhet vo vtorom, no zabyli sdelat' eto v ostal'nyh. -------- fajl a.c -------- void g(void); void h(void); int x = 0, y = 13; void f(int arg){ printf("f(%d)\n", arg); x = arg; x++; } int main(int ac, char *av[]){ h(); f(1); g(); printf("x=%d y=%d\n", x, y); return 0; } A. Bogatyrev, 1992-95 - 346 - Si v UNIX -------- fajl b.c -------- extern int x, y; int f(int); void g(){ y = f(5); } -------- fajl c.c -------- void f(); void h(){ f(); } Vydacha programmy: abs@wizard$ cc a.c b.c c.c -o aaa a.c: b.c: c.c: abs@wizard$ aaa f(-277792360) f(1) f(5) x=6 y=5 abs@wizard$ Obratite vnimanie, chto vo vseh treh fajlah f() imeet raznye prototipy! Poetomu prog- ramma pechataet nechto, chto dovol'no-taki bessmyslenno! Reshenie takovo: starat'sya vynesti prototipy v include-fajl, chtoby vse fajly programmy vklyuchali odni i te zhe prototipy. Starat'sya, chtoby etot include-fajl vklyu- chalsya takzhe v fajl s samim opredeleniem funkcii. V takom sluchae izmenenie tol'ko zagolovka funkcii ili tol'ko prototipa vyzovet rugan' kompilyatora o nesootvetstvii. Vot kak dolzhen vyglyadet' nash proekt: ------------- fajl header.h ------------- extern int x, y; void f(int arg); int main(int ac, char *av[]); void g(void); void h(void); A. Bogatyrev, 1992-95 - 347 - Si v UNIX -------- fajl a.c -------- #include "header.h" int x = 0, y = 13; void f(int arg){ printf("f(%d)\n", arg); x = arg; x++; } int main(int ac, char *av[]){ h(); f(1); g(); printf("x=%d y=%d\n", x, y); return 0; } -------- fajl b.c -------- #include "header.h" void g(){ y = f(5); } -------- fajl c.c -------- #include "header.h" void h(){ f(); } Popytka kompilyacii: abs@wizard$ cc a.c b.c c.c -o aaa a.c: b.c: "b.c", line 4: operand cannot have void type: op "=" "b.c", line 4: assignment type mismatch: int "=" void cc: acomp failed for b.c c.c: "c.c", line 4: prototype mismatch: 0 args passed, 1 expected cc: acomp failed for c.c A. Bogatyrev, 1992-95 - 348 - Si v UNIX Terminal v UNIX s tochki zreniya programm - eto fajl. On predstavlyaet soboj dva ustrojstva: pri zapisi write() v etot fajl osushchestvlyaetsya vyvod na ekran; pri chtenii read()-om iz etogo fajla - chitaetsya informaciya s klaviatury. Sovremennye terminaly v opredelennom smysle yavlyayutsya ustrojstvami pryamogo dos- tupa: - informaciya mozhet byt' vyvedena v lyuboe mesto ekrana, a ne tol'ko posledovatel'no stroka za strokoj. - nekotorye terminaly pozvolyayut prochest' soderzhimoe proizvol'noj oblasti ekrana v vashu programmu. Tradicionnye terminaly yavlyayutsya samostoyatel'nymi ustrojstvami, obshchayushchimisya s komp'yu- terom cherez liniyu svyazi. Protokol|- obshcheniya obrazuet sistemu komand terminala i mozhet byt' razlichen dlya terminalov raznyh modelej. Poetomu biblioteka raboty s tradicionnym terminalom dolzhna reshat' sleduyushchie problemy: - nastrojka na sistemu komand dannogo ustrojstva, chtoby odna i ta zhe programma rabotala na raznyh tipah terminalov. - emulyaciya nedostayushchih v sisteme komand; maksimal'noe ispol'zovanie predostavlen- nyh terminalom vozmozhnostej. - mimnimizaciya peredachi dannyh cherez liniyu svyazi (dlya uskoreniya raboty). - bylo by polezno, chtoby biblioteka predostavlyala pol'zovatelyu nekotorye logiches- kie abstrakcii, vrode OKON - pryamougol'nyh oblastej na ekrane, vedushchih sebya podobno malen'kim terminalam. V UNIX eti zadachi reshaet standartnaya biblioteka curses (a tol'ko pervuyu zadachu - bolee prostaya biblioteka termcap). Dlya nastrojki na sistemu komand konkretnogo disp- leya eti biblioteki schityvayut opisanie sistemy komand, hranyashcheesya v fajle /etc/termcap. Krome nih byvayut i drugie ekrannye biblioteki, a takzhe sushchestvuyut inye sposoby raboty s ekranom (cherez videopamyat', sm. nizhe). V zadachah dannogo razdela vam pridetsya pol'zovat'sya bibliotekoj curses. Pri kom- pilyacii programm eta biblioteka podklyuchaetsya pri pomoshchi ukazaniya klyucha -lcurses, kak v sleduyushchem primere: cc progr.c -Ox -o progr -lcurses -lm Zdes' podklyuchayutsya dve biblioteki: /usr/lib/libcurses.a (rabota s ekranom) i /usr/lib/libm.a (matematicheskie funkcii, vrode sin, fabs). Klyuchi dlya podklyucheniya bibliotek dolzhny byt' zapisany v komande SAMYMI POSLEDNIMI. Zametim, chto standartnaya biblioteka yazyka Si (soderzhashchaya sistemnye vyzovy, biblioteku stdio (funkcii printf, scanf, fread, fseek, ...), raznye chasto upotreblyaemye funkcii (strlen, strcat, sleep, malloc, rand, ...)) /lib/libc.a podklyuchaetsya avtomaticheski i ne trebuet ukazaniya klyucha -lc. V nachale svoej programmy vy dolzhny napisat' direktivu #include <curses.h> podklyuchayushchuyu fajl /usr/include/curses.h, v kotorom opisany formaty dannyh, ispol'zue- myh bibliotekoj curses, nekotorye predopredelennye konstanty i.t.p. (eto nado, chtoby vasha programma pol'zovalas' imenno etimi standartnymi soglasheniyami). Posmotrite v etot fajl! Kogda vy pol'zuetes' curses-om, vy NE dolzhny pol'zovat'sya funkciyami standartnoj biblioteki stdio dlya neposredstvennogo vyvoda na ekran; tak vy ne dolzhny pol'zovat'sya ____________________ |- Pod protokolom v programmirovanii podrazumevayut ryad soglashenij dvuh storon (ser- vera i klientov; dvuh mashin v seti (kstati, termin dlya oboznacheniya mashiny v seti - "host" ili "site")) o formate (pravilah oformleniya) i smysle dannyh v peredavaemyh drug drugu soobshcheniyah. Analogiya iz zhizni - chelovecheskie rech' i yazyk. Rech' vseh lyu- dej sostoit iz odnih i teh zhe zvukov i mozhet byt' zapisana odnimi i temi zhe bukvami (a dannye - bajtami). No esli dva cheloveka govoryat na raznyh yazykah - t.e. po- raznomu konstruiruyut frazy i interpretiruyut zvuki - oni ne pojmut drug druga! A. Bogatyrev, 1992-95 - 349 - Si v UNIX funkciyami printf, putchar. |to proishodit potomu, chto curses hranit v pamyati pro- cessa kopiyu soderzhimogo ekrana, i esli vy vyvodite chto-libo na ekran terminala obhodya funkcii biblioteki curses, to real'noe soderzhimoe ekrana i poziciya kursora na nem perestayut sootvetstvovat' hranimym v pamyati, i biblioteka curses nachnet vyvodit' nep- ravil'noe izobrazhenie. PROGRAMMA | | | CURSES---kopiya ekrana | printw,addch,move | | V V biblioteka STDIO --printf,putchar----> ekran Takim obrazom, curses yavlyaetsya dopolnitel'nym "sloem" mezhdu vashej programmoj i stan- dartnym vyvodom i ignorirovat' etot sloj ne sleduet. Napomnim, chto izobrazhenie, sozdavaemoe pri pomoshchi biblioteki curses, snachala formiruetsya v pamyati programmy bez vypolneniya kakih-libo operacij s ekranom displeya (t.e. vse funkcii wmove, waddch, waddstr, wprintw izmenyayut tol'ko OBRAZY okon v pamyati, a na ekrane nichego ne proishodit!). I lish' tol'ko POSLE togo, kak vy vyzo- vete funkciyu refresh() ("obnovit'"), vse izmeneniya proisshedshie v oknah budut otobra- zheny na ekrane displeya (takoe odnovremennoe obnovlenie vseh izmenivshihsya chastej ekrana pozvolyaet provesti ryad optimizacij). Esli vy zabudete sdelat' refresh - ekran ostanetsya neizmennym. Obychno etu funkciyu vyzyvayut pered tem, kak zaprosit' u pol'zo- vatelya kakoj-libo vvod s klaviatury, chtoby pol'zovatel' uvidel tekushchuyu "svezhuyu" kar- tinku. Hranenie soderzhimogo okon v pamyati programmy pozvolyaet ej schityvat' soderzhi- moe okon, togda kak bol'shinstvo obychnyh terminalov ne sposobny vydat' v komp'yuter soderzhimoe kakoj-libo oblasti ekrana. Obshchenie s terminalom cherez liniyu svyazi (ili voobshche cherez posledovatel'nyj proto- kol) yavlyaetsya dovol'no medlennym. Na personal'nyh komp'yuterah sushchestvuet drugoj spo- sob raboty s ekranom: cherez pryamoj dostup v tak nazyvaemuyu "videopamyat'" - special'- nuyu oblast' pamyati komp'yutera, soderzhimoe kotoroj apparatno otobrazhaetsya na ekrane konsoli. Rabota s ekranom prevrashchaetsya dlya programmista v rabotu s etim massivom bajt (zapis'/chtenie). Programmy, pol'zuyushchiesya etim sposobom, prosty i rabotayut ochen' bystro (ibo dostup k pamyati cherezvychajno bystr, i sdelannye v nej izmeneniya "proyavlya- yutsya" na ekrane pochti mgnovenno). Nedostatok takih programm - privyazannost' k konk- retnomu tipu mashiny. |ti programmy nemobil'ny i ne mogut rabotat' ni na obychnyh ter- minalah (podklyuchaemyh k linii svyazi), ni na mashinah s drugoj strukturoj videopamyati. Vybor mezhdu "tradicionnoj" rabotoj s ekranom i pryamym dostupom (fakticheski - mezhdu mobil'nost'yu i skorost'yu) - vopros principial'nyj, tem ne menee prinyatie resheniya zavisit tol'ko ot vas. Videopamyat' IBM PC v tekstovom rezhime 80x25 16 cvetov imeet sleduyushchuyu strukturu: struct symbol{ /* IBM PC family */ char chr; /* kod simvola */ char attr; /* atributy simvola (cvet) */ } mem[ 25 ] [ 80 ]; /* 25 strok po 80 simvolov */ Struktura bajta atributov: ------------------------------------------- | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | # bita ------------------|------------------------ |blink| R | G | B | intensity | r | g | b | cvet ------------------|------------------------ background (fon) | foreground (cvet bukv) R - red (krasnyj) G - green (zelenyj) B - blue (sinij) blink - mercanie bukv (ne fona!) intensity - povyshennaya yarkost' A. Bogatyrev, 1992-95 - 350 - Si v UNIX Koordinatnaya sistema na ekrane: verhnij levyj ugol ekrana imeet koordinaty (0,0), os' X gorizontal'na, os' Y vertikal'na i napravlena sverhu vniz. Cvet simvola poluchaetsya smeshivaniem 3h cvetov: krasnogo, zelenogo i sinego (elektronno-luchevaya trubka displeya imeet 3 elektronnye pushki, otvechayushchie etim cve- tam). Krome togo, dopustimy bolee yarkie cveta. 4 bita zadayut kombinaciyu 3h osnovnyh cvetov i povyshennoj yarkosti. Obrazuetsya 2**4=16 cvetov: I R G B nomer cveta BLACK 0 0 0 0 0 chernyj BLUE 0 0 0 1 1 sinij GREEN 0 0 1 0 2 zelenyj CYAN 0 0 1 1 3 cianovyj (sero-goluboj) RED 0 1 0 0 4 krasnyj MAGENTA 0 1 0 1 5 malinovyj BROWN 0 1 1 0 6 korichnevyj LIGHTGRAY 0 1 1 1 7 svetlo-seryj (temno-belyj) DARKGRAY 1 0 0 0 8 temno-seryj LIGHTBLUE 1 0 0 1 9 svetlo-sinij LIGHTGREEN 1 0 1 0 10 svetlo-zelenyj LIGHTCYAN 1 0 1 1 11 svetlo-cianovyj LIGHTRED 1 1 0 0 12 yarko-krasnyj LIGHTMAGENTA 1 1 0 1 13 yarko-malinovyj YELLOW 1 1 1 0 14 zheltyj WHITE 1 1 1 1 15 (yarko)-belyj Fizicheskij adres videopamyati IBM PC v cvetnom alfavitno-cifrovom rezhime (80x25, 16 cvetov) raven 0xB800:0x0000. V MS DOS ukazatel' na etu pamyat' mozhno poluchit' pri pomoshchi makrosa make far pointer: MK_FP (eto dolzhen byt' far ili huge ukazatel'!). V XENIX|- ukazatel' poluchaetsya pri pomoshchi sistemnogo vyzova ioctl, prichem sistema pre- dostavit vam virtual'nyj adres, ibo privelegiya raboty s fizicheskimi adresami v UNIX prinadlezhit tol'ko sisteme. Rabotu s ekranom v XENIX vy mozhete uvidet' v primere "osypayushchiesya bukvy". 8.1. /*#! /bin/cc fall.c -o fall -lx * "Osypayushchiesya bukvy". * Ispol'zovanie videopamyati IBM PC v OS XENIX. * Dannaya programma illyustriruet dostup k ekranu * personal'nogo komp'yutera kak k massivu bajt; * vse izmeneniya v massive nemedlenno otobrazhayutsya na ekrane. * Funkciya nap() nahoditsya v biblioteke -lx * Pokazana takzhe rabota s portami IBM PC pri pomoshchi ioctl(). */ #include <stdio.h> #include <fcntl.h> /* O_RDWR */ #include <signal.h> #include <ctype.h> #include <sys/types.h> #include <sys/at_ansi.h> #include <sys/kd.h> /* for System V/4 and Interactive UNIX only */ /*#include <sys/machdep.h> for XENIX and SCO UNIX only */ #include <sys/sysmacros.h> ____________________ |- XENIX - (proiznositsya "ziniks") versiya UNIX dlya IBM PC, pervonachal'no razrabo- tannaya firmoj Microsoft i postavlyaemaya firmoj Santa Cruz Operation (SCO). A. Bogatyrev, 1992-95 - 351 - Si v UNIX #ifdef M_I386 # define far /* na 32-bitnoj mashine far ne trebuetsya */ #endif char far *screen; /* videopamyat' kak massiv bajt */ /* far - "dlinnyj" (32-bitnyj) adres(segment,offset) */ int segm; /* segment s videopamyat'yu */ #define COLS 80 /* chislo kolonok na ekrane */ #define LINES 25 /* chislo strok */ #define DELAY 20 /* zaderzhka (millisekund) */ int ega; /* deskriptor dlya dostupa k drajveru EGA */ /* struktura dlya obmena s portami */ static struct port_io_struct PORT[ 4 /* ne bolee 4 za raz */] = { /* operaciya nomer porta dannye */ /* .dir .port .data */ /* Pereustanovit' flip/flop: * zastavit' port 0x3C0 ozhidat' pary adres/znachenie * pri posledovatel'noj zapisi bajtov v etot port. */ { IN_ON_PORT, 0x3DA, -1 }, /* IN-chtenie */ /* Teper' 3c0 ozhidaet pary adres/znachenie */ { OUT_ON_PORT, 0x3C0, -1 /* adres */ }, { OUT_ON_PORT, 0x3C0, -1 /* znachenie*/ }, /* OUT-zapis' */ /* pereinicializirovat' displej, ustanoviv bit #5 porta 3c0 */ { OUT_ON_PORT, 0x3C0, 0x20 } }; void closescr(nsig){ /* konec raboty */ setbgcolor(0); /* ustanovit' chernyj fon ekrana */ exit(0); } /* poluchenie dostupa k videopamyati adaptera VGA/EGA/CGA */ void openscr () { static struct videodev { char *dev; int mapmode; } vd[] = { { "/dev/vga", MAPVGA }, { "/dev/ega", MAPEGA }, { "/dev/cga", MAPCGA }, { NULL, -1 } }, *v; /* ustrojstvo dlya dostupa k videoadapteru */ for(v=vd; v->dev;v++ ) if((ega = open (v->dev, O_RDWR)) >= 0 ) goto ok; fprintf( stderr, "Can't open video adapter\n" ); exit(1); A. Bogatyrev, 1992-95 - 352 - Si v UNIX ok: /* fprintf(stderr, "Adapter:%s\n", v->dev); */ /* poluchit' adres videopamyati i dostup k nej */ #ifdef M_I386 screen = (char *) ioctl (ega, v->mapmode, 0); #else segm = ioctl (ega, v->mapmode, 0); screen = sotofar (segm, 0); /* (segment,offset) to far pointer */ #endif signal( SIGINT, closescr ); } /* makrosy dlya dostupa k bajtam "simvol" i "atributy" * v koordinatah (x,y) ekrana. */ #define GET(x,y) screen[ ((x) + (y) * COLS ) * 2 ] #define PUT(x,y, c) screen[ ((x) + (y) * COLS ) * 2 ] = (c) #define GETATTR(x,y) screen[ ((x) + (y) * COLS ) * 2 + 1 ] #define PUTATTR(x,y, a) screen[ ((x) + (y) * COLS ) * 2 + 1 ] = (a) /* simvol izobrazhaetsya kak chernyj probel ? */ #define white(c,a) ((isspace(c) || c==0) && (attr & 0160)==0) /* ustanovit' cvet fona ekrana */ void setbgcolor( color ){ PORT[1].data = 0; /* registr nomer 0 palitry soderzhit cvet fona */ /* vsego v palitre 16 registrov (0x00...0xFF) */ PORT[2].data = color ; /* novoe znachenie cveta, sostavlennoe kak bitovaya maska * RGBrgb (r- krasnyj, g- zelenyj, b- sinij, RGB- dopolnitel'nye * tusklye cveta) */ /* vypolnit' obmeny s portami */ if( ioctl( ega, EGAIO, PORT ) < 0 ){ fprintf( stderr, "Can't out port\n" ); perror( "out" ); } } A. Bogatyrev, 1992-95 - 353 - Si v UNIX void main(ac, av) char **av;{ void fall(); openscr(); if( ac == 1 ){ setbgcolor(020); /* temno-zelenyj fon ekrana */ fall(); /* osypanie bukv */ } else { if(*av[1] == 'g') /* Ustanovit' rezhim adaptera graphics 640x350 16-colors */ ioctl( ega, SW_CG640x350, NULL); /* Esli vy hotite poluchit' adres videopamyati v graficheskom rezhime, * vy dolzhny SNACHALA vklyuchit' etot rezhim, * ZATEM sdelat' screen=ioctl(ega, v->mapmode, NULL); * i ESHCHE RAZ sdelat' vklyuchenie graficheskogo rezhima. */ /* Ustanovit' rezhim adaptera text 80x25 16-colors */ else ioctl( ega, SW_ENHC80x25, NULL); } closescr(0); } /* osypat' bukvy vniz */ void fall(){ register i, j; int rest; int nextcol; int n; int changed = 1; /* ne 0, esli eshche ne vse bukvy opali */ char mask [ COLS ]; while( changed ){ changed = 0; for( i = 0 ; i < COLS ; i++ ) mask[ i ] = 0; for( i = 0 ; i < COLS ; i++ ){ rest = COLS - i; /* ostalos' osypat' kolonok */ nextcol = rand() % rest; j = 0; /* indeks v mask */ n = 0; /* schetchik */ for(;;){ if( mask[j] == 0 ){ if( n == nextcol ) break; n++; } j++; } changed += fallColumn( j ); mask[j] = 1; } } } A. Bogatyrev, 1992-95 - 354 - Si v UNIX /* osypat' bukvy v odnom stolbce */ int fallColumn( x ){ register int y; char ch, attr; int firstspace = (-1); int firstnospace = (-1); Again: /* find the falled array */ for( y=LINES-1; y >= 0 ; y-- ){ ch = GET( x, y ); attr = GETATTR( x,y ); if( white(ch, attr)){ firstspace = y; goto FindNoSpace; } } AllNoSpaces: return 0; /* nichego ne izmenilos' */ FindNoSpace: /* najti ne probel */ for( ; y >= 0 ; y-- ){ ch = GET( x, y ); attr = GETATTR( x, y ); if( !white(ch, attr)){ firstnospace = y; goto Fall; } } AllSpaces: /* v dannom stolbce vse upalo */ return 0; Fall: /* "uronit'" bukvu */ for( y = firstnospace ; y < firstspace ; y++ ){ /* peremestit' simvol na ekrane na odnu poziciyu vniz */ ch = GET( x, y ); attr = GETATTR( x, y ); PUT( x, y, 0 ); PUTATTR( x, y, 0 ); PUT( x, y+1 , ch ); PUTATTR( x, y+1, attr ); nap( DELAY ); /* podozhdat' DELAY millisekund */ } return 1; /* chto-to izmenilos' */ } 8.2. Dlya raboty mozhet okazat'sya bolee udobnym imet' ukazatel' na videopamyat' kak na massiv struktur. Privedem primer dlya sistemy MS DOS: A. Bogatyrev, 1992-95 - 355 - Si v UNIX #include <dos.h> /* tam opredeleno MK_FP */ char far *screen = MK_FP(0xB800 /*segment*/, 0x0000 /*smeshchenie*/); struct symb{ char chr; char attr; } far *scr, far *ptr; #define COLS 80 /* chislo kolonok */ #define LINES 25 /* chislo strok */ #define SCR(x,y) scr[(x) + COLS * (y)] /* x iz 0..79, y iz 0..24 */ void main(){ int x, y; char c; scr = (struct symb far *) screen; /* ili srazu * scr = (struct symb far *) MK_FP(0xB800,0x0000); */ /* perepisat' stroki ekrana sprava nalevo */ for(x=0; x < COLS/2; x++ ) for( y=0; y < LINES; y++ ){ c = SCR(x,y).chr; SCR(x,y).chr = SCR(COLS-1-x, y).chr; SCR(COLS-1-x, y).chr = c; } /* sdelat' cvet ekrana: zheltym po sinemu */ for(x=0; x < COLS; x++) for(y=0; y < LINES; y++) SCR(x,y).attr = (0xE | (0x1 << 4)); /* zheltyj + sinij fon */ /* prochest' lyubuyu knopku s klaviatury (pauza) */ (void) getch(); } I, nakonec, eshche udobnee rabota s videopamyat'yu kak s dvumernym massivom struktur: #include <dos.h> /* MS DOS */ #define COLS 80 #define LINES 25 struct symb { char chr; char attr; } (far *scr)[ COLS ] = MK_FP(0xB800, 0); void main(void){ register x, y; for(y=0; y < LINES; y++) for(x=0; x < COLS; ++x){ scr[y][x].chr = '?'; scr[y][x].attr = (y << 4) | (x & 0xF); } getch(); } Uchtite, chto pri rabote s ekranom cherez videopamyat', kursor ne peremeshchaetsya! Esli v A. Bogatyrev, 1992-95 - 356 - Si v UNIX obychnoj rabote s ekranom tekst vyvoditsya v pozicii kursora i kursor avtomaticheski prodvigaetsya, to zdes' kursor budet ostavat'sya na svoem prezhnem meste. Dlya peremeshche- niya kursora v nuzhnoe vam mesto, vy dolzhny ego postavit' yavnym obrazom po okonchanii zapisi v videopamyat' (naprimer, obrashchayas' k portam videokontrollera). Obratite vnimanie, chto specifikator modeli pamyati far dolzhen ukazyvat'sya pered KAZHDYM ukazatelem (imenno dlya illyustracii etogo v pervom primere opisan neispol'zue- myj ukazatel' ptr). 8.3. Sostav'te programmu sohraneniya soderzhimogo ekrana IBM PC (videopamyati) v teks- tovom rezhime v fajl i obratno (v sisteme XENIX). 8.4. Pol'zuyas' pryamym dostupom v videopamyat', napishite funkcii dlya spaseniya pryamou- gol'noj oblasti ekrana v massiv i obratno. Vot funkciya dlya spaseniya v massiv: typedef struct { short xlen, ylen; char *save; } Pict; extern void *malloc(unsigned); Pict *gettext (int x, int y, int xlen, int ylen){ Pict *n = (Pict *) malloc(sizeof *n); register char *s; register i, j; n->xlen = xlen; n->ylen = ylen; s = n->save = (char *) malloc( 2 * xlen * ylen ); for(i=y; i < y+ylen; i++) for(j=x; j < x+xlen; j++){ *s++ = SCR(j,i).chr ; *s++ = SCR(j,i).attr; } return n; } Dobav'te proverki na korrektnost' xlen, ylen (v predelah ekrana). Napishite funkciyu puttext dlya vyvoda spasennoj oblasti obratno; funkciyu free(buf) luchshe v nee ne vstav- lyat'. void puttext (Pict *n, int x, int y){ register char *s = n->save; register i, j; for(i=y; i < y + n->ylen; i++) for(j=x; j < x + n->xlen; j++){ SCR(j,i).chr = *s++; SCR(j,i).attr = *s++; } } /* ochistka pamyati tekstovogo bufera */ void deltext(Pict *n){ free(n->save); free(n); } Privedem eshche odnu poleznuyu funkciyu, kotoraya mozhet vam prigodit'sya - eto analog printf pri pryamoj rabote s videopamyat'yu. #include <stdarg.h> /* tekushchij cvet: belyj po sinemu */ static char currentColor = 0x1F; int videoprintf (int x, int y, char *fmt, ...){ char buf[512], *s; va_list var; A. Bogatyrev, 1992-95 - 357 - Si v UNIX /* clipping (otsechenie po granicam ekrana) */ if( y < 0 || y >= LINES ) return x; va_start(var, fmt); vsprintf(buf, fmt, var); va_end(var); for(s=buf; *s; s++, x++){ /* otsechenie */ if(x < 0 ) continue; if(x >= COLS) break; SCR(x,y).chr = *s; SCR(x,y).attr = currentColor; } return x; } void setcolor (int col){ currentColor = col; } 8.5. Pol'zuyas' napisannymi funkciyami, realizujte funkcii dlya "vyskakivayushchih" okon (pop-up window): Pict *save; save = gettext (x,y,xlen,ylen); // ... risuem cvetnymi probelami pryamougol'nik s // uglami (x,y) vverhu-sleva i (x+xlen-1,y+ylen-1) // vnizu-sprava... // ...risuem nekie tablicy, menyu, tekst v etoj zone... // stiraem narisovannoe okno, vosstanoviv to izobrazhenie, // poverh kotorogo ono "vsplylo". puttext (save,x,y); deltext (save); Dlya nachala napishite "vyskakivayushchee" okno s soobshcheniem; okno dolzhno ischezat' po nazha- tiyu lyuboj klavishi. c = message(x, y, text); Razmer okna vychislyajte po dline stroki text. Kod klavishi vozvrashchajte v kachestve zna- cheniya funkcii. Teper' sdelajte text massivom strok: char *text[]; (poslednyaya stroka - NULL). 8.6. Sdelajte tak, chtoby "vyskakivayushchie" okna imeli ten'. Dlya etogo nado sohranit' v nekotoryj bufer atributy simvolov (sami simvoly ne nado!), nahodyashchihsya na mestah $: ########## ##########$ ##########$ $$$$$$$$$$ a zatem propisat' etim simvolam na ekrane atribut 0x07 (belyj po chernomu). Pri stira- nii okna (puttext-om) sleduet vosstanovit' spasennye atributy etih simvolov (steret' ten'). Esli okno imeet razmer xlen*ylen, to razmer bufera raven xlen+ylen-1 bajt. 8.7. Napishite funkciyu, risuyushchuyu na ekrane pryamougol'nuyu ramku. Ispol'zujte ee dlya risovaniya ramki okna. A. Bogatyrev, 1992-95 - 358 - Si v UNIX 8.8. Napishite "vyskakivayushchee" okno, kotoroe proyavlyaetsya na ekrane kak by rasshiryayas' iz tochki: ############## ###### ############## ### ###### ############## ###### ############## ############## Vam sleduet napisat' funkciyu box(x,y,width,height), risuyushchuyu cvetnoj pryamougol'nik s verhnim levym uglom (x,y) i razmerom (width,height). Pust' konechnoe okno zadaetsya uglom (x0,y0) i razmerom (W,H). Togda "vyrastanie" okna opisyvaetsya takim algoritmom: void zoom(int x0, int y0, int W, int H){ int x, y, w, h, hprev; /* promezhutochnoe okno */ for(hprev=0, w=1; w < W; w++){ h = H * w; h /= W; /* W/H == w/h */ if(h == hprev) continue; hprev = h; x = x0 + (W - w)/2; /* chtoby centry okon */ y = y0 + (H - h)/2; /* sovpadali */ box(x, y, w, h); delay(10); /* zaderzhka 10 millisek. */ } box(x0, y0, W, H); } 8.9. Sostav'te biblioteku funkcij, analogichnyh biblioteke curses, dlya |VM IBM PC v OS XENIX. Ispol'zujte pryamoj dostup v videopamyat'. 8.10. Napishite rekursivnoe reshenie zadachi "hanojskie bashni" (perekladyvanie diskov: est' tri sterzhnya, na odin iz nih nadety diski ubyvayushchego k vershine diametra. Trebu- etsya perelozhit' ih na tretij sterzhen', nikogda ne kladya disk bol'shego diametra poverh diska men'shego diametra). Uslozhnenie - ispol'zujte paket curses dlya izobrazheniya perekladyvaniya diskov na ekrane terminala. Ukazanie: ideya rekursivnogo algoritma: carry(n, from, to, by) = if( n > 0 ){ carry( n-1, from, by, to ); perenesiOdinDisk( from, to ); carry( n-1, by, to, from ); } Vyzov: carry( n, 0, 1, 2 ); n - skol'ko diskov perenesti (n > 0). from - otkuda (nomer sterzhnya). to - kuda. by - pri pomoshchi (promezhutochnyj sterzhen'). n diskov potrebuyut (2**n)-1 perenosov. 8.11. Napishite programmu, ishchushchuyu vyhod iz labirinta ("chervyak v labirinte"). Labi- rint zagruzhaetsya iz fajla .maze (ne zabud'te pro rasshirenie tabulyacij!). Algoritm imeet rekursivnuyu prirodu i vyglyadit primerno tak: #include <setjmp.h> jmp_buf jmp; int found = 0; maze(){ /* |to golovnaya funkciya */ if( setjmp(jmp) == 0 ){ /* nachalo */ if( neStenka(x_vhoda, y_vhoda)) GO( x_vhoda, y_vhoda); A. Bogatyrev, 1992-95 - 359 - Si v UNIX } } GO(x, y){ /* pojti v tochku (x, y) */ if( etoVyhod(x, y)){ found = 1; /* nashel vyhod */ pometit'(x, y); longjmp(jmp, 1);} pometit'(x, y); if( neStenka(x-1,y)) GO(x-1, y); /* vlevo */ if( neStenka(x,y-1)) GO(x, y-1); /* vverh */ if( neStenka(x+1,y)) GO(x+1, y); /* vpravo */ if( neStenka(x,y+1)) GO(x, y+1); /* vniz */ snyat'Pometku(x, y); } #define pometit'(x, y) labirint[y][x] = '*' #define snyat'Pometku(x, y) labirint[y][x] = ' ' #define etoVyhod(x, y) (x == x_vyhoda && y == y_vyhoda) /* mozhno iskat' "zoloto": (labirint[y][x] == '$') */ neStenka(x, y){ /* stenku izobrazhajte simvolom @ ili # */ if( koordinatyVnePolya(x, y)) return 0; /*kraj labirinta*/ return (labirint[y][x] == ' '); } Otobrazite massiv labirint na videopamyat' (ili vospol'zujtes' curses-om). Vy uvidite chervyaka, polzayushchego po labirintu v svoih iskaniyah. 8.12. Ispol'zuya biblioteku termcap napishite funkcii dlya: - ochistki ekrana. - pozicionirovaniya kursora. - vklyucheniya/vyklyucheniya rezhima vydeleniya teksta inversiej. 8.13. Ispol'zuya napisannye funkcii, realizujte programmu vybora v menyu. Vybrannuyu stroku vydelyajte inversiej fona. /*#!/bin/cc termio.c -O -o termio -ltermcap * Smotri man termio, termcap i screen. * Rabota s terminalom v stile System-V. * Rabota s sistemoj komand terminala cherez /etc/termcap * Rabota so vremenem. * Rabota s budil'nikom. */ #include <stdio.h> /* standard input/output */ #include <sys/types.h> /* system typedefs */ #include <termio.h> /* terminal input/output */ #include <signal.h> /* signals */ #include <fcntl.h> /* file control */ #include <time.h> /* time structure */ void setsigs(), drawItem(), drawTitle(), prSelects(), printTime(); A. Bogatyrev, 1992-95 - 360 - Si v UNIX /* Rabota s opisaniem terminala TERMCAP ---------------------------------*/ extern char *getenv (); /* poluchit' peremennuyu okruzheniya */ extern char *tgetstr (); /* poluchit' strochnyj opisatel' /termcap/ */ extern char *tgoto (); /* podstavit' %-parametry /termcap/ */ static char Tbuf[2048], /* bufer dlya opisaniya terminala, obychno 1024 */ /* Tbuf[] mozhno sdelat' lokal'noj avtomaticheskoj peremennoj * v funkcii tinit(), chtoby ne zanimat' mesto */ Strings[256], /* bufer dlya rasshifrovannyh opisatelej */ *p; /* vspomogatel'naya perem. */ char *tname; /* nazvanie tipa terminala */ int COLS, /* chislo kolonok ekrana */ LINES; /* chislo strok ekrana */ char *CM; /* opisatel': cursor motion */ char *CL; /* opisatel': clear screen */ char *CE; /* opisatel': clear end of line */ char *SO, *SE; /* opisateli: standout Start i End */ char *BOLD, *NORM; /* opisateli: boldface and NoStandout */ int BSflag; /* mozhno ispol'zovat' back space '\b' */ void tinit () { /* Funkciya nastrojki na sistemu komand displeya */ p = Strings; /* Prochest' opisanie terminala v Tbuf */ switch (tgetent (Tbuf, tname = getenv ("TERM"))) { case -1: printf ("Net fajla TERMCAP (/etc/termcap).\n"); exit (1); case 0: printf ("Terminal %s ne opisan.\n", tname); exit (2); case 1: break; /* OK */ } COLS = tgetnum ("co"); /* Prochest' chislovye opisateli. */ LINES = tgetnum ("li"); CM = tgetstr ("cm", &p); /* Prochest' strochnye opisateli. */ CL = tgetstr ("cl", &p); /* Opisatel' deshifruetsya i zanositsya */ CE = tgetstr ("ce", &p); /* v massiv po adresu p. Zatem */ SO = tgetstr ("so", &p); /* ukazatel' p prodvigaetsya na */ SE = tgetstr ("se", &p); /* svobodnoe mesto, a adres rasshif- */ BOLD = tgetstr ("md", &p); /* rovannoj stroki vydaetsya iz f-cii */ NORM = tgetstr ("me", &p); BSflag = tgetflag( "bs" ); /* Uznat' znachenie flazhka: 1 - est', 0 - net */ } A. Bogatyrev, 1992-95 - 361 - Si v UNIX /* Makros, vnesennyj v funkciyu. Delo v tom, chto tputs v kachestve tret'ego argumenta trebuet imya funkcii, kotoruyu ona vyzyvaet v cikle: (*f)(c); Esli podat' na vhod makros, vrode putchar, a ne adres vhoda v funkciyu, my i ne dostignem zhelannogo effekta, i poluchim rugan' ot kompilyatora. */ void put (c) char c; { putchar (c); } /* ochistit' ekran */ void clearScreen () { if (CL == NULL) /* Funkciya tputs() dorasshifrovyvaet opisatel' */ return; /* (obrabatyvaya zaderzhki) i vydaet ego */ tputs (CL, 1, put); /* posimvol'no f-ciej put(c) 1 raz */ /* Mozhno vydat' komandu ne 1 raz, a neskol'ko: naprimer esli eto */ /* komanda sdviga kursora na 1 poziciyu vlevo '\b' */ } /* ochistit' konec stroki, kursor ostaetsya na meste */ void clearEOL () { /* clear to the end of line */ if (CE == NULL) return; tputs (CE, 1, put); } /* pozicionirovat' kursor */ void gotoXY (x, y) { /* y - po vertikali SVERHU-VNIZ. */ if (x < 0 || y < 0 || x >= COLS || y >= LINES) { printf ("Tochka (%d,%d) vne ekrana\n", x, y); return; } /* CM - opisatel', soderzhashchij 2 parametra. Podstanovku parametrov * delaet funkciya tgoto() */ tputs (tgoto (CM, x, y), 1, put); } /* vklyuchit' vydelenie */ void standout () { if (SO) tputs (SO, 1, put); } /* vyklyuchit' vydelenie */ void standend () { if (SE) tputs (SE, 1, put); /* else normal(); */ } /* vklyuchit' zhirnyj shrift */ void bold () { if (BOLD) tputs (BOLD, 1, put); } A. Bogatyrev, 1992-95 - 362 - Si v UNIX /* vyklyuchit' lyuboj neobychnyj shrift */ void normal () { if (NORM) tputs (NORM, 1, put); else standend(); } /* Upravlenie drajverom terminala --------------------------------- */ #define ESC '\033' #define ctrl(c) ((c) & 037 ) int curMode = 0; int inited = 0; struct termio old, new; int fdtty; void ttinit () { /* otkryt' terminal v rezhime "chtenie bez ozhidaniya" */ fdtty = open ("/dev/tty", O_RDWR | O_NDELAY); /* uznat' tekushchie rezhimy drajvera */ ioctl (fdtty, TCGETA, &old); new = old; /* input flags */ /* otmenit' preobrazovanie koda '\r' v '\n' na vvode */ new.c_iflag &= ~ICRNL; if ((old.c_cflag & CSIZE) == CS8) /* 8-bitnyj kod */ new.c_iflag &= ~ISTRIP; /* otmenit' & 0177 na vvode */ /* output flags */ /* otmenit' TAB3 - zamenu tabulyacij '\t' na probely */ /* otmenit' ONLCR - zamenu '\n' na paru '\r\n' na vyvode */ new.c_oflag &= ~(TAB3 | ONLCR); /* local flags */ /* vyklyuchit' rezhim ICANON, vklyuchit' CBREAK */ /* vyklyuchit' ehootobrazhenie nabiraemyh simvolov */ new.c_lflag &= ~(ICANON | ECHO); /* control chars */ /* pri vvode s klavish zhdat' ne bolee ... */ new.c_cc[VMIN] = 1; /* 1 simvola i */ new.c_cc[VTIME] = 0; /* 0 sekund */ /* |to sootvetstvuet rezhimu CBREAK */ /* Simvoly, nazhatie kotoryh zastavlyaet drajver terminala poslat' signal * libo otredaktirovat' nabrannuyu stroku. Znachenie 0 oznachaet, * chto sootvetstvuyushchego simvola ne budet */ new.c_cc[VINTR] = ctrl ('C'); /* simvol, generyashchij SIGINT */ new.c_cc[VQUIT] = '\0'; /* simvol, generyashchij SIGQUIT */ new.c_cc[VERASE] = '\0'; /* zaboj (otmena poslednego simvola)*/ new.c_cc[VKILL] = '\0'; /* simvol otmeny stroki */ /* Po umolchaniyu eti knopki ravny: DEL, CTRL/\, BACKSPACE, CTRL/U */ setsigs (); inited = 1; /* uzhe inicializirovano */ } A. Bogatyrev, 1992-95 - 363 - Si v UNIX void openVisual () { /* open visual mode (vklyuchit' "ekrannyj" rezhim) */ if (!inited) ttinit (); if (curMode == 1) return; /* ustanovit' mody drajvera iz struktury new */ ioctl (fdtty, TCSETAW, &new); curMode = 1; /* ekrannyj rezhim */ } void closeVisual () { /* canon mode (vklyuchit' kanonicheskij rezhim) */ if (!inited) ttinit (); if (curMode == 0) return; ioctl (fdtty, TCSETAW, &old); curMode = 0; /* kanonicheskij rezhim */ } /* zavershit' process */ void die (nsig) { normal(); closeVisual (); /* Pri zavershenii programmy (v tom chisle po * signalu) my dolzhny vosstanovit' prezhnie rezhimy drajvera, * chtoby terminal okazalsya v korrektnom sostoyanii. */ gotoXY (0, LINES - 1); putchar ('\n'); if (nsig) printf ("Prishel signal #%d\n", nsig); exit (nsig); } void setsigs () { register ns; /* Perehvatyvat' vse signaly; zavershat'sya po nim. */ /* UNIX imeet 15 standartnyh signalov. */ for (ns = 1; ns <= 15; ns++) signal (ns, die); } A. Bogatyrev, 1992-95 - 364 - Si v UNIX /* Rabota s menyu -------------------------------------------- */ struct menu { char *m_text; /* vydavaemaya stroka */ int m_label; /* pomechena li ona ? */ } menuText[] = { /* nazvaniya pesen Beatles */ { "Across the Universe", 0 } , { "All I've got to do", 0 } , { "All my loving", 0 } , { "All together now", 0 } , { "All You need is love",0 } , { "And I love her", 0 } , { "And your bird can sing", 0 } , { "Another girl", 0 } , { "Any time at all", 0 } , { "Ask me why", 0 } , { NULL, 0 } }; #define Y_TOP 6 int nitems; /* kolichestvo strok v menyu */ int nselected = 0; /* kolichestvo vybrannyh strok */ char title[] = "PROBEL - vniz, ZABOJ - vverh, ESC - vyhod, \ ENTER - vybrat', TAB - otmenit'"; # define TIMELINE 1 void main (ac, av) char **av; { char **line; register i; int c; int n; /* tekushchaya stroka */ extern char readkey (); /* forward */ extern char *ttyname (); /* imya terminala */ char *mytty; extern char *getlogin (); /* imya pol'zovatelya */ char *userName = getlogin (); srand (getpid () + getuid ()); /* inicializirovat' * datchik sluchajnyh chisel */ /* schitaem stroki menyu */ for (nitems = 0; menuText[nitems].m_text != NULL; nitems++); /* inicializiruem terminal */ tinit (); ttinit(); mytty = ttyname(fdtty); openVisual (); A. Bogatyrev, 1992-95 - 365 - Si v UNIX again: clearScreen (); if (mytty != NULL && userName != NULL) { gotoXY (0, TIMELINE); bold (); printf ("%s", userName); normal (); printf (" at %s (%s)", mytty, tname); } drawTitle ("", Y_TOP - 4); drawTitle (title, Y_TOP - 3); drawTitle ("", Y_TOP - 2); /* risuem menyu */ for (i = 0; i < nitems; i++) { drawItem (i, 20, Y_TOP + i, 0); } /* cikl peremeshchenij po menyu */ for (n=0; ; ) { printTime (); /* vydaem tekushchee vremya */ drawItem (n, 20, Y_TOP + n, 1); c = getcharacter (); drawItem (n, 20, Y_TOP + n, 0); switch (c) { case ' ': go_down: n++; if (n == nitems) n = 0; break; case '\b': case 0177: n--; if (n < 0) n = nitems - 1; break; case ESC: goto out; case '\t': /* Unselect item */ if (menuText[n].m_label != 0) { menuText[n].m_label = 0; drawItem (n, 20, Y_TOP + n, 0); nselected--; prSelects (); } goto go_down; A. Bogatyrev, 1992-95 - 366 - Si v UNIX case '\r': /* Select item */ case '\n': bold (); drawTitle (menuText[n].m_text, LINES - 2); /* last but two line */ normal (); if (menuText[n].m_label == 0) { menuText[n].m_label = 1; drawItem (n, 20, Y_TOP + n, 0); nselected++; prSelects (); } goto go_down; default: goto go_down; } } out: clearScreen (); gotoXY (COLS / 3, LINES / 2); bold (); printf ("Nazhmi lyubuyu knopku."); normal (); /* zamusorit' ekran */ while (!(c = readkey ())) { /* sluchajnye tochki */ gotoXY (rand () % (COLS - 1), rand () % LINES); putchar ("@.*"[rand () % 3]); /* vydat' simvol */ fflush (stdout); } standout (); printf ("Nazhata knopka s kodom 0%o\n", c & 0377); standend (); if (c == ESC) { sleep (2); /* podozhdat' 2 sekundy */ goto again; } die (0); /* uspeshno zavershit'sya, * vosstanoviv rezhimy drajvera */ } A. Bogatyrev, 1992-95 - 367 - Si v UNIX /* Narisovat' stroku menyu nomer i * v koordinatah (x,y) s ili bez vydeleniya */ void drawItem (i, x, y, out) { gotoXY (x, y); if (out) { standout (); bold (); } printf ("%c %s ", menuText[i].m_label ? '-' : ' ', /* pomecheno ili net */ menuText[i].m_text /* sama stroka */ ); if (out) { standend (); normal (); } } /* narisovat' centrirovannuyu stroku v inversnom izobrazhenii */ void drawTitle (title, y) char *title; { register int n; int length = strlen (title); /* dlina stroki */ gotoXY (0, y); /* clearEOL(); */ standout (); for (n = 0; n < (COLS - length) / 2; n++) putchar (' '); printf ("%s", title); n += length; /* dorisovat' inversiej do konca ekrana */ for (; n < COLS - 1; n++) putchar (' '); standend (); } /* vydat' obshchee chislo vybrannyh strok */ void prSelects () { char buffer[30]; if (nselected == 0) { gotoXY (0, LINES - 1); clearEOL (); } else { sprintf (buffer, "Vybrano: %d/%d", nselected, nitems); drawTitle (buffer, LINES - 1); } } A. Bogatyrev, 1992-95 - 368 - Si v UNIX /* Rabota s budil'nikom -------------------------- */ #define PAUSE 4 int alarmed; /* flag budil'nika */ /* reakciya na signal "budil'nik" */ void onalarm (nsig) { alarmed = 1; } /* Prochest' simvol s klaviatury, no ne pozzhe chem cherez PAUSE sekund. * inache vernut' kod 'probel'. */ int getcharacter () { int c; fflush(stdout); /* zakazat' reakciyu na budil'nik */ signal (SIGALRM, onalarm); alarmed = 0; /* sbrosit' flag */ /* zakazat' signal "budil'nik" cherez PAUSE sekund */ alarm (PAUSE); /* zhdat' nazhatiya knopki. * |tot operator zavershitsya libo pri nazhatii knopki, * libo pri poluchenii signala. */ c = getchar (); /* proveryaem flag */ if (!alarmed) { /* byl nazhat simvol */ alarm (0); /* otmenit' zakaz budil'nika */ return c; } /* byl poluchen signal "budil'nik" */ return ' '; /* prodvinut' vybrannuyu stroku vniz */ } /* ---- NDELAY read ----------------------------- */ /* Vernut' 0 esli na klaviature nichego ne nazhato, * inache vernut' nazhatuyu knopku */ char readkey () { char c; int nread; nread = read (fdtty, &c, 1); /* obychnyj read() dozhidalsya by nazhatiya knopki. * O_NDELAY pozvolyaet ne zhdat', no vernut' "prochitano 0 simvolov". */ return (nread == 0) ? 0 : c; } A. Bogatyrev, 1992-95 - 369 - Si v UNIX /* -------- Rabota so vremenem ------------------------ */ void printTime () { time_t t; /* tekushchee vremya */ struct tm *tm; extern struct tm *localtime (); char tmbuf[30]; static char *week[7] = { "Vs", "Pn", "Vt", "Sr", "CHt", "Pt", "Sb" }; static char *month[12] = { "YAnv", "Fev", "Mar", "Apr", "Maj", "Iyun", "Iyul", "Avg", "Sen", "Okt", "Noya", "Dek" }; time (&t); /* uznat' tekushchee vremya */ tm = localtime (&t); /* razlozhit' ego na komponenty */ sprintf (tmbuf, "%2s %02d:%02d:%02d %02d-%3s-%d", week[tm -> tm_wday], /* den' nedeli (0..6) */ tm -> tm_hour, /* chasy (0..23) */ tm -> tm_min , /* minuty (0..59) */ tm -> tm_sec , /* sekundy (0..59) */ tm -> tm_mday, /* chislo mesyaca (1..31) */ month[tm -> tm_mon], /* mesyac (0..11) */ tm -> tm_year + 1900 /* god */ ); gotoXY (COLS / 2, TIMELINE); clearEOL (); gotoXY (COLS - strlen (tmbuf) - 1, TIMELINE); bold (); printf ("%s", tmbuf); normal (); } 8.14. Napishite programmu, vydayushchuyu fajl na ekran porciyami po 20 strok i ozhidayushchuyu nazhatiya klavishi. Uslozhneniya: a) dobavit' klavishu dlya vozvrata k nachalu fajla. b) ispol'zuya biblioteku termcap, ochishchat' ekran pered vydachej ocherednoj porcii teksta. c) napishite etu programmu, ispol'zuya biblioteku curses. d) ispol'zuya curses, napishite programmu parallel'nogo prosmotra 2-h fajlov v 2-h neperekryvayushchihsya oknah. e) to zhe v perekryvayushchihsya oknah. 8.15. Napishite funkcii vklyucheniya i vyklyucheniya rezhima eho-otobrazheniya nabiraemyh na klaviature simvolov (ECHO). 8.16. To zhe pro "rezhim nemedlennogo vvoda" (CBREAK). V obychnom rezhime stroka, nab- rannaya na klaviature, snachala popadaet v nekotoryj bufer v drajvere terminala|-. ____________________ |- Takie bufera nosyat nazvanie "character lists" - clist. Sushchestvuyut "syroj" (raw) clist, v kotoryj popadayut VSE simvoly, vvodimye s klaviatury; i "kanonicheskij" clist, v kotorom hranitsya otredaktirovannaya stroka - obrabotany zaboj, otmena stroki. Sami special'nye simvoly (redaktirovaniya i generacii signalov) v kanonicheskuyu ochered' ne popadayut (v rezhime ICANON). A. Bogatyrev, 1992-95 - 370 - Si v UNIX "Syraya" "Kanonicheskaya" klaviatura--->Ochered'Vvoda--*-->Ochered'Vvoda-->read | fajl-ustrojstvo drajver terminala V eho /dev/tty?? | ekran<---Ochered'Vyvoda---<--*--<-----------<---write |tot bufer ispol'zuetsya dlya predchteniya - vy mozhete nabirat' tekst na klaviature eshche do togo, kak programma zaprosit ego read-om: etot nabrannyj tekst sohranitsya v bufere i pri postuplenii zaprosa budet vydan iz bufera. Takzhe, v kanonicheskom rezhime ICANON, bufer vvoda ispol'zuetsya dlya redaktirovaniya vvedennoj stroki: zaboj otmenyaet posled- nij nabrannyj simvol, CTRL/U otmenyaet vsyu nabrannuyu stroku; a takzhe on ispol'zuetsya dlya vypolneniya nekotoryh preobrazovanij simvolov na vvode i vyvode|=. Vvedennaya stroka popadaet v programmu (kotoraya zaprosila dannye s klaviatury pri pomoshchi read, gets, putchar) tol'ko posle togo, kak vy nazhmete knopku <ENTER> s kodom '\n'. Do etogo vvodimye simvoly nakaplivayutsya v bufere, no v programmu ne peredayutsya - programma tem vremenem "spit" v vyzove read. Kak tol'ko budet nazhat simvol '\n', on sam postupit v bufer, a programma budet razbuzhena i smozhet nakonec prochest' iz bufera vvoda nabrannyj tekst. Dlya menyu, redaktorov i drugih "ekrannyh" programm etot rezhim neudoben: prishlos' by slishkom chasto nazhimat' <ENTER>. V rezhime CBREAK nazhataya bukva nemedlenno popadaet v vashu programmu (bez ozhidaniya nazhatiya '\n'). V dannom sluchae bufer drajvera ispol'- zuetsya tol'ko dlya predchteniya, no ne dlya redaktirovaniya vvodimogo teksta. Redaktiro- vanie vozlagaetsya na vas - predusmotrite ego v svoej programme sami! Zamet'te, chto kod knopki <ENTER> ("konec vvoda") - '\n' - ne tol'ko "protalki- vaet" tekst v programmu, no i sam popadaet v bufer drajvera, a zatem v vashu prog- rammu. Ne zabyvajte ego kak-to obrabatyvat'. V MS DOS funkciya chteniya knopki v rezhime ~ECHO+CBREAK nazyvaetsya getch(). V UNIX analogichno ej budet rabotat' obychnyj getchar(), esli pered ego ispol'zovaniem ustano- vit' nuzhnye rezhimy drajvera tty vyzovom ioctl. Po okonchanii programmy rezhim drajvera nado vosstanovit' (za vas eto nikto ne sdelaet). Takzhe sleduet vosstanavlivat' rezhim drajvera pri avarijnom zavershenii programmy (po lyubomu signalu|-|-). Ocheredi vvoda i vyvoda ispol'zuyutsya takzhe dlya sinhronizacii skorosti raboty programmy (skazhem, skorosti napolneniya bufera vyvoda simvolami, postupayushchimi iz prog- rammy cherez vyzovy write) i skorosti raboty ustrojstva (s kotoroj drajver vybiraet simvoly s drugogo konca ocheredi i vydaet ih na ekran); a takzhe dlya preobrazovanij simvolov na vvode i vyvode. Primer upravleniya vsemi rezhimami est' v prilozhenii. 8.17. Funkcional'nye klavishi bol'shinstva displeev posylayut v liniyu ne odin, a nes- kol'ko simvolov. Naprimer na terminalah, rabotayushchih v sisteme komand standarta ANSI, knopki so strelkami posylayut takie posledovatel'nosti: strelka vverh "\033[A" knopka Home "\033[H" strelka vniz "\033[B" knopka End "\033[F" strelka vpravo "\033[C" knopka PgUp "\033[I" strelka vlevo "\033[D" knopka PgDn "\033[G" (poskol'ku pervym simvolom upravlyayushchih posledovatel'nostej obychno yavlyaetsya simvol '\033' (escape), to ih nazyvayut eshche escape-posledovatel'nostyami). Nam zhe v programme udobno vosprinimat' takuyu posledovatel'nost' kak edinstvennyj kod s celym znacheniem bol'shim 0xFF. Sklejka posledovatel'nostej simvolov, postupayushchih ot funkcional'nyh klavish, v takoj vnutrennij kod - takzhe zadacha ekrannoj biblioteki (uchet sistemy komand displeya na vvode). ____________________ |= Rezhimy preobrazovanij, simvoly redaktirovaniya, i.t.p. upravlyayutsya sistemnym vy- zovom ioctl. Bol'shoj primer na etu temu est' v prilozhenii. |-|- Esli vasha programma zavershilas' avarijno i mody terminala ostalis' v "strannom" sostoyanii, to privesti terminal v chuvstvo mozhno komandoj stty sane A. Bogatyrev, 1992-95 - 371 - Si v UNIX Samym interesnym yavlyaetsya to, chto odinochnyj simvol '\033' tozhe mozhet prijti s klaviatury - ego posylaet klavisha Esc. Poetomu esli my stroim raspoznavatel' klavish, kotoryj pri postuplenii koda 033 nachinaet ozhidat' sostavnuyu posledovatel'nost' - my dolzhny vystavlyat' tajmaut, naprimer alarm(1); i esli po ego istechenii bol'she nikakih simvolov ne postupilo - vydavat' kod 033 kak kod klavishi Esc. Napishite raspoznavatel' kodov, postupayushchih s klaviatury. Kody obychnyh bukv vyda- vat' kak est' (0..0377), kody funkcional'nyh klavish vydavat' kak chisla >= 0400. Uchtite, chto raznye tipy displeev posylayut raznye posledovatel'nosti ot odnih i teh zhe funkcional'nyh klavish: predusmotrite nastrojku na sistemu komand DANNOGO displeya pri pomoshchi biblioteki termcap. Raspoznavatel' udobno stroit' pri pomoshchi sravneniya postu- payushchih simvolov s vetvyami dereva (spuskayas' po nuzhnoj vetvi dereva pri postuplenii ocherednogo simvola. Kak tol'ko dostigli lista dereva - vozvrashchaem kod, pripisannyj etomu listu): ---> '\033' ---> '[' ---> 'A' --> vydat' 0400 | \--> 'B' --> 0401 | \--> 'C' --> 0402 | \--> 'D' --> 0403 \--> 'X' -----------> 0404 ... Nuzhnoe derevo strojte pri nastrojke na sistemu komand dannogo displeya. Biblioteka curses uzhe imeet takoj vstroennyj raspoznavatel'. CHtoby sostavnye posledovatel'nosti skleivalis' v special'nye kody, vy dolzhny ustanovit' rezhim keypad: int c; WINDOW *window; ... keypad(window, TRUE); ... c = wgetch(window); Bez etogo wgetch() schityvaet vse simvoly poodinochke. Simvolicheskie nazvaniya kodov dlya funkcional'nyh klavish perechisleny v <curses.h> i imeyut vid KEY_LEFT, KEY_RIGHT i.t.p. Esli vy rabotaete s edinstvennym oknom razmerom s ves' ekran, to v kachestve parametra window vy dolzhny ispol'zovat' standartnoe okno stdscr (eto imya predoprede- leno v include-fajle curses.h). # ======================================== Makefile dlya getch getch: getch.o cc getch.o -o getch -ltermlib getch.o: getch.c getch.h cc -g -DUSG -c getch.c A. Bogatyrev, 1992-95 - 372 - Si v UNIX /* Razbor sostavnyh posledovatel'nostej klavish s klaviatury. */ /* ================================================== getch.h */ #define FALSE 0 #define TRUE 1 #define BOOLEAN unsigned char #define INPUT_CHANNEL 0 #define OUTPUT_CHANNEL 1 #define KEY_DOWN 0400 #define KEY_UP 0401 #define KEY_LEFT 0402 #define KEY_RIGHT 0403 #define KEY_PGDN 0404 #define KEY_PGUP 0405 #define KEY_HOME 0406 #define KEY_END 0407 #define KEY_BACKSPACE 0410 #define KEY_BACKTAB 0411 #define KEY_DC 0412 #define KEY_IC 0413 #define KEY_DL 0414 #define KEY_IL 0415 #define KEY_F(n) (0416+n) #define ESC ' 33' extern char *tgetstr(); void _put(char c); void _puts(char *s); void keyboard_access_denied(void); char *strdup(const char *s); void keyinit(void); int getc_raw(void); void keyreset(void); int getch(void); int lgetch(BOOLEAN); int ggetch(BOOLEAN); int kgetch(void); void _sigalrm(int n); void init_keytry(void); void add_to_try(char *str, short code); void keypad_on(void); void keypad_off(void); int dotest(void); void tinit(void); void main(void); A. Bogatyrev, 1992-95 - 373 - Si v UNIX /* ===================================================== getch.c * The source version of getch.c file was * written by Pavel Curtis. * */ #include <stdio.h> #include <signal.h> #include <setjmp.h> #include <termios.h> #include <ctype.h> #include <string.h> #include <locale.h> #include "getch.h" #define keypad_local S[0] #define keypad_xmit S[1] #define key_backspace S[2] #define key_backtab S[3] #define key_left S[4] #define key_right S[5] #define key_up S[6] #define key_down S[7] #define key_ic S[8] #define key_dc S[9] #define key_il S[10] #define key_dl S[11] #define key_f1 S[12] #define key_f2 S[13] #define key_f3 S[14] #define key_f4 S[15] #define key_f5 S[16] #define key_f6 S[17] #define key_f7 S[18] #define key_f8 S[19] #define key_f9 S[20] #define key_f10 S[21] /* f0 */ #define key_f11 S[22] /* f11 */ #define key_f12 S[23] /* f12 */ #define key_home S[24] #define key_end S[25] #define key_npage S[26] #define key_ppage S[27] #define TOTAL 28 A. Bogatyrev, 1992-95 - 374 - Si v UNIX /* descriptors for keys */ char *KEYS[TOTAL+1] = { "ke", "ks", "kb", "kB", "kl", "kr", "ku", "kd", "kI", "kD", "kA", "kL", "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "f0", "f.", "f-", "kh", "kH", "kN", "kP", NULL }, *S[TOTAL]; void _put (char c) { write( INPUT_CHANNEL, &c, 1 ); } void _puts(char *s) { tputs ( s, 1, _put ); } static int _backcnt = 0; static char _backbuf[30]; static struct try { struct try *child; struct try *sibling; char ch; short value; } *_keytry; BOOLEAN keypadok = FALSE; struct termios new_modes; void keyboard_access_denied(){ printf( "Klaviatura nedostupna.\n" ); exit(1); } char *strdup(const char *s) { return strcpy((char *) malloc(strlen(s)+1), s); } A. Bogatyrev, 1992-95 - 375 - Si v UNIX /* Inicializaciya tablicy strok */ void keyinit(){ char *key, nkey[80], *p; register i; keyreset(); for( i=0; i < TOTAL; i++ ){ p = nkey; printf("tgetstr(%s)...", KEYS[i]); key = tgetstr(KEYS[i], &p); if(S[i]) free(S[i]); if(key == NULL){ S[i] = NULL; /* No such key */ printf("klavisha ne opredelena.\n"); }else{ /* Decrypted string */ S[i] = strdup(key); printf("schitano.\n"); } } init_keytry(); if( tcgetattr(INPUT_CHANNEL, &new_modes) < 0 ){ keyboard_access_denied(); } /* input flags */ /* otmenit' preobrazovanie koda '\r' v '\n' na vvode */ new_modes.c_iflag &= ~ICRNL; if ((new_modes.c_cflag & CSIZE) == CS8) /* 8-bitnyj kod */ new_modes.c_iflag &= ~ISTRIP; /* otmenit' & 0177 na vvode */ /* output flags */ /* otmenit' TAB3 - zamenu tabulyacij '\t' na probely */ /* otmenit' ONLCR - zamenu '\n' na paru '\r\n' na vyvode */ new_modes.c_oflag &= ~(TAB3 | ONLCR); /* local flags */ /* vyklyuchit' rezhim ICANON, vklyuchit' CBREAK */ /* vyklyuchit' ehootobrazhenie nabiraemyh simvolov */ new_modes.c_lflag &= ~(ICANON | ECHO); /* control chars */ /* pri vvode s klavish zhdat' ne bolee ... */ new_modes.c_cc[VMIN] = 1; /* 1 simvola i */ new_modes.c_cc[VTIME] = 0; /* 0 sekund */ /* |to sootvetstvuet rezhimu CBREAK */ /* Simvoly, nazhatie kotoryh zastavlyaet drajver terminala poslat' signal * libo otredaktirovat' nabrannuyu stroku. Znachenie 0 oznachaet, * chto sootvetstvuyushchego simvola ne budet */ new_modes.c_cc[VINTR] = '\0'; /* simvol, generyashchij SIGINT */ new_modes.c_cc[VQUIT] = '\0'; /* simvol, generyashchij SIGQUIT */ new_modes.c_cc[VERASE] = '\0'; /* zaboj (otmena poslednego simvola)*/ new_modes.c_cc[VKILL] = '\0'; /* simvol otmeny stroki */ } A. Bogatyrev, 1992-95 - 376 - Si v UNIX /* CHtenie odnogo simvola neposredstvenno s klaviatury */ int getc_raw(){ int n; char c; n = read(INPUT_CHANNEL, &c, 1); if (n <= 0) return EOF; return (c & 0xFF); } static BOOLEAN _getback = FALSE; static char _backchar = '\0'; /* CHtenie simvola - libo iz bufera (esli ne pust), libo s klaviatury */ #define nextc() (_backcnt > 0 ? _backbuf[--_backcnt] : \ _getback ? _getback = FALSE, _backchar : \ getc_raw()) #define putback(ch) _backbuf[_backcnt++] = ch void keyreset(){ _backcnt = 0; _backchar = '\0'; _getback = FALSE; } /* Funkciya chteniya sostavnogo simvola */ int getch(){ int c = lgetch(TRUE); keypad_off(); return c; } /* VNIMANIE! Esli v processe budet poluchen signal, v to vremya kak process nahoditsya vnutri vyzova getch(), to sistemnyj vyzov read() vernet 0 i errno == EINTR. V etom sluchae getch() vernet '\0'. CHtoby izbezhat' etoj situacii ispol'zuetsya funkciya lgetch() */ int lgetch(BOOLEAN kpad) { int c; while((c = ggetch(kpad)) <= 0); return c; } int ggetch(BOOLEAN kpad) { int kgetch(); if( kpad ) keypad_on(); else keypad_off(); return keypadok ? kgetch() : nextc(); } A. Bogatyrev, 1992-95 - 377 - Si v UNIX /* ** int kgetch() ** ** Get an input character, but take care of keypad sequences, returning ** an appropriate code when one matches the input. After each character ** is received, set a one-second alarm call. If no more of the sequence ** is received by the time the alarm goes off, pass through the sequence ** gotten so far. ** */ #define CRNL(c) (((c) == '\r') ? '\n' : (c)) /* bor'ba s russkoj klaviaturoj */ #if !defined(XENIX) || defined(VENIX) # define unify(c) ( (c)&(( (c)&0100 ) ? ~0240 : 0377 )) #else # define unify(c) (c) #endif A. Bogatyrev, 1992-95 - 378 - Si v UNIX /* ==================================================================== */ #if !defined(XENIX) && !defined(USG) && !defined(M_UNIX) && !defined(unix) /* Dlya semejstva BSD */ static BOOLEAN alarmed; jmp_buf jbuf; int kgetch() { register struct try *ptr; int ch; char buffer[10]; /* Assume no sequences longer than 10 */ register char *bufp = buffer; void (*oldsig)(); void _sigalrm(); ptr = _keytry; oldsig = signal(SIGALRM, _sigalrm); alarmed = FALSE; if( setjmp( jbuf )) /* chtob svalit'sya syuda s read-a */ ch = EOF; do { if( alarmed ) break; ch = nextc(); if (ch != EOF) /* getc() returns EOF on error, too */ *(bufp++) = ch; if (alarmed) break; while (ptr != (struct try *)NULL && (ch == EOF || unify(CRNL(ptr->ch)) != unify(CRNL(ch)) )) ptr = ptr->sibling; if (ptr != (struct try *)NULL) { if (ptr->value != 0) { alarm(0); signal(SIGALRM, oldsig); return(ptr->value); } else { ptr = ptr->child; alarm(1); } } } while (ptr != (struct try *)NULL); alarm(0); signal(SIGALRM, oldsig); if (ch == EOF && bufp == buffer) return ch; A. Bogatyrev, 1992-95 - 379 - Si v UNIX while (--bufp > buffer) putback(*bufp); return(*bufp & 0377); } void _sigalrm(int n) { alarmed = TRUE; longjmp(jbuf, 1); } A. Bogatyrev, 1992-95 - 380 - Si v UNIX /* ==================================================================== */ #else /* XENIX or USG */ /* Dlya semejstva SYSTEM V */ static BOOLEAN alarmed; int kgetch() { register struct try *ptr; int ch; char buffer[10]; /* Assume no sequences longer than 10 */ register char *bufp = buffer; void (*oldsig)(); void _sigalrm(); ptr = _keytry; oldsig = signal(SIGALRM, _sigalrm); alarmed = FALSE; do { ch = nextc(); if (ch != EOF) /* getc() returns EOF on error, too */ *(bufp++) = ch; if (alarmed) break; while (ptr != (struct try *)NULL && (ch == EOF || unify(CRNL(ptr->ch)) != unify(CRNL(ch)) )) ptr = ptr->sibling; if (ptr != (struct try *)NULL) { if (ptr->value != 0) { alarm(0); signal(SIGALRM, oldsig); return(ptr->value); } else { ptr = ptr->child; alarm(1); } } } while (ptr != (struct try *)NULL); alarm(0); signal(SIGALRM, oldsig); if (ch == EOF && bufp == buffer) return ch; while (--bufp > buffer) putback(*bufp); return(*bufp & 0377); } A. Bogatyrev, 1992-95 - 381 - Si v UNIX void _sigalrm(int n) { alarmed = TRUE; signal(SIGALRM, _sigalrm); } #endif /*XENIX*/ /* ==================================================================== */ /* ** init_keytry() ** Postroenie dereva razbora posledovatel'nostej simvolov. ** */ void init_keytry() { _keytry = (struct try *) NULL; add_to_try(key_backspace, KEY_BACKSPACE); add_to_try("\b", KEY_BACKSPACE); add_to_try("\177", KEY_BACKSPACE); add_to_try(key_backtab, KEY_BACKTAB); add_to_try(key_dc, KEY_DC); add_to_try(key_dl, KEY_DL); add_to_try(key_down, KEY_DOWN); add_to_try(key_f1, KEY_F(1)); add_to_try(key_f2, KEY_F(2)); add_to_try(key_f3, KEY_F(3)); add_to_try(key_f4, KEY_F(4)); add_to_try(key_f5, KEY_F(5)); add_to_try(key_f6, KEY_F(6)); add_to_try(key_f7, KEY_F(7)); add_to_try(key_f8, KEY_F(8)); add_to_try(key_f9, KEY_F(9)); add_to_try(key_f10, KEY_F(10)); add_to_try(key_f11, KEY_F(11)); add_to_try(key_f12, KEY_F(12)); add_to_try(key_home, KEY_HOME); add_to_try(key_ic, KEY_IC); add_to_try(key_il, KEY_IL); add_to_try(key_left, KEY_LEFT); add_to_try(key_npage, KEY_PGDN); add_to_try(key_ppage, KEY_PGUP); add_to_try(key_right, KEY_RIGHT); add_to_try(key_up, KEY_UP); add_to_try(key_end, KEY_END); } A. Bogatyrev, 1992-95 - 382 - Si v UNIX void add_to_try(char *str, short code) { static BOOLEAN out_of_memory = FALSE; struct try *ptr, *savedptr; if (str == NULL || out_of_memory) return; if (_keytry != (struct try *) NULL) { ptr = _keytry; for (;;) { while (ptr->ch != *str && ptr->sibling != (struct try *)NULL) ptr = ptr->sibling; if (ptr->ch == *str) { if (*(++str)) { if (ptr->child != (struct try *)NULL) ptr = ptr->child; else break; } else { ptr->value = code; return; } } else { if ((ptr->sibling = (struct try *) malloc(sizeof *ptr)) == (struct try *)NULL) { out_of_memory = TRUE; return; } savedptr = ptr = ptr->sibling; ptr->child = ptr->sibling = (struct try *)NULL; ptr->ch = *str++; ptr->value = 0; break; } } /* end for (;;) */ } else /* _keytry == NULL :: First sequence to be added */ { savedptr = ptr = _keytry = (struct try *) malloc(sizeof *ptr); if (ptr == (struct try *) NULL) { out_of_memory = TRUE; return; } ptr->child = ptr->sibling = (struct try *) NULL; A. Bogatyrev, 1992-95 - 383 - Si v UNIX ptr->ch = *(str++); ptr->value = 0; } /* at this point, we are adding to the try. ptr->child == NULL */ while (*str) { ptr->child = (struct try *) malloc(sizeof *ptr); ptr = ptr->child; if (ptr == (struct try *)NULL) { out_of_memory = TRUE; ptr = savedptr; while (ptr != (struct try *)NULL) { savedptr = ptr->child; free(ptr); ptr = savedptr; } return; } ptr->child = ptr->sibling = (struct try *)NULL; ptr->ch = *(str++); ptr->value = 0; } ptr->value = code; return; } /* Vklyuchenie al'ternativnogo rezhima klaviatury */ void keypad_on(){ if( keypadok ) return; keypadok = TRUE; if( keypad_xmit ) _puts( keypad_xmit ); } /* Vklyuchenie standartnogo rezhima klaviatury */ void keypad_off(){ if( !keypadok ) return; keypadok = FALSE; if( keypad_local ) _puts( keypad_local ); } A. Bogatyrev, 1992-95 - 384 - Si v UNIX /* Testovaya funkciya */ int dotest() { struct termios saved_modes; int c; char *s; char keyname[20]; if( tcgetattr(INPUT_CHANNEL, &saved_modes) < 0 ){ err: keyboard_access_denied(); } if( tcsetattr(INPUT_CHANNEL, TCSADRAIN, &new_modes) < 0 ) goto err; keyreset(); for(;;){ c = getch(); switch(c){ case KEY_DOWN: s = "K_DOWN" ; break; case KEY_UP: s = "K_UP" ; break; case KEY_LEFT: s = "K_LEFT" ; break; case KEY_RIGHT: s = "K_RIGHT" ; break; case KEY_PGDN: s = "K_PGDN" ; break; case KEY_PGUP: s = "K_PGUP" ; break; case KEY_HOME: s = "K_HOME" ; break; case KEY_END: s = "K_END" ; break; case KEY_BACKSPACE: s = "K_BS" ; break; case '\t': s = "K_TAB" ; break; case KEY_BACKTAB: s = "K_BTAB" ; break; case KEY_DC: s = "K_DEL" ; break; case KEY_IC: s = "K_INS" ; break; case KEY_DL: s = "K_DL" ; break; case KEY_IL: s = "K_IL" ; break; case KEY_F(1): s = "K_F1" ; break; case KEY_F(2): s = "K_F2" ; break; case KEY_F(3): s = "K_F3" ; break; case KEY_F(4): s = "K_F4" ; break; case KEY_F(5): s = "K_F5" ; break; case KEY_F(6): s = "K_F6" ; break; case KEY_F(7): s = "K_F7" ; break; case KEY_F(8): s = "K_F8" ; break; case KEY_F(9): s = "K_F9" ; break; case KEY_F(10): s = "K_F10" ; break; case KEY_F(11): s = "K_F11" ; break; case KEY_F(12): s = "K_F12" ; break; case ESC: s = "ESC" ; break; case EOF: s = "K_EOF" ; break; case '\r': s = "K_RETURN"; break; case '\n': s = "K_ENTER" ; break; default: s = keyname; if( c >= 0400 ){ sprintf(keyname, "K_F%d", c - KEY_F(0)); } else if( iscntrl(c)){ sprintf(keyname, "CTRL(%c)", c + 'A' - 1); } else { sprintf(keyname, "%c", c ); A. Bogatyrev, 1992-95 - 385 - Si v UNIX } } printf("Klavisha: %s\n\r", s); if(c == ESC) break; } tcsetattr(INPUT_CHANNEL, TCSADRAIN, &saved_modes); } /* Funkciya nastrojki na sistemu komand displeya */ void tinit (void) { /* static */ char Tbuf[2048]; /* Tbuf dolzhen sohranyat'sya vse vremya, poka mogut vyzyvat'sya funkcii tgetstr(). * Dlya etogo on libo dolzhen byt' static, libo vyzov funkcii keyinit() * dolzhen nahodit'sya vnutri tinit(), chto i sdelano. */ char *tname; extern char *getenv(); if((tname = getenv("TERM")) == NULL){ printf("TERM ne opredeleno: neizvestnyj tip terminala.\n"); exit(2); } printf("Terminal: %s\n", tname); /* Prochest' opisanie terminala v Tbuf */ switch (tgetent(Tbuf, tname)) { case -1: printf ("Net fajla TERMCAP (/etc/termcap).\n"); exit (1); case 0: printf ("Terminal '%s' ne opisan.\n", tname); exit (2); case 1: break; /* OK */ } if(strlen(Tbuf) >= 1024) printf("Opisanie terminala slishkom dlinnoe - vozmozhny poteri v konce opisaniya\n"); keyinit(); /* inicializirovat' stroki, poka Tbuf[] dostupen */ } void main(void){ setlocale(LC_ALL, ""); tinit(); /* keyinit(); */ dotest(); exit(0); } Po povodu etogo algoritma nado skazat' eshche paru slov. Ego modifikaciya mozhet s uspehom primenyat'sya dlya poiska slov v tablice (komand, klyuchej v baze dannyh, itp.): spisok slov prevrashchaetsya v derevo. V takom poiskovom algoritme ne trebuyutsya tajmauty, neob- hodimye pri vvode s klaviatury, poskol'ku est' yavnye terminatory strok - simvoly '\0', kotoryh net pri vvode s klaviatury. V chem effektivnost' takogo algoritma? Sravnim posledovatel'nyj perebor pri pomoshchi strcmp i poisk v dereve bukv: A. Bogatyrev, 1992-95 - 386 - Si v UNIX "zzzzzzzzzza" "zzzzzzzzzzb" "zzzzzzzzzzbx" "zzzzzzzzzzc" "zzzzzzzzzzcx" Dlya linejnogo perebora (dazhe v otsortirovannom massive) poisk stroki zzzzzzzzzzcx potrebuet zzzzzzzzzza | 11 sravnenij, otkaz zzzzzzzzzzb | 11 sravnenij, otkaz zzzzzzzzzzbx | 12 sravnenij, otkaz zzzzzzzzzzc | 11 sravnenij, otkaz zzzzzzzzzzcx V 12 sravnenij, uspeh Vsego: 57 shagov. Dlya poiska v dereve: __z__z__z__z__z__z__z__z__z__z__a__\0 |_b__\0 | |_x__\0 | |_c__\0 |_x__\0 potrebuetsya prohod vpravo (vniz) na 10 shagov, potom vybor sredi 'a','b','c', potom - vybor sredi '\0' i 'x'. Vsego: 15 shagov. Za schet togo, chto obshchij "koren'" prohoditsya rovno odin raz, a ne kazhdyj raz zanovo. No eto i trebuet predvaritel'noj podgotovki dannyh: prevrashcheniya strok v derevo! 8.18. Napishite funkciyu dlya "ekrannogo" redaktirovaniya vvodimoj stroki v rezhime CBREAK. Napishite analogichnuyu funkciyu na curses-e. V curses-noj versii nado umet' otrabatyvat': zaboj (udalenie simvola pered kursorom), otmenu vsej stroki, smeshchenie vlevo/vpravo po stroke, udalenie simvola nad kursorom, vstavku probela nad kursorom, zamenu simvola, vstavku simvola, pererisovku ekrana. Uchtite, chto parallel'no s izme- neniem kartinki v okne, vy dolzhny vnosit' izmeneniya v nekotoryj massiv (stroku), kotoraya i budet soderzhat' rezul'tat. |ta stroka dolzhna byt' argumentom funkcii redak- tirovaniya. Zaboj mozhno uproshchenno emulirovat' kak addstr( "\b \b" ); ili addch( '\b' ); delch(); Nedostatkom etih sposobov yavlyaetsya nekorrektnoe povedenie v nachale stroki (pri x==0). Isprav'te eto! 8.19. Na curses-e napishite funkciyu redaktirovaniya teksta v okne. Funkciya dolzhna vozvrashchat' massiv strok s obrezannymi koncevymi probelami. Variant: vozvrashchat' odnu stroku, v kotoroj stroki okna razdelyayutsya simvolami '\n'. 8.20. Napishite funkciyu, risuyushchuyu pryamuyu liniyu iz tochki (x1,y1) v (x2,y2). Ukazanie: ispol'zujte algoritm Brezenhema (minimal'nogo otkloneniya). Otvet: pust' funkciya putpixel(x,y,color) risuet tochku v koordinatah (x,y) cvetom color. void line(int x1, int y1, int x2, int y2, int color){ int dx, dy, i1, i2, i, kx, ky; register int d; /* "otklonenie" */ register int x, y; short /* boolean */ l; A. Bogatyrev, 1992-95 - 387 - Si v UNIX dy = y2 - y1; dx = x2 - x1; if( !dx && !dy ){ putpixel(x1,y1, color); return; } kx = 1; /* shag po x */ ky = 1; /* shag po y */ /* Vybor taktovoj osi */ if( dx < 0 ){ dx = -dx; kx = -1; } /* Y */ else if( dx == 0 ) kx = 0; /* X */ if( dy < 0 ){ dy = -dy; ky = -1; } if( dx < dy ){ l = 0; d = dx; dx = dy; dy = d; } else l = 1; i1 = dy + dy; d = i1 - dx; i2 = d - dx; x = x1; y = y1; for( i=0; i < dx; i++ ){ putpixel( x, y, color ); if( l ) x += kx; /* shag po takt. osi */ else y += ky; if( d < 0 ) /* gorizontal'nyj shag */ d += i1; else{ /* diagonal'nyj shag */ d += i2; if( l ) y += ky; /* prirost vysoty */ else x += kx; } } putpixel(x, y, color); /* poslednyaya tochka */ } 8.21. Sostav'te programmu, kotoraya stroit grafik funkcii sin(x) na otrezke ot 0 do 2*pi. Uchtite takie veshchi: sosednie tochki grafika sleduet soedinyat' otrezkom pryamoj, chtoby grafik vyglyadel nepreryvnym; ne zabyvajte privodit' double k int, t.k. koordi- naty pikselov|- - celye chisla. 8.22. Napishite funkciyu, kotoraya zapolnyaet v massive bajt count bit podryad, nachinaya s x-ogo bita ot levogo kraya massiva: bajt 0 | bajt 1 7 6 5 4 3 2 1 0 | 7 6 5 4 3 2 1 0 : bity v bajte 0 1 2 3 4 5 6 7 | 8 9 10 11 12 13 14 15 : x ========================== x=2, count=11 Takoj algoritm ispol'zuetsya v rastrovoj mashinnoj grafike dlya risovaniya gorizontal'nyh pryamyh linij (togda massiv - eto videopamyat' komp'yutera, kazhdyj bit sootvetstvuet pikselu na ekrane). Otvet (prichem my zapolnyaem bity ne prosto edinicami, a "uzorom" pattern): void horizLine(char *addr,int x,int count,char pattern){ static char masks[8] = { 0xFF, 0x7F, 0x3F, 0x1F, 0x0F, 0x07, 0x03, 0x01 }; /* indeks v etom massive raven chislu 0-bitov sleva */ ____________________ |- Piksel (pixel, pel) - picture element, v mashinnoj grafike - tochka rastra na ek- rane. A. Bogatyrev, 1992-95 - 388 - Si v UNIX register i; char mask; short lbits, rbits; /* chislo bitov sleva i sprava */ short onebyte; /* edinstvennyj bajt ? */ addr += x/8; /* v bajte 8 bit */ mask = masks[ lbits = x & 7 ]; /* x % 8 */ if( count >= (rbits = 8 - lbits)){ count -= rbits; onebyte = 0; }else{ mask &= ~masks[ lbits = (x+count) & 7 ]; onebyte = 1; } /* Pervyj bajt */ *addr = (*addr & ~mask) | (pattern & mask); addr++; /* Dlya pattern==0xFF mozhno prosto * *addr++ |= mask; * poskol'ku (a &~m)|(0xFF & m) = (a &~m) | m = * (a|m) & (~m|m) = (a|m) & 0xFF = a | m * Pochemu zdes' nel'zya napisat' *addr++ = (*addr...) ? * Potomu, chto ++ mozhet byt' sdelan DO vychisleniya * pravoj chasti prisvaivaniya! */ if(onebyte) return; /* Srednie bajty */ for(i = count/8; i > 0; --i) *addr++ = pattern; /* mask==0xFF */ /* Poslednij bajt */ if((lbits = count & 7) == 0) return; /* poslednij bajt byl polnym */ mask = ~masks[lbits]; *addr = (*addr & ~mask) | (pattern & mask); } Zametim, chto dlya bystrodejstviya podobnye algoritmy obychno pishutsya na assemblere. 8.23. Napishite pri pomoshchi curses-a "elektronnye chasy", otobrazhayushchie tekushchee vremya bol'shimi ciframi (naprimer, razmerom 8x8 obychnyh simvolov) kazhdye 5 sekund. Ispol'- zujte alarm(), pause(). 8.24. Sostav'te programmu, realizuyushchuyu prostoj dialogovyj interfejs, osnovannyj na menyu. Menyu hranyatsya v tekstovyh fajlah vida: A. Bogatyrev, 1992-95 - 389 - Si v UNIX fajl menu2_12 ----------------------------------------------- ZAGOLOVOK_MENYU +komanda_vypolnyaemaya_pri_vhode_v_menyu -komanda_vypolnyaemaya_pri_vyhode_iz_menyu al'ternativa_1 komanda1_1 komanda1_2 al'ternativa_2 komanda2_1 komanda2_2 #kommentarij komanda2_3 al'ternativa_3 >menu2_2 #eto perehod v drugoe menyu al'ternativa_4 >>menu3_7 #hranimoe v fajle menu3_7 ... ... ----------------------------------------------- Programma dolzhna obespechivat': vozvrat k predydushchemu menyu po klavishe Esc (dlya etogo sleduet hranit' "istoriyu" vyzovov menyu drug iz druga, naprimer v vide "polnogo imeni menyu": .rootmenu.menu1_2.menu2_4.menu3_1 gde menuI_J - imena fajlov s menyu), obespechit' vyhod iz programmy po klavisham 'q' i ESC, vydachu podskazki po F1, vydachu polnogo imeni menyu po F2. Vyzov menyu pri pomoshchi > oznachaet zameshchenie tekushchego menyu novym, chto sootvetstvuet zamene poslednej kompo- nenty v polnom imeni menyu. Vyzov >> oznachaet vyzov menyu kak funkcii, t.e. posle vybora v novom menyu i vypolneniya nuzhnyh dejstvij avtomaticheski dolzhno byt' vydano to menyu, iz kotorogo proizoshel vyzov (takoj vyzov sootvetstvuet udlineniyu polnogo imeni, a vozvrat iz vyzova - otsecheniyu poslednej komponenty). |tot vyzov mozhet byt' pokazan na ekrane kak poyavlenie novogo "vyskakivayushchego" okna poverh okna s predydushchim menyu (okno voznikaet chut' sdvinutym - skazhem, na y=1 i x=-2), a vozvrat - kak ischeznovenie etogo okna. Zagolovok menyu dolzhen vysvechivat'sya v verhnej stroke menyu: |------------------- |--ZAGOLOVOK_MENYU---- | | al'ternativa_1 | | | al'ternativa_2 | | | *al'ternativa_3 | | | al'ternativa_4 |-- --------------------- Snachala realizujte versiyu, v kotoroj kazhdoj "al'ternative" sootvetstvuet edinstvennaya stroka "komanda". Komandy sleduet zapuskat' pri pomoshchi standartnoj funkcii system(komanda). Uslozhnite funkciyu vybora v menyu tak, chtoby al'ternativy mozhno bylo vybirat' po pervoj bukve pri pomoshchi nazhatiya knopki s etoj bukvoj (v lyubom registre): Compile Edit Run program 8.25. Napishite na curses-e funkciyu, realizuyushchuyu vybor v menyu - pryamougol'noj tab- lice: A. Bogatyrev, 1992-95 - 390 - Si v UNIX slovo1 slovo4 slovo7 slovo2 *slovo5 slovo8 slovo3 slovo6 Stroki - elementy menyu - peredayutsya v funkciyu vybora v vide massiva strok. CHislo elementov menyu zaranee neizvestno i dolzhno podschityvat'sya vnutri funkcii. Uchtite, chto vse stroki mogut ne pomestit'sya v tablice, poetomu nado predusmotret' "prokruchi- vanie" strok cherez tablicu pri dostizhenii kraya menyu (t.e. tablica sluzhit kak by "okoshkom" cherez kotoroe my obozrevaem tablicu bol'shego razmera, vozmozhno peremeshchaya okno nad nej). Predusmotrite takzhe sluchaj, kogda tablica okazyvaetsya zapolnennoj ne polnost'yu (kak na risunke). 8.26. Ispol'zuya biblioteku curses, napishite programmu, realizuyushchuyu kletochnyj avtomat Konveya "ZHizn'". Pravila: est' pryamougol'noe pole (voobshche govorya beskonechnoe, no pri- nyato v konechnoj modeli zamykat' kraya v kol'co), v kotorom zhivut "kletki" nekotorogo organizma. Kazhdaya imeet 8 sosednih polej. Sleduyushchee pokolenie "kletok" obrazuetsya po takim pravilam: - esli "kletka" imeet 2 ili 3 sosedej - ona vyzhivaet. - esli "kletka" imeet men'she 2 ili bol'she 3 sosedej - ona pogibaet. - v pustom pole, imeyushchem rovno 3h zhivyh sosedej, rozhdaetsya novaya "kletka". Predusmotrite: redaktirovanie polya, sluchajnoe zapolnenie polya, ostanov pri smerti vseh "kletok", ostanov pri stabilizacii kolonii. 8.27. Pri pomoshchi curses-a napishite ekrannyj redaktor kodov dostupa k fajlu (v forme rwxrwxrwx). Rasshir'te programmu, pozvolyaya redaktirovat' kody dostupa u gruppy faj- lov, izobrazhaya imena fajlov i kody dostupa v vide tablicy: NAZVANIE KODY DOSTUPA fajl1 rwxrw-r-- fajl2 rw-r-xr-x fajl3 rwxrwxr-- Imena fajlov zadavajte kak argumenty dlya main(). Ukazanie: ispol'zujte dlya polucheniya tekushchih kodov dostupa sistemnyj vyzov stat(), a dlya ih izmeneniya - sistemnyj vyzov chmod(). A. Bogatyrev, 1992-95 - 391 - Si v UNIX Operacii, raspolozhennye vyshe, imeyut bol'shij prioritet. Operatory Associativnost' -------------------------------------------------- 1. () [] -> :: . Left to right 2. ! ~ + - ++ -- & * (typecast) sizeof new delete Right to left 3. .* ->* Left to right 4. * / % Left to right 5. + - Left to right 6. << >> Left to right 7. < <= > >= Left to right 8. == != Left to right 9. & Left to right 10. ^ Left to right 11. | Left to right 12. && Left to right 13. || Left to right 14. ?: (uslovnoe vyrazhenie) Right to left 15. = *= /= %= += -= &= ^= |= <<= >>= Right to left 16. , Left to right Zdes' "*" i "&" v stroke 2 - eto adresnye operacii; v stroke 2 "+" i "-" - unarnye; "&" v stroke 9 - eto pobitnoe "i"; "(typecast)" - privedenie tipa; "new" i "delete" - operatory upravleniya pamyat'yu v C++. Associativnost' Left to right (sleva napravo) oznachaet gruppirovku operatorov takim obrazom: A1 @ A2 @ A3 eto ((A1 @ A2) @ A3) Associativnost' Rigth to left (sprava nalevo) eto A1 @ A2 @ A3 eto (A1 @ (A2 @ A3)) 9.2.1. V vyrazheniyah. 1. Esli operand imeet tip ne int i ne double, to snachala privoditsya: signed char --> int rasshireniem znakovogo bita (7) unsigned char --> int dopolneniem nulyami sleva short --> int rasshireniem znakovogo bita (15) unsigned short --> unsigned int dopolneniem nulyami sleva enum --> int poryadkovyj nomer v perechislimom tipe float --> double drobnaya chast' dopolnyaetsya nulyami 2. Esli lyuboj operand imeet tip double, to i drugoj operand privoditsya k tipu dou- ble. Rezul'tat: tipa double. Zapishem vse dal'nejshie preobrazovaniya v vide shemy: A. Bogatyrev, 1992-95 - 392 - Si v UNIX esli est' to drugoj rezul'tat operand tipa privoditsya k tipu imeet tip if(double) -->double double else if(unsigned long) -->unsigned long unsigned long else if(long) -->long long else if(unsigned int) -->unsigned int unsigned int else oba operanda imeyut tip int int Pri vyzove funkcij ih argumenty - tozhe vyrazheniya, poetomu v nih privodyatsya char,short k int i float k double. |to govorit o tom, chto argumenty (formal'nye parametry) funk- cij mozhno vsegda ob®yavlyat' kak int i double vmesto char,short i float sootvetstvenno. Zato specifikator unsigned yavlyaetsya sushchestvennym. 9.2.2. V prisvaivaniyah. op = expr; Tip vyrazheniya expr privoditsya k tipu levoj chasti - op. Pri etom vozmozhny privedeniya bolee "dlinnogo" tipa k bolee "korotkomu" pri pomoshchi usecheniya, vrode: int --> char obrubaetsya starshij bajt. long --> int obrubaetsya starshee slovo. float --> int otbros drobnoj chasti double --> int i obrubanie mantissy, esli ne lezet. double --> float okruglenie drobnoj chasti. Vot eshche nekotorye privedeniya tipov: signed --> unsigned virtual'no (prosto znakovyj bit unsigned --> signed schitaetsya znachashchim ili naoborot). unsigned int --> long dobavlenie nulej sleva. int --> long rasshirenie znakovogo bita. float --> int preobrazovanie vnutrennego int --> float predstavleniya: mashinno zavisimo. Nekotorye preobrazovaniya mogut idti v neskol'ko stadij, naprimer: char --> long eto char --> int --> long char --> unsigned long eto char --> int --> unsigned long %d %o %X pobitno -------------------------------- 0 0 0x0 0000 1 1 0x1 0001 2 2 0x2 0010 3 3 0x3 0011 4 4 0x4 0100 5 5 0x5 0101 6 6 0x6 0110 7 7 0x7 0111 A. Bogatyrev, 1992-95 - 393 - Si v UNIX -------------------------------- 8 010 0x8 1000 9 011 0x9 1001 10 012 0xA 1010 11 013 0xB 1011 12 014 0xC 1100 13 015 0xD 1101 14 016 0xE 1110 15 017 0xF 1111 16 020 0x10 10000 n 2**n | n 2**n --------------|--------------- 0 1 | 8 256 1 2 | 9 512 2 4 | 10 1024 3 8 | 11 2048 4 16 | 12 4096 5 32 | 13 8192 6 64 | 14 16384 7 128 | 15 32768 | 16 65536 Celye chisla v bol'shinstve sovremennyh komp'yuterov predstavleny v vide dvoichnogo koda. Pust' mashinnoe slovo sostoit iz 16 bit. Bity numeruyutsya sprava nalevo nachinaya s 0. Oboznachim uslovno bit nomer i cherez b[i]. Znacheniem ego mozhet byt' libo 0, libo 1. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | 0| 0| 0| 0| 1| 0| 1| 1| 0| 1| 1| 0| 1| 1| 0| 0| +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ Togda unsigned chislo, zapisannoe v slove, ravno d = 2**15 * b[15] + 2**14 * b[14] + ... 2**1 * b[1] + b[0]; (2**n - eto 2 v stepeni n). Takoe razlozhenie chisla d edinstvenno. Pri slozhenii dvuh chisel bity skladyvayutsya po pravilam: 0 + 0 = 0 0 + 1 = 1 1 + 0 = 1 1 + 1 = 0 i perenos 1 v razryad sleva CHisla so znakom interpretiruyutsya chut' inache. Bit b[15] schitaetsya znakovym: 0 - chislo polozhitel'no ili ravno nulyu, 1 - otricatel'no. Otricatel'nye chisla hranyatsya v vide dopolnitel'nogo koda: -a = ~a + 1 Naprimer: A. Bogatyrev, 1992-95 - 394 - Si v UNIX 2 = 0000000000000010 ~2 = 1111111111111101 ~2+1 = 1111111111111110 = -2 -1 = 1111111111111111 -2 = 1111111111111110 -3 = 1111111111111101 -4 = 1111111111111100 -5 = 1111111111111011 Takoe predstavlenie vybrano ishodya iz pravila a + (-a) = 0 znak| 2 = 0|000000000000010 slozhim ih -2 = 1|111111111111110 ---------|--------------- summa: 10|000000000000000 Kak vidim, proizoshel perenos 1 v bit nomer 16. No slovo soderzhit lish' bity 0..15 i bit b[16] prosto ignoriruetsya. Poluchaetsya, chto summa ravna 0000000000000000 = 0 chto i trebovalos'. V dvoichnom kode vychitanie realizuetsya po sheme a - b = a + (-b) = a + (~b + 1) Vos'merichnye chisla sootvetstvuyut razbieniyu dvoichnogo chisla na gruppy po 3 bita i zapisi kazhdoj gruppy v vide sootvetstvuyushchej vos'merichnoj cifry (smotri tablicu vyshe). SHestnadcaterichnye chisla sootvetstvuyut razbieniyu na gruppy po 4 bita (nibble): x = 0010011111011001 chislo: 0010 0111 1101 1001 16-richnoe: 0x 2 7 D 9 = 0x27D9 chislo: 0 010 011 111 011 001 8-richnoe: 0 0 2 3 7 3 1 = 023731 V dannom prilozhenii privoditsya neskol'ko soderzhatel'nyh i dostatochno bol'shih primerov, kotorye illyustriruyut kak sam yazyk Si, tak i nekotorye vozmozhnosti sistemy UNIX, a takzhe nekotorye programmistskie priemy resheniya zadach pri pomoshchi Si. Mnogie iz etih primerov soderzhat v kachestve svoih chastej otvety na nekotorye iz zadach. Nekotorye primery pozaimstvovany iz drugih knig, no dopolneny i ispravleny. Vse pri- mery provereny v dejstvii. Smysl nekotoryh funkcij v primerah mozhet okazat'sya vam neizvesten; odnako v silu togo, chto dannaya kniga ne yavlyaetsya uchebnikom, my otsylaem vas za podrobnostyami k "Operativnomu rukovodstvu" (man) po operacionnoj sisteme UNIX i k dokumentacii po sisteme. I v zaklyuchenie - neskol'ko slov o putyah razvitiya yazyka "C". CHistyj yazyk "C" uzhe otstal ot sovremennyh tehnologij programmirovaniya. Takie metody kak moduli (yazyki "Modula-2", "Ada", "CLU"), rodovye pakety ("Ada", "CLU"), ob®ektno-orientirovannoe programmirovanie ("Smalltalk", "CLU") trebuyut novyh sredstv. Poetomu v nastoyashchee vremya "C" postepenno vytesnyaetsya bolee moshchnym i intellektual'nym yazykom "C++" |-, obladayushchim sredstvami dlya ob®ektno-orientirovannogo programmirovaniya i rodovyh ____________________ |- C++ kak i C razrabotan v AT&T; proiznositsya "Si plas-plas" A. Bogatyrev, 1992-95 - 395 - Si v UNIX klassov. Sushchestvuyut takzhe rasshireniya standartnogo "C" ob®ektno-orientirovannymi voz- mozhnostyami ("Objective-C"). Bol'shoj prostor predostavlyaet takzhe sborka programm iz chastej, napisannyh na raznyh yazykah programmirovaniya (naprimer, "C", "Pascal", "Pro- log"). ____________________ |=|=|= Avtor blagodarit avtorov programm i knig po Si i UNIX, po kotorym nekogda uchilsya ya sam; kolleg iz IPK Minavtoproma/Demosa; programmistov iz setej Usenet i Rel- com, davshih materialy dlya zadach i rassuzhdenij; slushatelej kursov po Si za mnogochis- lennyj material dlya knigi. A.Bogatyrev. Uchen'yu ne odin my posvyatili god, Potom drugih uchit' prishel i nam chered. Kakie zh vyvody iz etoj vsej nauki? Iz praha my prishli, nas veter uneset. Omar Hajyam Oglavlenie. 0. Naputstvie v kachestve vstupleniya. .......................................... 1 1. Prostye programmy i algoritmy. Syurprizy, sovety. ........................... 3 2. Massivy, stroki, ukazateli. ................................................ 81 3. Mobil'nost' i mashinnaya zavisimost' programm. Problemy s russkimi bukvami. ........................................................................... 122 4. Rabota s fajlami. .......................................................... 137 5. Struktury dannyh. .......................................................... 172 6. Sistemnye vyzovy i vzaimodejstvie s UNIX. .................................. 186 6.1. Fajly i katalogi. ........................................................ 189 6.2. Vremya v UNIX. ............................................................ 198 6.3. Svobodnoe mesto na diske. ................................................ 207 6.4. Signaly. ................................................................. 212 6.5. ZHizn' processov. ......................................................... 219 6.6. Truby i FIFO-fajly. ...................................................... 230 6.7. Nelokal'nyj perehod. ..................................................... 233 6.8. Hozyain fajla, processa, i proverka privelegij. ........................... 235 6.9. Blokirovka dostupa k fajlam. ............................................. 240 6.10. Fajly ustrojstv. ........................................................ 244 6.11. Mul'tipleksirovanie vvoda-vyvoda ......................................... 259 6.12. Prostoj interpretator komand. ........................................... 271 7. Tekstovaya obrabotka. ....................................................... 283 8. |krannye biblioteki i rabota s videopamyat'yu. ............................... 348 9. Prilozheniya. ................................................................ 391 9.1. Tablica prioritetov operacij yazyka C++ .................................... 391 9.2. Pravila preobrazovanij tipov. ............................................ 391 9.3. Tablica shestnadcaterichnyh chisel (HEX). ................................... 392 9.4. Tablica stepenej dvojki. ................................................. 393 9.5. Dvoichnyj kod: vnutrennee predstavlenie celyh chisel. ...................... 393 10. Primery. .................................................................. 394 Primer 1. Razmen monet. ...................................................... Primer 2. Podschet bukv v fajle. .............................................. Primer 3. Centrirovanie strok. ............................................... Primer 4. Razmetka teksta dlya nroff. ......................................... Primer 5. Invertirovanie poryadka slov v strokah. ............................. Primer 6. Puzyr'kovaya sortirovka. ............................................ Primer 7. Hesh-tablica. ....................................................... Primer 8. Prostaya baza dannyh. ............................................... Primer 9. Vstavka/udalenie strok v fajl. ..................................... Primer 10. Bezopasnyj free, pozvolyayushchij obrashcheniya k avtomaticheskim peremen- nym. ..................................................................... Primer 11. Poimka oshibok pri rabote s dinamicheskoj pamyat'yu. .................. Primer 12. Kopirovanie/peremeshchenie fajla. .................................... Primer 13. Obhod poddereva katalogov v MS DOS pri pomoshchi chdir. .............. Primer 14. Rabota s signalami. ............................................... Primer 15. Upravlenie skorost'yu obmena cherez liniyu. .......................... Primer 16. Prosmotr fajlov v oknah. .......................................... Primer 17. Rabota s ierarhiej okon v curses. CHast' proekta uxcom. ............ Primer 18. Podderzhka soderzhimogo kataloga. CHast' proekta uxcom. .............. Primer 19. Rolliruemoe menyu. CHast' proekta uxcom. ............................ Primer 20. Vybor v stroke-menyu. CHast' proekta uxcom. ......................... Primer 21. Redaktor stroki. CHast' proekta uxcom. ............................. Primer 22. Vybor v pryamougol'noj tablice. CHast' proekta uxcom. ............... Primer 23. UNIX commander - prostoj vizual'nyj SHell. Golovnoj modul' proekta uxcom. ................................................................... Primer 24. Obshchenie dvuh processov cherez "trubu". ............................. Primer 25. Obshchenie processov cherez FIFO-fajl. ................................ Primer 26. Obshchenie processov cherez obshchuyu pamyat' i semafory. .................. Primer 27. Protokolirovanie raboty programmy pri pomoshchi psevdoterminala i processov. ............................................................... Primer 28. Ocenka fragmentirovannosti fajlovoj sistemy. ...................... Primer 29. Vosstanovlenie udalennogo fajla v BSD-2.9. ........................ Primer 30. Kopirovanie fajlov iz MS DOS v UNIX. .............................. Primer 31. Programma, pechatayushchaya svoj sobstvennyj tekst. ..................... Primer 32. Formatirovanie teksta Si-programmy. ............................... 1.11. Treugol'nik iz zvezdochek. ............................................... 6 1.34. Prostye chisla. .......................................................... 10 1.36. Celochislennyj kvadratnyj koren'. ........................................ 12 1.39. Vychislenie integrala po Simpsonu. ....................................... 14 1.49. Sortirovka SHella. ....................................................... 20 1.50. Bystraya sortirovka. ..................................................... 21 1.67. Funkciya chteniya stroki. .................................................. 28 1.88. Perestanovki elementov. ................................................. 38 1.117. Shema Gornera. ......................................................... 58 1.137. Sistemnaya funkciya qsort - format vyzova. ............................... 67 1.146. Process kompilyacii programm. ........................................... 76 2.58. Funkciya bcopy. .......................................................... 108 2.59. Funkciya strdup. ......................................................... 111 2.61. Uproshchennyj analog funkcii printf. ....................................... 112 3.9. _ctype[] .................................................................. 126 3.12. Programma transliteracii: tr. ........................................... 129 3.16. Funkciya zapisi trassirovki (otladochnyh vydach) v fajl. ................... 132 3.18. Uslovnaya kompilyaciya: #ifdef .............................................. 132 4.39. Bystryj dostup k strokam fajla. ......................................... 161 4.45. |mulyaciya osnov biblioteki STDIO, po motivam 4.2 BSD. .................... 165 5.12. Otsortirovannyj spisok slov. ............................................ 180 5.16. Struktury s polyami peremennogo razmera. ................................. 183 5.17. Spisok so "stareniem". .................................................. 184 6.1.1. Opredelenie tipa fajla. ................................................ 189 6.1.3. Vydacha neotsortirovannogo soderzhimogo kataloga (ls). ................... 191 6.1.5. Rekursivnyj obhod katalogov i podkatalogov. ............................ 192 6.2.9. Funkciya zaderzhki v mikrosekundah. ...................................... 201 6.4.3. Funkciya sleep. ......................................................... 217 6.10.1. Opredelenie tekushchego kataloga: funkciya getwd. ......................... 252 6.10.2. Kanonizaciya polnogo imeni fajla. ...................................... 257 6.11.1. Mul'tipleksirovanie vvoda iz neskol'kih fajlov. ....................... 259 6.11.2. Programma script. ..................................................... 261 7.12. Programma uniq. ......................................................... 285 7.14. Rasshirenie tabulyacij v probely, funkciya untab. .......................... 285 7.15. Funkciya tabify. ......................................................... 285 7.25. Poisk metodom polovinnogo deleniya. ...................................... 288 7.31. Programma pechati v dve polosy. .......................................... 292 7.33. Invertirovanie poryadka strok v fajle. ................................... 296 7.34. Perenos nerazbivaemyh blokov teksta. .................................... 298 7.36. Dvoichnaya sortirovka strok pri pomoshchi dereva. ............................ 300 7.41. Funkciya match. .......................................................... 309 7.43. Funkciya kontekstnoj zameny po regulyarnomu vyrazheniyu. .................... 313 7.44. Algoritm bystrogo poiska podstroki v stroke. ............................ 316 7.52. Korrekciya pravopisaniya. ................................................. 321 7.67. Kal'kulyator-1. .......................................................... 330 7.68. Kal'kulyator-2. .......................................................... 336 8.1. Osypayushchiesya bukvy. ....................................................... 350 8.13. Ispol'zovanie biblioteki termcap. ....................................... 359 8.17. Razbor ESC-posledovatel'nostej s klaviatury. ............................ 371 1) B.Kernigan, D.Ritchi, A.F'yuer. YAzyk programmirovaniya Si. Zadachi po yazyku Si. - M.: Finansy i statistika, 1985. 2) M.Uejt, S.Prata, D.Martin. YAzyk Si. Rukovodstvo dlya nachinayushchih. - M.: Mir, 1988. 3) M.Bolski. YAzyk programmirovaniya Si. Spravochnik. - M.: Radio i svyaz', 1988. 4) L.Henkok, M.Kriger. Vvedenie v programmirovanie na yazyke Si. - M.: Radio i svyaz', 1986. 5) M.Dansmur, G.Dejvis. OS UNIX i programmirovanie na yazyke Si. - M.: Radio i svyaz', 1989. 6) R.Berri, B.Mikinz. YAzyk Si. Vvedenie dlya programmistov. - M.: Finansy i sta- tistika, 1988. 7) M.Belyakov, A.Liverovskij, V.Semik, V.SHyaudkulis. Instrumental'naya mobil'naya ope- racionnaya sistema INMOS. - M.: Finansy i statistika, 1985. 8) K.Kristian. Vvedenie v operacionnuyu sistemu UNIX. - M.: Finansy i statistika, 1985. 9) R.Got'e. Rukovodstvo po operacionnoj sisteme UNIX. - M.: Finansy i statistika, 1986. 10) M.Banahan, |.Ratter. Vvedenie v operacionnuyu sistemu UNIX. - M.: Radio i svyaz', 1986. 11) S.Baurn. Operacionnaya sistema UNIX. - M.: Mir, 1986. 12) P.Braun. Vvedenie v operacionnuyu sistemu UNIX. - M.: Mir, 1987. 13) M.Bach. The design of the UNIX operating system. - Prentice Hall, Englewood Cliffs, N.J., 1986. 14) S.Dewhurst, K.Stark. Programming in C++. - Prentice Hall, 1989. 15) M.Ellis, B.Stroustrup. The annotated C++ Reference Manual. - Addison-Wesley, 1990. /* Primer 1 */ /* Zadacha o razmene monety: * Poisk vseh vozmozhnyh koefficientov a0 .. an razlozheniya chisla S * v vide * S = a0 * c0 + a1 * c1 + ... + an * cn * gde vesa c0 .. cn zadany zaranee i uporyadocheny. * Vesa i koefficienty neotricatel'ny (ai >= 0, ci >= 0). */ #include <stdio.h> /* Dostoinstva razmennyh monet (vesa ci) */ int cost[] = { 1, 2, 3, 5, 10, 15, 20, 50, 100, 300, 500 /* kopeek */ }; #define N (sizeof cost / sizeof(int)) int count[ N ]; /* chislo monet dannogo tipa (koefficienty ai) */ long nvar; /* chislo variantov */ main( ac, av ) char *av[]; { int coin; if( ac == 1 ){ fprintf( stderr, "Ukazhite, kakuyu monetu razmenivat': %s chislo\n", av[0] ); exit(1); } coin = atoi( av[1] ); printf( " Tablica razmenov monety %d kop.\n", coin ); printf( " Kazhdyj stolbec soderzhit kolichestvo monet ukazannogo dostoinstva.\n" ); printf( "-------------------------------------------------------------------\n" ); printf( "| 5r. | 3r. | 1r. | 50k.| 20k.| 15k.| 10k.| 5k.| 3k.| 2k.| 1k.|\n" ); printf( "-------------------------------------------------------------------\n" ); change( N-1, coin ); printf( "-------------------------------------------------------------------\n" ); printf( "Vsego %ld variantov\n", nvar ); } /* rekursivnyj razmen */ change( maxcoin, sum ) int sum; /* moneta, kotoruyu menyaem */ int maxcoin; /* indeks po massivu cost[] monety maksimal'nogo * dostoinstva, dopustimoj v dannom razmene. */ { register i; if( sum == 0 ){ /* vsya summa razmenyana */ /* raspechatat' ocherednoj variant */ putchar( '|' ); for( i = N-1 ; i >= 0 ; i-- ) if( count[i] ) printf(" %3d |", count[ i ] ); else printf(" |" ); putchar( '\n' ); nvar++; return; } if( sum >= cost [ maxcoin ] ){ /* esli mozhno vydat' monetu dostoinstvom cost[maxcoin] , * to vydat' ee: */ count[ maxcoin ] ++; /* poschitali vydannuyu monetu */ /* razmenivaem ostatok summy : * Pervyj argument - mozhet byt' mozhno dat' eshche odnu takuyu monetu ? * Vtoroj argument - obshchaya summa ubavilas' na odnu monetu cost[maxcoin]. */ change( maxcoin, sum - cost[maxcoin] ); count[ maxcoin ] --; /* ... Teper' poprobuem inoj variant ... */ } /* poprobovat' razmen bolee melkimi monetami */ if( maxcoin ) change( maxcoin-1, sum ); } /* Primer 2 */ /* Podschet kolichestva vhozhdenij kazhdoj iz bukv alfavita v fajl. * Vydacha tablicy. * Podschet chastoty ispol'zovaniya bitov v bajtah fajla. */ #include <stdio.h> #include <ctype.h> long bcnt[8]; char masks[8] = { /* maski bitov */ 1, 2, 4, 8, 16, 32, 64, 128 }; long cnt[256]; /* schetchiki dlya kazhdoj iz 256 bukv */ /* raspechatka bukv v stile yazyka SI */ char *pr( c ){ static char buf[ 20 ]; switch( c ){ case '\n': return " \\n " ; case '\r': return " \\r " ; case '\t': return " \\t " ; case '\b': return " \\b " ; case '\f': return " \\f " ; case '\033': return " ESC" ; case '\0': return " \\0 " ; case 0177: return " ^? " ; } if( c < ' ' ){ sprintf( buf, " ^%c ", c + 'A' - 1 ); }else if( isspace(c)){ sprintf( buf, " '%c'", c ); }else if( ! isprint( c )) sprintf( buf, "\\%3o", c ); else sprintf( buf, " %c ", c ); return buf; } main( argc, argv ) char **argv; { FILE *fp; if( argc == 1 ) process( stdin ); else{ argv++; argc--; while( *argv ){ printf( "----- FILE %s -----\n", *argv ); if((fp = fopen( *argv, "r" )) == NULL ){ printf( "Can not open\n" ); }else{ process( fp ); fclose( fp ); } argv++; argc--; } } exit(0); } /* obrabotat' fajl s pointerom fp */ process( fp ) FILE *fp; { register i; int c; int n; /* zachistka schetchikov */ for( i=0; i < 256; i++ ) cnt[i] = 0L; for( i=0; i < 8 ; i++ ) bcnt[i] = 0; while( ( c=getc(fp)) != EOF ){ c &= 0377; /* podschitat' bukvu */ cnt[ c ] ++; /* podschet bitov */ for( i=0; i < 8; i++ ) if( c & masks[i] ) bcnt[ i ] ++; } /* vydacha rezul'tatov v COL kolonok */ #define COL 4 printf( "\tASCII map\n" ); for( n=i=0; i < 256; i++ ){ /* if( cnt[i] == 0l ) continue; */ printf( "%s %5ld |", pr(i), cnt[i] ); if( ++n == COL ){ n = 0; putchar('\n'); } /* ili if((i % COL) == (COL-1)) putchar('\n'); */ } printf( "\n\tBITS map\n" ); for( i=7; i >=0 ; i-- ) printf( "%6d ", i ); putchar( '\n' ); for( i=7; i >=0 ; i-- ) printf( "%6ld ", bcnt[i] ); putchar( '\n' ); putchar( '\n' ); } /* Primer 3 */ /* Centrirovanie strok teksta. Primer na rabotu s ukazatelyami. */ /* Vhodnye stroki ne dolzhny soderzhat' tabulyacij */ /* Vyzov: a.out < vhodnoj_fajl */ #include <stdio.h> extern char *gets(); #define WIDTH 60 /* shirina lista */ main(){ char rd[81]; register char *s; char *head, /* nachalo teksta */ *tail; /* konec teksta */ register int len, i; int shift; /* otstup */ /* CHitat' so standartnogo vvoda v rd po odnoj stroke, * poka fajl ne konchitsya. Pri vvode s klaviatury konec fajla * oboznachaetsya nazhatiem klavish CTRL+D */ while( gets( rd ) != NULL ){ if( !*rd ){ /* Stroka pusta */ putchar( '\n' ); continue; } /* propusk probelov v nachale stroki */ for( s = rd; *s == ' ' ; s++ ); if( ! *s ){ /* Stroka sostoit tol'ko iz probelov */ putchar( '\n' ); continue; } head = s; /* vstat' na konec stroki */ while( *s ) s++; /* iskat' poslednij neprobel */ s--; while( *s == ' ' && s != rd ) s--; tail = s; /* Dlina teksta */ len = (tail-head) + 1; /* raznost' ukazatelej - celoe */ shift = (WIDTH - len)/2; if(shift < 0 ){ fprintf(stderr, "Stroka dlinnee chem %d\n", WIDTH ); shift = 0; } /* Pechat' rezul'tata */ for( i=0; i < shift; i++ ) putchar( ' ' ); while( head <= tail ) putchar( *head++ ); putchar( '\n' ); } } /* Primer 4 */ /* Predvaritel'naya razmetka teksta dlya nroff */ #include <stdio.h> #include <stdlib.h> #include <ctype.h> #include <string.h> /* prototip strchr() */ #include <locale.h> FILE *fout = stdout; /* kanal vyvoda */ /* Sostoyaniya vyvoda */ #define SPACE 0 /* probely */ #define TEXT 1 /* tekst */ #define PUNCT 2 /* znaki prepinaniya */ #define UC(c) ((unsigned char)(c)) /* Vyvod stroki teksta iz bufera */ void putstr (FILE *fp, unsigned char *s) { /* Punct - znaki prepinaniya, trebuyushchie prikleivaniya k * koncu predydushchego slova. * PunctS - znaki, vsegda trebuyushchie posle sebya probela. * PunctN - znaki, kotorye mogut sledovat' za znakom * prepinaniya bez probela. */ static char Punct [] = ",:;!?.)" ; static char PunctS[] = ",:;" ; static char PunctN[] = " \t\"'" ; #define is(c, set) (strchr(set, UC(c)) != NULL) int c, state = TEXT, cprev = 'X'; while ((c = *s) != '\0') { /* Probely */ if(isspace(c)) state = SPACE; /* Znaki prepinaniya. Probely pered nimi ignoriruyutsya. */ else if(is(c, Punct)){ switch(state){ case SPACE: if(is(cprev, Punct ) && cprev==c && c != ')') putc(' ', fp); /* a prosto probely - ignorirovat' */ break; case PUNCT: if(is(cprev, PunctS)) putc(' ', fp); break; } putc(cprev = c, fp); /* vyvodim sam znak */ state = PUNCT; } else { /* Neskol'ko probelov svorachivaem v odin */ switch(state){ case SPACE: putc(' ', fp); break; case PUNCT: if(!is(c, PunctN)) putc(' ', fp); break; } putc(cprev = c, fp); /* sama bukva */ state = TEXT; if(c == '\\') putc('e', fp); } s++; } /* probely v konce stroki prosto ignoriruyutsya */ putc ('\n', fp); } /* Obrabotat' fajl s imenem name */ void proceed (char *name) { FILE *fp; static unsigned char inp[2048]; /* dostatochno bol'shoj bufer vvoda */ if (strcmp(name, "-") == 0 ) fp = stdin; else if ((fp = fopen (name, "r")) == NULL) { fprintf (stderr, "Cannot read %s\n", name); return; } while (fgets (inp, sizeof inp, fp) != NULL) { register unsigned char *s, *p; int len = strlen (inp); if (len && inp[len - 1] == '\n') inp[--len] = '\0'; if (!*inp) { /* .sp N - propusk N pustyh strok */ space: fprintf (fout, ".sp 1\n"); continue; } /* obrezat' koncevye probely */ for(p = NULL, s = inp; *s; ++s){ if (!isspace (*s)) p = s; } if(p) p[1] = '\0'; else goto space; /* p ukazyvaet na poslednij neprobel */ /* Udalit' perenosy slov v konce stroki: perenos - eto minus, prizhatyj k koncu slova */ if (*p == '-' && p != inp /* ne v nachale stroki */ && isalnum(UC(p[-1])) /* posle bukvy */ ){ int c; *p = '\0'; /* zateret' perenos */ /* CHitaem prodolzhenie slova iz nachala sleduyushchej stroki */ while (isspace (c = getc (fp))); ungetc (c, fp); while ((c = getc (fp)) != '\n' && !isspace (c)) *p++ = c; *p = '\0'; if (c != '\n' ){ /* prochli probel */ /* vychityvaem VSE probely */ while (isspace(c = getc (fp))); if(c != '\n') ungetc (c, fp); } } /* .pp - direktiva nachala abzaca. */ if (isspace (*inp)) { fprintf (fout, ".pp\n"); for (s = inp; isspace (*s); s++); putstr (fout, s); } else { if (*inp == '.' || *inp == '\'') fprintf (fout, "\\&"); putstr (fout, inp); } } if( fp != stdin ) fclose (fp); } int main (int argc, char *argv[]) { int i; setlocale(LC_ALL, ""); for (i = 1; i < argc; i++) proceed (argv[i]); return 0; /* exit code */ } /* Primer 5 */ /* Programma, raspechatyvayushchaya slova v strokah fajla v obratnom poryadke */ #include <stdio.h> #include <ctype.h> #include <string.h> #include <locale.h> #define MAXL 255 /* maks. dlina stroki */ /* Esli by my ne vklyuchili ctype.h, to my dolzhny byli by opredelit' * #define isspace(c) ((c) == ' ' || (c) == '\t' || (c) == '\f') */ main ( argc, argv ) char **argv;{ setlocale(LC_ALL, ""); if( argc == 1 ){ /* programma vyzvana bez argumentov */ munch( "" ); }else{ /* argumenty programmy - imena fajlov */ while( argv[ 1 ] ){ munch( argv[1] ); argv++; argc--; } } total(); exit(0); } /* obrabotat' fajl s imenem name */ munch( name ) char *name; { char l[MAXL]; /* bufer dlya ocherednoj stroki */ int len; /* dlina etoj stroki */ char *words[50]; /* tablica polej stroki */ char **s; /* sluzhebnaya */ int nwords; /* chislo slov v stroke */ FILE *fp; if( name == NULL || !*name ) fp = stdin; /* standartnyj vvod */ else if( (fp = fopen( name, "r" )) == NULL ){ fprintf( stderr, "Ne mogu otkryt' fajl %s\n", name ); return; } printf( "----------------------------%s----\n", name ); while( fgets( l, MAXL, fp ) != NULL ){ len = strlen( l ); if( len && l[len-1] == '\n' ) l[--len] = '\0' ; if( nwords = parse( l, words)){ /* raspechatka slov v obratnom poryadke */ for( --nwords; nwords >= 0; nwords-- ){ printf( "%s ", words[ nwords] ); add( words[ nwords ] ); } } putchar ('\n'); } if( fp != stdin ) fclose( fp ); } /* razobrat' stroku na slova */ parse( s, tabl ) register unsigned char *s; unsigned char *tabl[]; { char eol = 0; int nwords = 0; while ( !eol ){ /* propustit' probely i tabulyacii */ while(isspace(*s)) s++; if( !*s ) /* stroka konchilas' */ break; *tabl++ = s; nwords++; /* nachalo ocherednogo slova */ /* poka ne probel i ne konec stroki */ while( *s && !isspace(*s))s++; /* ukazatel' stoit na simvole, sleduyushchem za slovom */ if( ! *s ) eol ++; *s = '\0'; /* zakryli Slovo, nachinaem Delo */ s++; } *tabl = NULL; return nwords; } /* postroenie tablicy slov, vstrechayushchihsya v fajle */ #define MAXWORDS 1024 struct W{ int ctr; /* chislo vhozhdenij slova */ char *wrd; /* slovo */ }w [MAXWORDS]; /* tablica */ int busy = 0 ; /* zanyato v tablice */ extern char *malloc(); /* Dobavit' slovo v tablicu */ add( word ) char *word; { register i; static alert = 1; /* net li uzhe slova v tablice ? */ /* esli est' - prosto uvelichit' schetchik */ for( i = 0; i < busy ; i++ ){ if( !strcmp( word, w[i].wrd )){ w[i].ctr++; return; } } if( busy >= MAXWORDS ){ if( alert ){ fprintf( stderr, "Perepolnenie tablicy slov\7\n"); alert = 0; } return; } /* net, slova net. Zanosim: */ w[busy].wrd = malloc( strlen( word ) + 1 ); /* 1 bajt pod simvol \0 */ if( w[busy].wrd == NULL ){ fprintf( stderr, "Malo pamyati\n"); busy = MAXWORDS+1; /* yakoby perepolnenie */ return; } w[busy].ctr = 1; strcpy( w[busy].wrd, word ); busy++; } compare( a, b ) struct W *a, *b; { return strcoll( a-> wrd, b-> wrd ); /* strcoll sravnivaet slova v alfavitnom poryadke */ } /* vydacha vseh slov, vstrechennyh v tekste, i chisla ih vhozhdenij */ total(){ register i; /* sortiruem slova po alfavitu */ qsort( w, busy, sizeof(struct W), compare ); printf( "-----|-----------ITOG---------------\n"); for( i=0; i < busy; i++ ) printf( "%4d | %s\n", w[i].ctr, w[i].wrd ); } /* Primer 6 */ /* Sortirovka bukv v stroke metodom "puzyr'ka" (bubble sort) */ #define YES 1 #define NO 0 bsort(s) char *s; { register i; /* indeks sravnivaemoj bukvy */ register need = YES; /* nado li prodolzhat' sortirovku ? */ while( need ){ need = NO; /* ne nado */ for(i=0; s[i+1]; i++ ) /* uslovie cikla: my sravnivaem i-uyu i i+1-uyu bukvy, * poetomu i proveryaem nalichie i+1oj bukvy */ if( s[i] > s[i+1] ){ /* v nevernom poryadke */ swap( &s[i], &s[i+1] ); /* perestavit' */ need = YES; /* chto-to izmenilos': nado budet * povtorit' prosmotr massiva bukv */ } } } /* A vot variant sortirovki, napisannyj s ukazatelyami */ bpsort(s) char *s; { register char *p; register need = YES; while( need ){ need = NO; for( p = s; p[1] != '\0' ; p++ ) if( *p > *(p+1) ){ swap( p, p+1 ); need = YES; } } } /* obmen dvuh bukv, nahodyashchihsya po adresam s1 i s2 */ swap( s1, s2 ) register char *s1, *s2; { char tmp; /* temporary */ tmp = *s1; *s1 = *s2; *s2 = tmp; } char sample1[] = "Homo homini lupus est - ergo bibamus!"; char sample2[ sizeof sample1 ]; /* massiv takogo zhe razmera */ main(){ strcpy( sample2, sample1 ); /* skopirovat' */ bsort ( sample1 ); printf( "%s\n", sample1 ); bpsort( sample2 ); printf( "%s\n", sample2 ); } /* Primer 7 */ /* Rabota s hesh-tablicej. CHast' funkcij napisana tak, chtoby * byt' nezavisimoj ot tipov klyucha i znacheniya i legko * podvergat'sya modifikacii. */ #include <stdio.h> #include <string.h> /* prototype for strchr() */ extern void *malloc(unsigned size); /* tipy klyucha i znacheniya: v nashem sluchae eto stroki */ typedef unsigned char uchar; typedef uchar *VAL; typedef uchar *KEY; /* Dlya ispol'zovaniya sleduet realizovat' operacii int HASHFUNC(KEY); int EQKEY(KEY, KEY); void FREEVAL(VAL); void SETVAL(VAL, VAL); void FREEKEY(KEY); void SETKEY(KEY, KEY); */ #define HASHSIZE 21 /* razmer tablicy: ochen' horosho 2**n */ uchar *strudup(const uchar *s){ /* sozdanie kopii stroki v "kuche" */ uchar *p = (uchar *) malloc(strlen(s)+1); strcpy(p, s); return p; } /* odna iz vozmozhnyh hesh-funkcij */ unsigned int hash; /* poslednee vychislennoe znachenie hesh-funkcii */ int HASHFUNC(KEY key){ unsigned int i = 0; uchar *keysrc = key; while(*key){ i = (i << 1)|(i >> 15); /* ROL */ i ^= *key++; } hash = i % HASHSIZE; printf( "hash(%s)=%d\n", keysrc, hash); /* otladka */ return hash; } #define EQKEY(s1, s2) (strcmp(s1, s2) == 0) #define FREEKEY(s) free(s) #define FREEVAL(s) free(s) #define SETVAL(at,s) at = strudup(s) #define SETKEY(at,s) at = strudup(s) #define KEYFMT "%s" #define VALFMT "%s" /* ================== tipo-nezavisimaya chast' ================= */ struct cell { struct cell *next; /* ssylka na ocherednoj element */ KEY key; /* klyuch */ VAL val; /* znachenie */ } *hashtable[ HASHSIZE ]; /* hesh-tablica */ /* poluchenie znacheniya po klyuchu */ struct cell *get(KEY key){ struct cell *p; for(p = hashtable[HASHFUNC(key)]; p; p = p->next) if(EQKEY(p->key, key)) return p; return NULL; /* otsutstvuet */ } /* zanesti paru klyuch:znachenie v tablicu */ void set(KEY key, VAL val){ struct cell *p; /* proverit' - ne bylo li zvena s takim klyuchom */ if((p = get(key)) == NULL){ /* ne bylo */ if(!(p = (struct cell *) malloc(sizeof(*p)))) return; SETKEY(p->key, key); p->next = hashtable[hash]; /* hash vychisleno v get() */ hashtable[hash] = p; } else /* uzhe bylo: izmenit' znachenie */ FREEVAL(p->val); SETVAL(p->val, val); } /* udalenie po klyuchu */ int del(KEY key){ int indx = HASHFUNC(key); struct cell *p, *prev = NULL; if((p = hashtable[indx]) == NULL) return 0; for( ;p ;prev = p, p=p->next) if(EQKEY(p->key, key)){ FREEVAL(p->val); FREEKEY(p->key); if( p == hashtable[indx] ) /* golova spiska */ hashtable[indx] = p->next; else prev->next = p->next; free((void *) p ); return 1; /* udalen */ } return 0; /* ne bylo takogo */ } /* raspechatat' paru klyuch:znachenie */ void printcell(struct cell *ptr){ putchar('('); printf( KEYFMT, ptr->key ); putchar(','); printf( VALFMT, ptr->val ); putchar(')'); } /* raspechatka tablicy (dlya otladki) */ void printtable(){ register i; struct cell *p; printf("----TABLE CONTENTS----\n"); for(i=0; i < HASHSIZE; i++) if((p = hashtable[i]) != NULL){ printf( "%d: ", i); for(; p; p=p->next) printcell(p), putchar(' '); putchar('\n'); } } /* iterator */ struct celliter { int index; struct cell *ptr; }; /* vydat' ocherednoe znachenie */ struct cell *nextpair(struct celliter *ci){ struct cell *result; while((result = ci->ptr) == NULL){ if( ++(ci->index) >= HASHSIZE ) return NULL; /* bol'she net */ ci->ptr = hashtable[ci->index]; } ci->ptr = result->next; return result; } /* inicializaciya iteratora */ struct cell *resetiter(struct celliter *ci){ ci->index = (-1); ci->ptr = NULL; return nextpair(ci); /* pervoe znachenie */ } /* =========================================================== */ void main(){ /* tablica iz imen i razmerov fajlov tekushchego kataloga */ struct celliter ci; struct cell *cl; char key[40], value[40]; struct cell *val; extern FILE *popen(); FILE *fp; char *s ; /* popen() chitaet vyvod komandy, zadannoj v 1-om argumente */ fp = popen( "ls -s", "r" ); while( fscanf( fp, "%s%s", value, key) == 2 ) set(key, value); pclose(fp); /* popen() nado zakryvat' pclose(); */ for(;;){ printf( "-> " ); /* priglashenie */ if( !gets( key )) break; /* EOF */ if( *key == '-' ){ /* -KLYUCH :udalit' */ printf( del( key+1 ) ? "OK\n" : "net takogo\n"); continue; } if( !*key || !strcmp(key, "=")){ /* = :raspechatat' tablicu*/ printtable(); continue; } if(s = strchr(key, '=')){ /* KLYUCH=ZNACHENIE :dobavit' */ *s++ = '\0'; set(key, s); continue; } if((val = get( key )) == NULL) /* KLYUCH :najti znachenie */ printf( "net takogo klyucha\n"); else{ printf( "znachenie "); printf(VALFMT, val->val); putchar('\n'); } } /* raspechatka tablicy pri pomoshchi iteratora */ for( cl = resetiter(&ci) ; cl ; cl = nextpair(&ci)) printcell(cl), putchar('\n'); } /* Primer 8 */ /* Primer malen'koj bazy dannyh. * Dannye hranyatsya BEZ dublikatov. * Nado zametit', chto ispol'zuetsya plohoj (neeffektivnyj) * algoritm dostupa - linejnyj poisk. */ #include <stdio.h> /* Vse zapisi v baze imeyut fiksirovannyj razmer */ #define VLEN 20 #define KEY_FREE (-13) /* klyuch svobodnogo mesta. On vybran proizvol'no, no ne dolzhen vstrechat'sya v kachestve vhodnyh dannyh */ struct data{ short b_key; /* klyuch */ char b_val[VLEN]; /* stroka-znachenie */ }; char BASEF[] = ".base" ; /* imya fajla bazy */ FILE *fbase; /* pointer na bazu */ struct data tmp; /* vspomogatel'naya peremennaya */ void initBase (void){ /* fopen: r read (chtenie) * w write (zapis'), fajl peresozdaetsya. * (sozdaetsya, esli ne bylo, esli byl - opustoshaetsya). * r+ chtenie i zapis' (fajl uzhe sushchestvuet). * w+ chtenie i zapis' (sozdaetsya pustoj fajl). * a append (zapis' v konec fajla), sozdat' esli net: * imeetsya v vidu, chto KAZHDAYA operaciya zapisi snachala * stavit ukazatel' zapisi na konec fajla. * V MS DOS netekstovyj fajl NEOBHODIMO otkryvat' kak * rb wb rb+ wb+ ab+ inache nichego ne budet rabotat'. */ if(( fbase = fopen( BASEF, "r+" )) == NULL ){ if(( fbase = fopen( BASEF, "w+" )) == NULL ){ fprintf( stderr, "Ne mogu otkryt' bazu dannyh %s\n", BASEF ); exit(1); } fprintf( stderr, "Baza sozdana\n" ); } } void closeBase (void){ fclose( fbase ); } /* Uchtite, chto esli vy zapisyvaete v fajl struktury, to v fajle ne budet razdeleniya na stroki - fajl NETEKSTOVYJ! Poetomu i chitat' takoj fajl mozhno tol'ko strukturami: read(), fread() (no ne scanf-om i ne fgets-om) */ /* Poisk po klyuchu . Vydat' (-1), esli zapisi s dannym klyuchom net, inache - nomer slota, gde soderzhitsya zapis' s dannym klyuchom. */ int bget (int key) { int n; /* posledovatel'no prosmotret' ves' fajl */ rewind( fbase ); /* v nachalo fajla. Ravno fseek(fbase, 0L, 0); */ n = 0 ; /* int skol'ko_elementov_massiva_dejstvitel'no_schitano = * fread( adres_massiva_kuda_schityvat', * razmer_odnogo_elementa_massiva, * skol'ko_elementov_schityvat'_v_massiv, kanal ); * Zamet'te, chto kolichestvo dannyh zadaetsya NE v bajtah, * a v 'shtukah' */ while( fread( &tmp, sizeof( tmp ), 1, fbase ) == 1 ){ if( tmp.b_key == key ) return n; n++; } return (-1); /* ne najdeno */ } /* modificirovat' zapis' s indeksom ind */ void bmod ( int ind, int key, /* novyj klyuch */ char *val /* novoe znachenie */ ) { struct data new; fseek( fbase, (long) sizeof( struct data ) * ind, 0 ); new.b_key = key; strncpy( new.b_val, val, VLEN ); /* int skol'ko_elementov_massiva_dejstvitel'no_zapisano = * fwrite( adres_massiva_kotoryj_zapisyvat', * razmer_odnogo_elementa_massiva, * skol'ko_elementov_massiva_zapisyvat', kanal ); */ if( fwrite( &new, sizeof new , 1, fbase ) != 1 ) fprintf( stderr, "Oshibka zapisi.\n" ); } /* udalenie zapisi po klyuchu */ int bdel (int key){ int ind = bget( key ); if( ind == -1 ) return (-1); /* zapisi s takim klyuchom net */ bmod( ind, KEY_FREE, "" ); /* zapisat' priznak svobodnogo mesta */ return 0; } /* Sluzhebnaya procedura dopisi k koncu fajla */ void bappend (int key, char *val) { struct data new; /* vstat' na konec fajla */ fseek( fbase, 0L, 2 ); /* i zapisat' novuyu strukturu v konec */ new.b_key = key; strncpy( new.b_val, val, VLEN ); fwrite( &new, sizeof( struct data ) , 1, fbase ); } /* dobavlenie novoj zapisi. Esli zapis' s takim klyuchom uzhe est' - vydat' oshibku */ int bput (int key, char *val) { int i = bget( key ); if( i != -1 ) return (-1); /* zapis' uzhe est' */ /* najti svobodnoe mesto */ i = bget( KEY_FREE ); if( i == -1 ) { /* net svobodnyh mest */ bappend( key, val ); return 0; } /* inache svobodnoe mesto najdeno. * Zamenyaem dyrku na poleznuyu informaciyu */ bmod( i, key, val ); } /* raspechatat' vsyu bazu dannyh podryad */ void bprint (void){ int n; int here = 0; rewind( fbase ); n = 0; printf( "-nomer--klyuch-------znachenie-----------------\n" ); while( fread( &tmp, sizeof tmp, 1, fbase ) == 1 ){ if( tmp.b_key == KEY_FREE ){ n++; continue; } printf( "#%-2d| %6d\t| %s\n", n, tmp.b_key, tmp.b_val ); here ++; n++; } printf( "--------------------------------------------\n" ); printf( "Dlina bazy:%d Zanyato:%d\n\n", n, here ); } /* zamena polya val u zapisi s klyuchom key */ int bchange (int key, char *val) { int ind; ind = bget( key ); if( ind == -1 ){ /* zapis' s takim klyuchom ne sushchestvuet */ /* Dobavit' kak novuyu zapis' */ bput( key, val ); return 0; } bmod( ind, key, val ); return 1; } /* Analogichnaya funkciya, no ispol'zuyushchaya drugoj sposob. * Krome togo, esli takoj klyuch otsutstvuet - nichego ne delaetsya */ int bchg (int key, char *val) { struct data d; rewind( fbase ); /* v nachalo fajla */ while( fread( &d, sizeof d, 1, fbase ) == 1 ){ /* poisk klyucha */ if( d.b_key == key ){ /* vernut'sya nazad ot tekushchej pozicii */ fseek( fbase, - (long) sizeof d, 1 ); /* ne goditsya (long)-sizeof d !!! */ d.b_key = key; strncpy( d.b_val, val, VLEN ); fwrite( &d, sizeof d, 1, fbase ); /* mezhdu fread i fwrite dolzhen byt' * hot' odin fseek. (magicheskoe zaklinanie!) */ fseek( fbase, 0L, 1); /* nikuda ne sdvigat'sya */ return 0; /* sdelano */ } } return (-1); /* takogo klyucha ne bylo */ } /* Primer */ void main (void){ int i; initBase(); bprint(); bdel( 8 ); printf( "Sozdaem bazu dannyh\n" ); bput( 1, "stroka 1" ); bput( 2, "stroka 2" ); bput( 3, "stroka 3" ); bput( 4, "stroka 4" ); bprint(); printf( "Udalyaem zapisi s klyuchami 1 i 3\n" ); bdel( 1 ); bdel( 3 ); bprint(); printf( "Dobavlyaem zapisi 5, 6 i 7\n" ); bput( 5, "stroka 5" ); bput( 6, "stroka 6" ); bput( 7, "stroka 7" ); bprint(); printf( "Zamenyaem stroku v zapisi s klyuchom 2\n" ); bchange( 2, "novaya stroka 2" ); bprint(); printf( "Zamenyaem stroku v zapisi s klyuchom 4\n" ); bchg( 4, "novaya stroka 4" ); bprint(); printf( "Zamenyaem stroku v zapisi s klyuchom 6 i klyuch 6 na 8\n" ); i = bget( 6 ); printf( "Sejchas zapis' s klyuchom 6 soderzhit \"%s\"\n", tmp.b_val ); bmod( i, 8, "Novaya stroka 6/8" ); bprint(); closeBase(); } /* Primer 9 */ /* Vstavka/udalenie strok v fajl */ #include <stdio.h> #define INSERT_BEFORE 1 /* Vstavit' stroku pered ukazannoj */ #define INSERT_AFTER 2 /* Vstavit' stroku posle ukazannoj */ #define DELETE 3 /* Udalit' stroku */ #define REPLACE 4 /* Zamenit' stroku */ /* K kazhdoj stroke linenum dolzhno otnosit'sya ne bolee 1 operacii !!! */ struct lineop { char op; /* Operaciya */ long linenum; /* Nomer stroki v fajle (s 0) */ char *str; /* Stroka (ili NULL dlya DELETE) */ }; long lineno; /* nomer tekushchej stroki */ int fileChange (char *name, /* imya fajla */ struct lineop ops[], /* zadanie */ int nops /* chislo elementov v massive ops[] */ ){ FILE *fin, *fout; static char TMPNAME[] = " ? "; char buffer[BUFSIZ]; register i; struct lineop tmpop; if ((fin = fopen (name, "r")) == NULL) return (-1); if ((fout = fopen (TMPNAME, "w")) == NULL) { fclose (fin); return (-1); } lineno = 0L; while (fgets (buffer, BUFSIZ, fin) != NULL) { if( nops ) for (i = 0; i < nops; i++) if (lineno == ops[i].linenum) { switch (ops[i].op) { case DELETE: /* udalit' */ break; case INSERT_BEFORE: /* vstavit' pered */ fprintf (fout, "%s\n", ops[i].str); fputs (buffer, fout); break; case INSERT_AFTER: /* vstavit' posle */ fputs (buffer, fout); fprintf (fout, "%s\n", ops[i].str); break; case REPLACE: /* zamenit' */ fprintf (fout, "%s\n", ops[i].str); break; } /* perestavit' vypolnennuyu operaciyu v konec massiva i zabyt' */ tmpop = ops[nops-1]; ops[nops-1] = ops[i]; ops[i] = tmpop; nops--; goto next; } /* inache stroka ne chislitsya v massive ops[] : skopirovat' */ fputs (buffer, fout); next: lineno++; } fclose (fin); fclose (fout); rename (TMPNAME, name); return nops; /* chislo nesdelannyh operacij (0 - vse sdelano) */ } struct lineop myops[] = { { DELETE, 2L, NULL }, { INSERT_BEFORE, 0L, "inserted before 0" }, { INSERT_BEFORE, 10L, "inserted before 10" }, { INSERT_AFTER, 5L, "inserted after 5" }, { DELETE, 6L, NULL }, { INSERT_AFTER, 8L, "inserted after 8" }, { INSERT_AFTER, 12L, "inserted after 12" }, { REPLACE, 3L, "3 replaced" } }; void main( void ){ int n; n = fileChange( "aFile", myops, sizeof(myops)/sizeof(struct lineop)); printf( "Strok v fajle: %ld; ostalos' operacij: %d\n", lineno, n); } /* ishodnyj fajl poluchivshijsya fajl line 0 inserted before 0 line 1 line 0 line 2 line 1 line 3 3 replaced line 4 line 4 line 5 line 5 line 6 inserted after 5 line 7 line 7 line 8 line 8 line 9 inserted after 8 line 10 line 9 inserted before 10 line 10 Strok v fajle: 11; ostalos' operacij: 1 */ /* Primer 10 */ /* Problema: pozvolit' delat' vyzov free(ptr) * na dannye, ne otvodivshiesya malloc()-om. * Reshenie: vesti spisok vseh dannyh, * otvedennyh malloc()om. * Vozmozhno takzhe otslezhivanie diapazona adresov, * no poslednee yavlyaetsya mashinno-zavisimym resheniem. * * Pri bol'shom kolichestve fajlov eta programma - neplohoj test * proizvoditel'nosti mashiny! */ #include <stdio.h> #include <stdlib.h> #include <string.h> typedef struct _cell { void *addr; struct _cell *next; } Cell; typedef struct _entry { int length; int used; Cell *list; } Entry; /* Heshirovannaya tablica */ #define NENTRIES 64 Entry aTab[NENTRIES]; /* Hesh-funkciya ot adresa */ int aHash(void *addr){ unsigned long x = (unsigned long) addr; x >>= 3; /* delenie na 8, tak kak adresa iz malloc() obychno chetnye, poskol'ku vyrovneny na granicu double */ return(x % NENTRIES); /* Tut k mestu napomnit', chto vychislenie ostatka ot deleniya na stepen' dvojki * mozhno soptimizirovat': * x % (2**N) = x & 0b0001.....1 (N dvoichnyh edinic) * K primeru, x % 64 = x & 0x3F; (6-aya stepen' dvojki) */ } /* Vydelit' pamyat', zapisat' adres v tablicu */ void *aCalloc(int n, int m){ void *ptr = calloc(n, m); Entry *ep = &aTab[ aHash(ptr) ]; Cell *p; for(p=ep->list; p; p=p->next) if(p->addr == NULL){ /* Svobodnaya yachejka: pereispol'zovat' */ p->addr = ptr; ep->used++; return ptr; } /* Net svobodnyh, zavesti novuyu */ p = (Cell *) calloc(1, sizeof(Cell)); p->addr = ptr; p->next = ep->list; ep->list = p; ep->length++; ep->used++; return ptr; } /* Osvobodit' pamyat' */ int aFree(void *ptr){ Entry *ep = &aTab[ aHash(ptr) ]; Cell *p; for(p=ep->list; p; p=p->next) if(p->addr == ptr){ free(ptr); p->addr = NULL; /* YAchejka ne udalyaetsya, no metitsya kak svobodnaya */ ep->used--; return 1; } /* Net, takoj ukazatel' ne otvodilsya. * Ne delat' free() */ return 0; } /* Vydat' statistiku ob ispol'zovanii hesha */ void aStat(){ int i; int len_all; int used_all; for(i=len_all=used_all=0; i < NENTRIES; i++){ len_all += aTab[i].length; used_all += aTab[i].used; printf("%d/%d%s", aTab[i].used, aTab[i].length, i==NENTRIES-1 ? "\n":" "); } printf("%d/%d=%g%%\n", used_all, len_all, (double)used_all * 100 / len_all); } /* TEST =================================================================*/ Cell *text; /* Prochitat' fajl v pamyat' */ void fileIn(char *name){ char buf[10000]; FILE *fp; if((fp = fopen(name, "r")) == NULL){ printf("Cannot read %s\n", name); return; } while(fgets(buf, sizeof buf, fp) != NULL){ char *s; Cell *p; s = (char *) aCalloc(1, strlen(buf)+1); strcpy(s, buf); p = (Cell *) aCalloc(sizeof(Cell), 1); p->addr = s; p->next = text; text = p; } fclose(fp); } /* Unichtozhit' tekst v pamyati */ void killAll(){ Cell *ptr, *nxtp; ptr = text; while(ptr){ nxtp = ptr->next; if(!aFree(ptr->addr)) printf("No free(1)\n"); if(!aFree(ptr)) printf("No free(2)\n"); ptr = nxtp; } } /* Udalit' iz teksta stroki, nachinayushchiesya s opredelennoj bukvy */ void randomKill(int *deleted){ unsigned char c = rand() % 256; Cell *ptr, *prevp; unsigned char *s; retry: prevp = NULL; ptr = text; while(ptr){ s = (unsigned char *) ptr->addr; if(*s == c){ /* nashel */ if(!aFree(s)) printf("No free(3)\n"); /* isklyuchit' iz spiska */ if(prevp) prevp->next = ptr->next; else text = ptr->next; if(!aFree(ptr)) printf("No free(4)\n"); /* Zavedomo nepravil'nyj free if(!aFree(ptr+1)) printf("No free(5)\n"); */ (*deleted)++; goto retry; } prevp = ptr; ptr = ptr->next; } } int main(int ac, char *av[]){ int i, r, d; char buffer[4098]; srand(time(NULL)); for(i=1; i < ac; i++){ printf("File: %s\n", av[i]); fileIn(av[i]); aStat(); d = 0; for(r=0; r < 128; r++) randomKill(&d); printf("%d lines deleted\n", d); aStat(); } killAll(); aStat(); if(!aFree(buffer)) printf("buffer[] - ne dinamicheskaya peremennaya.\n"); return 0; } /* Primer 11 */ /* Paket dlya lovli naezdov oblastej vydelennoj pamyati * drug na druga, * a takzhe prosto povrezhdenij dinamicheski otvedennoj pamyati. */ #include <stdio.h> #include <stdlib.h> #include <fcntl.h> /* O_RDWR */ #include <sys/types.h> #include <ctype.h> #include <locale.h> #define CHECKALL /* ----------------- <--------- ptr | red_zone | golovnaya "pogranichnaya zona" ----------------- | byte[0] | | ... | | byte[size-1] | | placeholder | ----------------- vyrovneno na granicu RedZoneType | red_zone | hvostovaya "pogranichnaya zona" ----------------- Osnovnye idei sostoyat v sleduyushchem: 1) Pered i posle oblasti dannyh stroitsya zona, zapolnennaya zaranee izvestnym "uzorom". Esli ee soderzhimoe izmenilos', isporcheno - znachit my gde-to razrushili nashu pamyat'. 2) Vedetsya tablica vseh otvedennyh malloc()-om segmentov pamyati; dlya ekonomii mesta eta tablica vynesena v fajl (no zato eto ochen' medlenno). 3) My ne mozhem pol'zovat'sya bibliotekoj STDIO dlya obmenov s fajlom, potomu chto eta biblioteka sama ispol'zuet malloc() i bufera mogut byt' razrusheny. */ typedef char *RedZoneType; /* vyravnivanie na granicu ukazatelya */ /* Mozhno vyravnivat' na granicu double: typedef double RedZoneType; */ /* Segment, vydelyaemyj v operativnoj pamyati */ typedef struct _allocFrame { RedZoneType red_zone; /* golovnaya "pogranichnaya zona" */ RedZoneType stuff[1]; /* mesto dlya dannyh */ /* hvostovaya "pogranichnaya zona" bezymyanna */ } AllocFrame; const int RedZoneTypeSize = sizeof(RedZoneType); /* Zapis', pomeshchaemaya v tablicu vseh vydelennyh malloc()om * oblastej pamyati. */ typedef struct _memFileRecord { AllocFrame *ptr; /* adres */ size_t size, adjsize; /* razmer vydelennoj oblasti */ /* (0,0) - oznachaet "segment osvobozhden" */ int serial; } MemFileRecord; char red_table[] = { 0x01, 0x03, 0x02, 0x04, 0x11, 0x13, 0x12, 0x14, 0x21, 0x23, 0x22, 0x24, 0x31, 0x33, 0x32, 0x34 }; char free_table[] = { 'F', 'r', 'e', 'e', 'p', 't', 'r', '\0', 'F', 'r', 'e', 'e', 'p', 't', 'r', '\0' }; /* Fajl dlya hraneniya tablicy ukazatelej */ static int mem_fd = (-1); #define PTABLE "PointerTable.bin" #define NRECORDS 256 MemFileRecord memrecords[NRECORDS]; /* ============================================================= */ void MEMputTableRecord(AllocFrame *newptr, AllocFrame *oldptr, size_t size, size_t adjsize); void MEMputTableRecordKilled(AllocFrame *ptr); void MEMerasePreviousRecords(AllocFrame *ptr); int MEMcheckRecord(MemFileRecord *rec); int MEMcheck_consistency(AllocFrame *ptr); void MEMmakeRedZones(char *cptr, size_t size, size_t adjsize); void MEMopenFd(); /* ============================================================= */ /* |tim sleduet pol'zovat'sya vmesto standartnyh funkcij */ void *MEMmalloc (size_t size); void *MEMrealloc(void *ptr, size_t size); void *MEMcalloc (size_t n, size_t size); void MEMfree (void *ptr); void MEMcheckAll(); /* eto mozhno vyzyvat' v seredine programmy */ /* ============================================================= */ void MEMopenFd(){ if(mem_fd < 0){ close(creat(PTABLE, 0644)); /* sozdat' fajl */ mem_fd = open(PTABLE, O_RDWR); /* chtenie+zapis' */ unlink(PTABLE); /* tol'ko dlya M_UNIX */ atexit(MEMcheckAll); setlocale(LC_ALL, ""); } } /* Pomestit' zapis' v tablicu vseh ukazatelej na * vydelennye oblasti pamyati. */ void MEMputTableRecord(AllocFrame *newptr, /* dlya zapisi */ AllocFrame *oldptr, /* dlya stiraniya */ size_t size, /* razmer dannyh */ size_t adjsize /* razmer vsej zapisi s zonami */ ){ MemFileRecord memrecord; static int serial = 0; memrecord.ptr = newptr; memrecord.size = size; memrecord.adjsize = adjsize; memrecord.serial = serial++; MEMopenFd(); #ifdef CHECKALL /* steret' prezhnie zapisi pro etot adres */ MEMerasePreviousRecords(oldptr); #endif lseek(mem_fd, 0L, SEEK_END); /* v konec */ write(mem_fd, &memrecord, sizeof memrecord); /* dobavit' */ } /* Sdelat' zapis' ob unichtozhenii oblasti pamyati */ void MEMputTableRecordKilled(AllocFrame *ptr){ /* Pometit' kak size=0, adjsize=0 */ MEMputTableRecord(ptr, ptr, 0, 0); } /* Kody otveta funkcii proverki */ #define OK 0 /* vse horosho */ #define DAMAGED 1 /* povrezhdena "pogranzona" */ #define FREED 2 /* eta pamyat' uzhe osvobozhdena */ #define NOTHERE (-1) /* net v tablice */ /* Proverit' sohrannost' "pogranichnyh zon" */ int MEMcheckRecord(MemFileRecord *rec){ int code = OK; char *cptr; register i; AllocFrame *ptr = rec->ptr; size_t size = rec->size; size_t adjsize = rec->adjsize; if(size == 0 && adjsize == 0){ printf("%p [%p] -- segment uzhe osvobozhden, " "record=#%d.\n", &ptr->stuff[0], ptr, rec->serial ); return FREED; } cptr = (char *) ptr; for(i=0; i < adjsize; i++){ if(i < RedZoneTypeSize || i >= RedZoneTypeSize + size ){ /* golovnaya pogranzona ILI hvostovaya pogranzona */ if( cptr[i] != red_table[ i % RedZoneTypeSize ] ){ printf("%p [%p] -- isporchen bajt %4d [%4d]" "= 0x%02X '%c' record=#%d size=%lu.\n", &ptr->stuff[0], ptr, i - RedZoneTypeSize, i, cptr[i] & 0xFF, isprint(cptr[i] & 0xFF) ? cptr[i] & 0xFF : '?', rec->serial, size ); code = DAMAGED; } } } for(i=0; i < RedZoneTypeSize; i++) if(cptr[i] == free_table[i]){ printf("%p -- uzhe osvobozhdeno?\n", ptr); code = FREED; } if(code != OK) putchar('\n'); return code; } /* Proverit' sohrannost' pamyati po ukazatelyu ptr. */ int MEMcheck_consistency(AllocFrame *ptr){ MemFileRecord mr_found; int nrecords, i, found = 0; size_t size; MEMopenFd(); /* Ishchem zapis' v tablice ukazatelej */ lseek(mem_fd, 0L, SEEK_SET); /* peremotat' v nachalo */ for(;;){ size = read(mem_fd, memrecords, sizeof memrecords); nrecords = size / sizeof(memrecords[0]); if(nrecords <= 0) break; for(i=0; i < nrecords; i++) if(memrecords[i].ptr == ptr){ /* My ishchem poslednyuyu zapis' pro pamyat' * s takim adresom, poetomu * vynuzhdeny prochitat' VESX fajl. */ mr_found = memrecords[i]; found++; } } if(found) { return MEMcheckRecord(&mr_found); } else { printf("%p -- zapis' v tablice otsutstvuet.\n", ptr); return NOTHERE; } } /* Unichtozhit' vse prezhnie zapisi pro ptr, propisyvaya ih adjsize=0 */ void MEMerasePreviousRecords(AllocFrame *ptr){ int nrecords, i, found; size_t size; MEMopenFd(); lseek(mem_fd, 0L, SEEK_SET); /* peremotat' v nachalo */ for(;;){ found = 0; size = read(mem_fd, memrecords, sizeof memrecords); nrecords = size / sizeof(memrecords[0]); if(nrecords <= 0) break; for(i=0; i < nrecords; i++) if(memrecords[i].ptr == ptr){ memrecords[i].adjsize = 0; /* memrecords[i].size = 0; */ found++; } if(found){ lseek(mem_fd, -size, SEEK_CUR); /* shag nazad */ write(mem_fd, memrecords, size); /* perezapisat' */ } } } void MEMcheckAll(){ #ifdef CHECKALL int nrecords, i; size_t size; printf("Proverka vseh ukazatelej -------------\n"); MEMopenFd(); lseek(mem_fd, 0L, SEEK_SET); /* peremotat' v nachalo */ for(;;){ size = read(mem_fd, memrecords, sizeof memrecords); nrecords = size / sizeof(memrecords[0]); if(nrecords <= 0) break; for(i=0; i < nrecords; i++) if(memrecords[i].adjsize != 0) MEMcheckRecord(&memrecords[i]); } printf("Proverka vseh ukazatelej zavershena ---\n"); #endif } /* ============================================================= */ /* Zapolnenie pogranichnyh zon obrazcom - "sledovoj dorozhkoj" */ void MEMmakeRedZones(char *cptr, size_t size, size_t adjsize){ register i; for(i=0; i < adjsize; i++){ if(i < RedZoneTypeSize || i >= RedZoneTypeSize + size ){ /* golovnaya pogranzona ILI * hvostovaya pogranzona + dopolnenie * do celogo chisla RedZoneType-ov */ cptr[i] = red_table[ i % RedZoneTypeSize ]; } } } /* ============================================================= */ /* Funkciya vydeleniya pamyati */ void *MEMmalloc(size_t size){ AllocFrame *retptr; int fullRedZoneTypes = (size + RedZoneTypeSize - 1) / RedZoneTypeSize; size_t adjustedSize = sizeof(retptr->red_zone) * 2 + /* dve pogranzony */ fullRedZoneTypes * RedZoneTypeSize; retptr = (AllocFrame *) malloc(adjustedSize); if(retptr == NULL) return NULL; MEMmakeRedZones ((char *) retptr, size, adjustedSize); MEMputTableRecord(retptr, retptr, size, adjustedSize); return &retptr->stuff[0]; /* vernut' ukazatel' na zonu dannyh */ } void *MEMrealloc(void *ptr, size_t size){ AllocFrame *retptr; char *cptr = (char *)ptr - RedZoneTypeSize; /* prezhnij AllocFrame */ AllocFrame *oldptr = (AllocFrame *) cptr; int fullRedZoneTypes = (size + RedZoneTypeSize - 1) / RedZoneTypeSize; size_t adjustedSize = sizeof(retptr->red_zone) * 2 + fullRedZoneTypes * RedZoneTypeSize; /* Proverit' sohrannost' togo, chto my sejchas budem realloc-it' */ MEMcheck_consistency(oldptr); retptr = (AllocFrame *) realloc((void *)oldptr, adjustedSize); if(retptr == NULL) return NULL; MEMmakeRedZones ((char *) retptr, size, adjustedSize); MEMputTableRecord(retptr, oldptr, size, adjustedSize); return &retptr->stuff[0]; } void *MEMcalloc(size_t n, size_t size){ size_t newsize = n * size; void *ptr = MEMmalloc(newsize); memset(ptr, '\0', newsize); return ptr; } /* Ochistka otvedennoj pamyati. * ptr - eto ukazatel' ne na AllocFrame, * a na dannye - to est' na stuff[0]. */ void MEMfree(void *ptr){ char *cptr = (char *)ptr - RedZoneTypeSize; int i, code; code = MEMcheck_consistency((AllocFrame *) cptr); for(i=0; i < RedZoneTypeSize; i++) cptr[i] = free_table[i]; if(code != FREED) free((void *) cptr); MEMputTableRecordKilled((AllocFrame *) cptr); } /* ============================================================= */ /* Testovyj primer */ /* ============================================================= */ #define MAXPTRS 512 char *testtable[MAXPTRS]; /* Sgenerirovat' stroku sluchajnoj dliny so sluchajnym soderzhimym */ char *wildstring(int c){ #define N 1024 char teststring[N + 1]; int len, i; char *ptr; len = rand() % N; for(i=0; i < len; i++) teststring[i] = c; teststring[len] = '\0'; ptr = (char *) MEMmalloc(len + 1); if(ptr){ strcpy(ptr, teststring); } else printf("NULL wildstring()\n"); return ptr; } int main(int ac, char *av[]){ int ilen, len, n, i; srand(time(NULL)); for(n=0; n < MAXPTRS; n++) testtable[n] = wildstring('A'); #define DAMAGE (MAXPTRS/3*2-1) #ifdef DAMAGE /* Navesti porchu */ len = strlen(testtable[DAMAGE]); testtable[DAMAGE][len+1] = 'x'; testtable[DAMAGE][-2] = 'y'; printf("ptr=%p len=%d\n", testtable[DAMAGE], len); #endif for(n=0; n < MAXPTRS/2; n++){ char *p = wildstring('B'); int length = strlen(p); char *ptr; i = rand() % MAXPTRS; /* Ne zabyt' prisvoit' vozvrashchennoe realloc() znachenie * obratno v testtable[i] !!! */ testtable[i] = ptr = (char *) MEMrealloc(testtable[i], length + 1); if(ptr == NULL) printf("Ne mogu sdelat' realloc()\n"); else strcpy(ptr, p); #ifdef DAMAGE /* Porcha */ if(n == MAXPTRS/3){ ptr[length+2] = 'z'; } #endif MEMfree(p); } for(n=0; n < MAXPTRS; n++){ if(testtable[n]) MEMfree(testtable[n]); } #ifdef DAMAGE MEMfree(testtable[DAMAGE]); #endif return 0; } /* Primer 12 */ /* Programma, sovmeshchayushchaya komandy mv i cp. Illyustraciya raboty s fajlami. * Primer togo, kak programma mozhet vybirat' rod raboty * po svoemu nazvaniyu. * Kompilyaciya: * cc cpmv.c -o copy ; ln copy move * Po motivam knigi M.Dansmura i G.Dejvisa. */ #include <stdio.h> /* buferizovannyj vvod/vyvod */ #include <sys/types.h> /* sistemnye tipy dannyh */ #include <sys/stat.h> /* struct stat */ #include <fcntl.h> /* O_RDONLY */ #include <errno.h> /* sistemnye kody oshibok */ /* #define strrchr rindex /* dlya versii DEMOS (BSD) */ extern char *strrchr(char *, char); /* iz biblioteki libc.a */ extern int errno; char MV[] = "move"; char CP[] = "copy"; #define OK 1 /* success - uspeh */ #define FAILED 0 /* failure - neudacha */ #define YES OK #define NO 0 /* Vydelit' bazovoe imya fajla: * ../wawa/xxx --> xxx * zzz --> zzz * / --> / */ char *basename( char *name ){ char *s = strrchr( name , '/' ); return (s == NULL) ? name : /* net sleshej */ (s[1] == '\0') ? name : /* kornevoj katalog */ s + 1; } #define ECANTSTAT (-1) /* fajl ne sushchestvuet */ struct ftype { unsigned type; /* tip fajla */ dev_t dev; /* kod ustrojstva, soderzhashchego fajl */ ino_t ino; /* indeksnyj uzel fajla na etom ustrojstve */ }; /* Poluchenie tipa fajla */ struct ftype filetype( char *name /* imya fajla */ ) { struct stat st; struct ftype f; if( stat( name, &st ) < 0 ){ f.type = ECANTSTAT; f.dev = f.ino = 0; } else { f.type = st.st_mode & S_IFMT; f.dev = st.st_dev; f.ino = st.st_ino; } return f; } /* Udalyaet fajly, krome ustrojstv */ int unlinkd( char *name, unsigned type ) { if( type == S_IFBLK || type == S_IFCHR || type == S_IFDIR) return 0; return unlink( name ); } /* Funkciya nizhnego urovnya: kopirovanie informacii bol'shimi porciyami */ int copyfile( int from, int to ) /* from - deskriptor otkuda */ /* to - deskriptor kuda */ { char buffer[ BUFSIZ ]; int n; /* chislo prochitannyh bajt */ while(( n = read( from, buffer, BUFSIZ )) > 0 ) /* read vozvrashchaet chislo prochitannyh bajt, * 0 v konce fajla */ if( write( to, buffer, n ) != n ){ printf( "Write error.\n" ); return FAILED; } return OK; } /* Kopirovanie fajla */ int docopy(char *src, char *dst, unsigned typefrom, unsigned typeto) { int retc; int fdin, fdout; printf( "copy %s --> %s\n", src, dst ); if((fdin = open( src, O_RDONLY )) < 0 ){ printf( "San't read %s\n", src ); return FAILED; } if((fdout = creat( dst, 0644 )) < 0 ){ /* rw-r--r-- */ printf( "Can't create %s\n", dst ); return FAILED; } retc = copyfile( fdin, fdout ); close( fdin ); close( fdout ); return retc; } /* Pereimenovanie fajla. Vernut' OK, esli udachno, FAILED - neudachno */ int mlink(char *src, char *dst, unsigned typefrom, unsigned typeto) { switch( typefrom ){ case S_IFDIR: /* pereimenovanie kataloga */ printf( "rename directory %s --> %s\n", src, dst ); if( access( dst, 0 ) == 0 ){ /* 0 - proverit' sushchestvovanie fajla */ printf( "%s exists already\n", dst ); /* fajl uzhe sushchestvuet */ return FAILED; } if( link( src, dst ) < 0 ){ printf( "Can't link to directory %s\n", dst ); perror( "link" ); /* Vozmozhno, chto dlya vypolneniya link() dlya katalogov, * programma dolzhna obladat' pravami superpol'zovatelya. */ return FAILED; } unlink( src ); return OK; default: /* dst - ne sushchestvuet ili obychnyj fajl */ printf( "move %s --> %s\n", src, dst ); unlinkd( dst, typeto ); /* zachishchaem mesto, t.k. link() * otkazyvaetsya vypolnyat'sya, esli * fajl dst uzhe sushchestvuet (errno==EEXIST). */ if( link( src, dst ) < 0 ) return FAILED; unlinkd( src, typefrom ); /* udalyaem staryj fajl */ return OK; } } /* Esli ne poluchilos' svyazat' fajl pri pomoshchi link() - sleduet * skopirovat' fajl v ukazannoe mesto, a zatem unichtozhit' staryj fajl. */ int mcopy(char *src, char *dst, unsigned typefrom, unsigned typeto) { if( typefrom == S_IFDIR ) return FAILED; /* katalog ne kopiruem, poskol'ku neposredstvennaya zapis' * v katalog (kak celevoj fajl) razreshena tol'ko yadru OS. */ return docopy( src, dst, typefrom, typeto ); } /* Pereimenovanie fajla */ int domove(char *src, char *dst, unsigned typefrom, unsigned typeto) { switch( typefrom ){ default: if( ! mlink( src, dst, typefrom, typeto)){ if( ! mcopy( src, dst, typefrom, typeto)){ printf( "Can't move %s\n", src ); return FAILED; } else unlinkd( src, typefrom ); /* steret' staryj */ } break; case S_IFDIR: /* katalog pereimenovyvaem v katalog */ if( ! strcmp( ".", basename(src))){ printf( "impossible to move directory \".\"\n" ); return FAILED; } if( ! mlink( src, dst, typefrom, typeto )){ if( errno == EXDEV ) printf( "No cross device directory links\n" ); return FAILED; } break; case ECANTSTAT: printf( "%s does not exist\n", src ); return FAILED; } return OK; /* okay */ } int docpmv( char *src, /* fajl-istochnik */ char *dst, /* fajl-poluchatel' */ struct ftype typeto, /* tip fajla-poluchatelya */ int cp, /* 0 - pereimenovanie, 1 - kopirovanie */ int *confirm /* zaprashivat' podtverzhdenie na perezapis' ? */ ){ struct ftype typefrom; /* tip istochnika */ char namebuf[BUFSIZ]; /* novoe imya poluchatelya (esli nado) */ typefrom = filetype(src); if(typefrom.type == ECANTSTAT){ /* ne sushchestvuet */ printf("%s does not exist.\n", src); return FAILED; } if( typefrom.type != S_IFDIR && typeto.type == S_IFDIR ){ /* fajl v kataloge dst */ sprintf(namebuf, "%s/%s", dst, basename(src)); typeto = filetype(dst = namebuf); } if(typefrom.dev == typeto.dev && typefrom.ino == typeto.ino){ /* Nel'zya kopirovat' fajl sam v sebya */ printf("%s and %s are identical.\n", src, dst); return OK; /* tak kak fajl uzhe est' - schitaem eto udachej */ } /* esli poluchatel' uzhe sushchestvuet, to * zaprosit' podtverzhdenie na perezapis' */ if(*confirm && typeto.type == S_IFREG){ char answer[40]; printf("%s already exists. Overwrite (y/n/all) ? ", dst); fflush(stdout); switch( *gets(answer)){ case 'n': default: return OK; /* nichego ne delat' */ case 'y': break; case 'a': *confirm = NO; /* dal'she - bez zaprosov */ break; } } return cp ? docopy(src, dst, typefrom.type, typeto.type) : domove(src, dst, typefrom.type, typeto.type) ; } void main(int argc, char *argv[]) { char *cmd; int cp, i, err, confirm = YES; struct ftype typeto; /* tip fajla-poluchatelya */ if( argc < 3 ) { printf( "Usage: %s source... destination\n", argv[0] ); exit(1); /* nenulevoj kod vozvrata signaliziruet ob oshibke */ } /* vydelyaem bazovoe imya programmy. */ cmd = basename( argv[0] ); if ( !strcmp( cmd, CP )) cp = 1; else if( !strcmp( cmd, MV )) cp = 0; else{ printf( "%s - wrong program name.\n", cmd ); exit(2); } typeto = filetype( argv[argc-1] ); if(cp && typeto.type != S_IFDIR && typeto.type != S_IFBLK && typeto.type != S_IFCHR && argc > 3){ printf("Group of files can be copied " "to the directory or device only.\n"); exit(3); } if(!cp && typeto.type != S_IFDIR && argc > 3){ printf("Group of files can be moved " "to the directory only.\n"); exit(4); } for(err=0, i=1; i < argc-1; i++) err += ! docpmv(argv[i], argv[argc-1], typeto, cp, &confirm); exit(err); /* 0, esli ne bylo oshibok */ } /* Primer 13 */ /* Obhod dereva katalogov v MS DOS pri pomoshchi smeny tekushchego kataloga. * Analog ls -R v UNIX. Po analogichnomu algoritmu rabotaet programma * find . -print (napishite komandu find, ispol'zuya match()) */ #define STYLE2 #include <stdio.h> #include <stdlib.h> #include <dir.h> #include <dos.h> #include <alloc.h> /* dlya malloc() */ #include <string.h> /* strchr(), strrchr(), strcpy(), ... */ /* prototipy */ char *strend(char *s); char *strdup(const char *s); void action(int, char **); void main(int, char **); int listdir(char *); void printdir(int n); #ifdef STYLE2 void lookdir(char *s, int ac, char **av, register int level); #else void lookdir(char *s, int ac, char **av); #endif char root[256]; /* imya startovogo kataloga */ char cwd[256]; /* polnoe imya tekushchego kataloga */ char *strend(register char *s){ while(*s)s++; return s; } char *strdup(const char *s){ /* prototip malloc v <stdlib.h> */ char *p = (char *) malloc(strlen(s) + 1); if(p) strcpy(p, s); return p; } stop(){ /* Reakciya na control/break */ chdir( root ); /* |to neobhodimo potomu, chto MS DOS imeet (v otlichie ot UNIX) ponyatie "tekushchij katalog" kak global'noe dlya vsej sistemy. Esli my prervem programmu, to okazhemsya ne v tom kataloge, otkuda nachinali. */ printf( "\nInterrupted by ctrl-break\n"); return 0; /* exit */ } void main(int argc, char **argv){ /* poluchit' imya tekushchego kataloga */ (void) getcwd(root, sizeof root); ctrlbrk( stop ); /* ustanovit' reakciyu na ctrl/break */ #ifndef STYLE2 lookdir( "." /* koren' dereva */, argc, argv ); #else /* dlya primera: derevo ot "\\" a ne ot "." */ lookdir( "\\", argc, argv, 0 /* nachal'nyj uroven' */ ); #endif /*STYLE2*/ chdir(root); /* vernut'sya v ish. katalog */ } # ifndef STYLE2 void lookdir(char *s, int ac, char **av){ static int level = 0; /* uroven' rekursii */ # else void lookdir(char *s, int ac, char **av, register int level){ # endif /*STYLE2*/ struct ffblk dblk, *psd = &dblk; register done; if( chdir(s) < 0 ){ /* vojti v katalog */ printf( "Cannot cd %s\n", s ); return; } else if (level == 0){ /* verhnij uroven' */ (void) getcwd(cwd, sizeof cwd); /* poluchit' polnoe imya kornya poddereva */ } action(ac, av); /* iskat' imena katalogov, udovletvoryayushchie shablonu "*" */ /* (ne v alfavitnom poryadke !) */ done = findfirst("*.", psd, FA_DIREC); while( !done ){ if((psd->ff_attrib & FA_DIREC) && psd->ff_name[0] != '.' ){ /* Vidim katalog: vojti v nego! */ char *tail = strend(cwd); char *addplace; if( tail[-1] == '\\' ){ addplace = tail; }else{ *tail = '\\'; addplace = tail+1; } strcpy(addplace, psd->ff_name); #ifndef STYLE2 level++; lookdir( psd->ff_name, ac, av ); level--; #else lookdir( psd->ff_name, ac, av, level+1 ); #endif *tail = '\0'; } /* Iskat' sleduyushchee imya. Informaciya o tochke, gde byl * prervan poisk, hranitsya v dblk */ done = findnext(psd); } if( level ) chdir( ".." ); /* vyjti vverh */ } /* Vypolnit' dejstviya v kataloge */ void action(int ac, char **av){ extern int busy; busy = 0; if( ac == 1 ) listdir( "*.*" ); else{ av++; while( *av ) listdir( *av++ ); } printdir( busy ); } #define MAXF 400 struct fst{ char *name; long size; short attr; } files[MAXF]; int busy; /* skol'ko imen sobrano */ /* Sobrat' imena, udovletvoryayushchie shablonu. */ int listdir( char *picture ){ int done, n; struct ffblk dentry; for(n=0, done=findfirst(picture, &dentry,0xFF /* vse tipy */); busy < MAXF && !done ; done = findnext( &dentry )){ files[busy].name = strdup(dentry.ff_name); files[busy].size = dentry.ff_fsize; files[busy].attr = dentry.ff_attrib; n++; busy++; } return n; } /* int cmp(struct fst *a, struct fst *b) */ /* novye veyaniya v Si trebuyut takogo prototipa: */ int cmp(const void *a, const void *b){ return strcmp(((struct fst *) a) -> name, ((struct fst *) b) -> name ); } /* otsortirovat' i napechatat' */ void printdir(int n){ register i; struct fst *f; qsort( files, n, sizeof files[0], cmp ); printf( "Directory %s\n", cwd ); for( i=0, f = files; i < n; i++, f++ ) printf("\t%-16s\t%10ld\t%c%c%c%c%c%c\n", f->name, f->size, f->attr & FA_DIREC ? 'd':'-', /* directory */ f->attr & FA_RDONLY ? 'r':'-', /* read only */ f->attr & FA_HIDDEN ? 'h':'-', /* hidden */ f->attr & FA_SYSTEM ? 's':'-', /* system */ f->attr & FA_LABEL ? 'l':'-', /* volume label */ f->attr & FA_ARCH ? 'a':'-' /* archive */ ), free(f->name); putchar('\n'); } /* Primer 14 */ /* Demonstraciya raboty s longjmp/setjmp i signalami */ /* Po motivam knigi M.Dansmura i G.Dejvisa. */ #include <stdio.h> #include <fcntl.h> #include <signal.h> #include <setjmp.h> /*#define IGN*/ /* potom otkommentirujte etu stroku */ jmp_buf cs_stack; /* control point */ int in_cs; /* flag, chto my v kriticheskoj sekcii */ int sig_recd; /* flag signal received */ /* aktivnaya zaderzhka */ Delay(){ int i; for( i=0; i < 10000; i++ ){ i += 200; i -= 200; } } interrupt( code ){ fprintf( stderr, "\n\n***\n" ); fprintf( stderr, "*** Obrabatyvaem signal (%s)\n", code == 1 ? "razreshennyj" : "otlozhennyj" ); fprintf( stderr, "***\n\n" ); } /* argument reakcii na signal - nomer signala (podstavlyaetsya sistemoj) */ void mexit( nsig ){ fprintf( stderr, "\nUbili signalom #%d...\n\n", nsig ); exit(0); } void main(){ extern void sig_vec(); int code; int killable = 1; signal( SIGINT, mexit ); signal( SIGQUIT, mexit ); fprintf( stderr, "Dannaya programma perezapuskaetsya po signalu INTR\n" ); fprintf( stderr, "Vyhod iz programmy po signalu QUIT\n\n\n" ); fprintf( stderr, "Sejchas vy eshche mozhete uspet' ubit' etu programmu...\n\n" ); Delay(); Delay(); Delay(); for(;;){ if( code = setjmp( cs_stack )){ /* Vozvrashchaet ne 0, esli vozvrat v etu tochku proizoshel * po longjmp( cs_stack, code ); gde code != 0 */ interrupt( code ); /* prishlo preryvanie */ } /* else setjmp() vozvrashchaet 0, * esli eto USTANOVKA kontrol'noj tochki (to est' * sohranenie registrov SP, PC i drugih v bufer cs_stack), * a ne pryzhok na nee. */ signal( SIGINT, sig_vec ); /* vyzyvat' po preryvaniyu */ if( killable ){ killable = 0; fprintf( stderr, "\7Teper' signaly INTR obrabatyvayutsya osobym obrazom\n\n\n" ); } body(); /* osnovnaya programma */ } } body(){ static int n = 0; int i; fprintf( stderr, "\tVoshli v telo %d-yj raz\n", ++n ); ecs(); for( i=0; i < 10 ; i++ ){ fprintf( stderr, "- %d\n",i); Delay(); } lcs(); for( i=0; i < 10 ; i++ ){ fprintf( stderr, "+ %d\n",i); Delay(); } } /* zapominanie poluchennyh signalov */ void sig_vec(nsig){ if( in_cs ){ /* we're in critical section */ #ifdef IGN signal( SIGINT, SIG_IGN ); /* ignorirovat' */ fprintf( stderr, "Dal'nejshie preryvaniya budut ignorirovat'sya\n" ); #else signal( SIGINT, sig_vec ); fprintf( stderr, "Dal'nejshie preryvaniya budut podschityvat'sya\n" ); #endif fprintf( stderr, "Poluchen signal i otlozhen\n" ); sig_recd++ ; /* signal received */ /* pometit', chto signal prishel */ }else{ signal( SIGINT, sig_vec ); fprintf( stderr, "Poluchen razreshennyj signal: prygaem na restart\n" ); longjmp( cs_stack, 1); } } ecs(){ /* enter critical section */ fprintf( stderr, "Otkladyvaem preryvaniya\n" ); sig_recd = 0; in_cs = 1; } lcs(){ /* leave critical section */ fprintf( stderr, "Razreshaem preryvaniya\n" ); in_cs = 0; if( sig_recd ){ fprintf( stderr, "Prygaem na restart, t.k. est' otlozhennyj signal (%d raz)\n", sig_recd ); sig_recd = 0; signal( SIGINT, sig_vec ); longjmp( cs_stack, 2); } } /* Primer 15 */ /* Komanda dlya izmeneniya skorosti obmena v linii (baud).*/ /* Primer vyzova v XENIX: baud /dev/tty1a 9600 */ /* /dev/tty1a - eto kommunikacionnyj posledov. port #1 */ /* Pro upravlenie modami terminala smotri man termio */ #include <fcntl.h> #include <termio.h> struct termio old, new; int fd = 2; /* stderr */ struct baudrate{ int speed; char *name;} br[] = { { B0, "HANGUP" }, { B1200, "1200" }, { B9600, "9600" }, { B600, "600" }, { B2400, "2400" }, { EXTA, "19200" }, }; #define RATES (sizeof br/sizeof br[0]) main(ac, av) char *av[]; { register i; char *newbaud; if( ac == 3 ){ if((fd = open(av[1], O_RDWR)) < 0 ){ printf("Ne mogu otkryt' %s\n", av[1]); exit(1); } newbaud = av[2]; } else newbaud = av[1]; if( ioctl(fd, TCGETA, &old) < 0 ){ printf("Popytka upravlyat' ne terminalom i ne portom.\n"); exit(2); } if(newbaud == (char*)0) newbaud = "<ne zadano>"; new=old; for(i=0; i < RATES; i++) if((old.c_cflag & CBAUD) == br[i].speed) goto ok; printf("Neizvestnaya skorost'\n"); exit(3); ok: printf("Bylo %s bod\n", br[i].name); for(i=0; i < RATES; i++) if( !strcmp(newbaud, br[i].name)){ new.c_cflag &= ~CBAUD; /* pobitnoe "ili" vseh masok B... */ new.c_cflag |= br[i].speed; if( ioctl(fd, TCSETA, &new) < 0) perror("ioctl"); /* Skorost' obmena mozhet ne izmenit'sya, esli terminal * ne otkryt ni odnim processom (drajver ne inicializirovan). */ exit(0); } printf("Nevernaya skorost' %s\n", newbaud); exit(4); } /* Primer 16 */ /*#!/bin/cc -DUSG wins.c -o wins -lncurses -lx Prosmotr dvuh fajlov v perekryvayushchihsya oknah. Redaktirovanie soderzhimogo okon. */ /* _______________________ fajl wcur.h __________________________ */ #include "curses.h" /* Makrosy, zavisimye ot realizacii curses */ /* chislo kolonok i strok v okne: */ # define wcols(w) ((w)-> _maxx+1 ) # define wlines(w) ((w)-> _maxy+1 ) /* verhnij levyj ugol okna: */ # define wbegx(w) ((w)-> _begx ) # define wbegy(w) ((w)-> _begy ) /* koordinaty kursora v okne: */ # define wcurx(w) ((w)-> _curx ) # define wcury(w) ((w)-> _cury ) /* dostup k pamyati strok okna: */ # define wtext(w) ((w)-> _line) /* chtype **_line; */ /* v drugih realizaciyah: ((w)-> _y) */ /* Psevdografika: Dlya curses Dlya IBM PC MS DOS */ #define HOR_LINE '\200' /* 196 */ #define VER_LINE '\201' /* 179 */ #define UPPER_LEFT '\210' /* 218 */ #define LOWER_LEFT '\202' /* 192 */ #define UPPER_RIGHT '\212' /* 191 */ #define LOWER_RIGHT '\204' /* 217 */ #define LEFT_JOIN '\205' /* 195 */ #define RIGHT_JOIN '\207' /* 180 */ #define TOP_JOIN '\211'