iskivat' 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座avleniya 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容ktnyj" fajl A. Bogatyrev, 1992-95 - 77 - Si v UNIX (soderzhashchij mashinnye komandy; ne budem vdavat'sya v podrobnosti). CHetvertaya komanda "skleivaet" ob容ktnye 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容ktnye 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容ktnye 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容ktnyh 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容ktnyh 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容ktnyh .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容ktnye 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 2. Massivy, stroki, ukazateli. Massiv predstavlyaet soboj agregat iz neskol'kih peremennyh odnogo i togo zhe tipa. Massiv s imenem a iz LENGTH elementov tipa TYPE ob座avlyaetsya tak: TYPE a[LENGTH]; |to sootvetstvuet tomu, chto ob座avlyayutsya 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座avlyat' 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座avlenie 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座asnyaet 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座avit' 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座avlenie 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座avlenii 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容kta 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座avit' 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座avleniyu { '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 -