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 4. Rabota s fajlami. 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 (i