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 -