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