ni est' */
while( next_arrangement (res))
print_arrangement(res, m);
clean_iterator(res);
1.89. Napishite makroopredeleniya ciklicheskogo sdviga peremennoj tipa unsigned int na
skew bit vlevo i vpravo (ROL i ROR). Otvet:
#define BITS 16 /* pust' celoe sostoit iz 16 bit */
#define ROL(x,skew) x=(x<<(skew))|(x>>(BITS-(skew)))
#define ROR(x,skew) x=(x>>(skew))|(x<<(BITS-(skew)))
A. Bogatyrev, 1992-95 - 40 - Si v UNIX
Vot kak rabotaet ROL(x, 2) pri BITS=6
|abcdef| ishodno
abcdef00 << 2
0000abcdef >> 4
------ operaciya |
cdefab rezul'tat
V sluchae signed int potrebuetsya nakladyvat' masku pri sdvige vpravo iz-za togo, chto
levye bity pri >> ne zapolnyayutsya nulyami. Privedem primer dlya sdviga peremennoj tipa
signed char (po umolchaniyu vse char - znakovye) na 1 bit vlevo:
#define CHARBITS 8
#define ROLCHAR1(x) x=(x<<1)|((x>>(CHARBITS-1)) & 01)
sootvetstvenno dlya sdviga
na 2 bita nado delat' & 03
na 3 & 07
na 4 & 017
na skew & ~(~0 << skew)
1.90. Napishite programmu, kotoraya invertiruet (t.e. zamenyaet 1 na 0 i naoborot) N
bitov, nachinayushchihsya s pozicii P, ostavlyaya drugie bity bez izmeneniya. Vozmozhnyj
otvet:
unsigned x, mask;
mask = ~(~0 << N) << P;
x = (x & ~mask) | (~x & mask);
/* xnew */
Gde maska poluchaetsya tak:
~0 = 11111....11111
~0 << N = 11111....11000 /* N nulej */
~(~0 << N) = 00000....00111 /* N edinic */
~(~0 << N) << P = 0...01110...00
/* N edinic na mestah P+N-1..P */
1.91. Operacii umnozheniya * i deleniya / i % obychno dostatochno medlenny. V kritichnyh
po skorosti funkciyah mozhno predprinyat' nekotorye ruchnye optimizacii, svyazannye s
predstavleniem chisel v dvoichnom kode (horoshij kompilyator delaet eto sam!) - pol'zuyas'
tem, chto operacii +, &, >> i << gorazdo bystree. Pust' u nas est'
unsigned int x;
(dlya signed operaciya >> mozhet ne zapolnyat' osvobozhdayushchiesya levye bity nulem!) i 2**n
oznachaet 2 v stepeni n. Togda:
x * (2**n) = x << n
x / (2**n) = x >> n
x % (2**n) = x - ((x >> n) << n)
x % (2**n) = x & (2**n - 1)
eto 11...111 n dvoichnyh edinic
Naprimer:
A. Bogatyrev, 1992-95 - 41 - Si v UNIX
x * 8 = x << 3;
x / 8 = x >> 3; /* delenie nacelo */
x % 8 = x & 7; /* ostatok ot deleniya */
x * 80 = x*64 + x*16 = (x << 6) + (x << 4);
x * 320 = (x * 80) * 4 = (x * 80) << 2 =
(x << 8) + (x << 6);
x * 21 = (x << 4) + (x << 2) + x;
x & 1 = x % 2 = chetnoe(x)? 0:1 = nechetnoe(x)? 1:0;
x & (-2) = x & 0xFFFE = | esli x = 2*k to 2*k
| esli x = 2*k + 1 to 2*k
| to est' okruglyaet do chetnogo
Ili formula dlya vychisleniya kolichestva dnej v godu (visokosnyj/prostoj):
days_in_year = (year % 4 == 0) ? 366 : 365;
zamenyaem na
days_in_year = ((year & 0x03) == 0) ? 366 : 365;
Vot eshche odno poleznoe ravenstvo:
x = x & (a|~a) = (x & a) | (x & ~a) = (x&a) + (x&~a)
iz chego vytekaet, naprimer
x - (x % 2**n) = x - (x & (2**n - 1)) =
= x & ~(2**n - 1) = (x>>n) << n
x - (x%8) = x-(x&7) = x & ~7
Poslednyaya stroka mozhet byt' ispol'zovana v funkcii untab() v glave "Tekstovaya obra-
botka".
1.92. Obychno my vychislyaem min(a,b) tak:
#define min(a, b) (((a) < (b)) ? (a) : (b))
ili bolee razvernuto
if(a < b) min = a;
else min = b;
Zdes' est' operaciya sravneniya i uslovnyj perehod. Odnako, esli (a < b) ekvivalentno
usloviyu (a - b) < 0, to my mozhem izbezhat' sravneniya. |to predpolozhenie verno pri
(unsigned int)(a - b) <= 0x7fffffff.
chto, naprimer, verno esli a i b - oba neotricatel'nye chisla mezhdu 0 i 0x7fffffff.
Pri etih usloviyah
min(a, b) = b + ((a - b) & ((a - b) >> 31));
Kak eto rabotaet? Rassmotrim dva sluchaya:
A. Bogatyrev, 1992-95 - 42 - Si v UNIX
Sluchaj 1: a < b
Zdes' (a - b) < 0, poetomu starshij (levyj, znakovyj) bit
raznosti (a - b) raven 1.
Sledovatel'no, (a - b) >> 31 == 0xffffffff,
i my imeem:
min(a, b) = b + ((a - b) & ((a - b) >> 31))
= b + ((a - b) & (0xffffffff))
= b + (a - b)
= a
chto korrektno.
Sluchaj 2: a >= b
Zdes' (a - b) >= 0, poetomu starshij bit raznosti
(a - b) raven 0. Togda (a - b) >> 31 == 0, i my imeem:
min(a, b) = b + ((a - b) & ((a - b) >> 31))
= b + ((a - b) & (0x00000000))
= b + (0)
= b
chto takzhe korrektno.
Stat'ya predostavlena by Jeff Bonwick.
1.93. Est' li bystryj sposob opredelit', yavlyaetsya li X stepen'yu dvojki? Da, est'.
int X yavlyaetsya stepen'yu dvojki
togda i tol'ko togda, kogda
(X & (X - 1)) == 0
(v chastnosti 2 zdes' okazhetsya stepen'yu dvojki). Kak eto rabotaet? Pust' X != 0. Esli
X - celoe, to ego dvoichnoe predstavlenie takovo:
X = bbbbbbbbbb10000...
gde 'bbb' predstavlyaet nekie bity, '1' - mladshij bit, i vse ostal'nye bity pravee -
nuli. Poetomu:
X = bbbbbbbbbb10000...
X - 1 = bbbbbbbbbb01111...
------------------------------------
X & (X - 1) = bbbbbbbbbb00000...
Drugimi slovami, X & (X-1) imeet effekt obnuleniya poslednego edinichnogo bita. Esli X
- stepen' dvojki, to on soderzhit v dvoichnom predstavlenii rovno ODIN takoj bit, poe-
tomu ego gashenie obrashchaet rezul'tat v nol'. Esli X - ne stepen' dvojki, to v slove
est' hotya by DVA edinichnyh bita, poetomu X & (X-1) dolzhno soderzhat' hotya by odin iz
ostavshihsya edinichnyh bitov - to est' ne ravnyat'sya nulyu.
Sledstviem etogo sluzhit programma, vychislyayushchaya chislo edinichnyh bitov v slove X:
int popc;
for (popc = 0; X != 0; X &= X - 1)
popc++;
Pri etom potrebuetsya ne 32 iteracii (chislo bit v int), a rovno stol'ko, skol'ko edi-
nichnyh bitov est' v X. Stat'ya predostavlena by Jeff Bonwick.
A. Bogatyrev, 1992-95 - 43 - Si v UNIX
1.94. Funkciya dlya poiska nomera pozicii starshego edinichnogo bita v slove. Ispol'zu-
etsya binarnyj poisk: poziciya nahoditsya maksimum za 5 iteracij (dvoichnyj logarifm
32h), vmesto 32 pri linejnom poiske.
int highbit (unsigned int x)
{
int i;
int h = 0;
for (i = 16; i >= 1; i >>= 1) {
if (x >> i) {
h += i;
x >>= i;
}
}
return (h);
}
Stat'ya predostavlena by Jeff Bonwick.
1.95. Napishite funkciyu, okruglyayushchuyu svoj argument vniz do stepeni dvojki.
#include <stdio.h>
#define INT short
#define INFINITY (-999)
/* Funkciya, vydayushchaya chislo, yavlyayushcheesya okrugleniem vniz
* do stepeni dvojki.
* Naprimer:
* 0000100010111000110
* zamenyaetsya na
* 0000100000000000000
* to est' ostaetsya tol'ko starshij bit.
* V parametr power2 vozvrashchaetsya nomer bita,
* to est' pokazatel' stepeni dvojki. Esli chislo == 0,
* to eta stepen' ravna minus beskonechnosti.
*/
A. Bogatyrev, 1992-95 - 44 - Si v UNIX
unsigned INT round2(unsigned INT x, int *power2){
/* unsigned - chtoby chislo rassmatrivalos' kak
* bitovaya shkala, a sdvig >> zapolnyal levye bity
* nulem, a ne rasshiryal vpravo znakovyj bit.
* Ideya funkcii: sdvigat' chislo >> poka ne poluchitsya 1
* (mozhno bylo by vybrat' 0).
* Zatem sdvinut' << na stol'ko zhe razryadov, pri etom vse pravye
* razryady zapolnyatsya nulem, chto i trebovalos'.
*/
int n = 0;
if(x == 0){
*power2 = -INFINITY; return 0;
}
if(x == 1){
*power2 = 0; return 1;
}
while(x != 1){
x >>= 1;
n++;
if(x == 0 || x == (unsigned INT)(-1)){
printf("Vizhu %x: pohozhe, chto >> rasshiryaet znakovyj bit.\n"
"Zaciklilis'!!!\n", x);
return (-1);
}
}
x <<= n;
*power2 = n; return x;
}
int counter[ sizeof(unsigned INT) * 8];
int main(void){
unsigned INT i;
int n2;
for(i=0; ; i++){
round2(i, &n2);
if(n2 == -INFINITY) continue;
counter[n2]++;
/* Nel'zya pisat' for(i=0; i < (unsigned INT)(-1); i++)
* potomu chto takoj cikl beskonechen!
*/
if(i == (unsigned INT) (-1)) break;
}
for(i=0; i < sizeof counter/sizeof counter[0]; i++)
printf("counter[%u]=%d\n", i, counter[i]);
return 0;
}
1.96. Esli nekotoraya vychislitel'naya funkciya budet vyzyvat'sya mnogo raz, ne sleduet
prenebregat' vozmozhnost'yu postroit' tablicu reshenij, gde znachenie vychislyaetsya odin
raz dlya kazhdogo vhodnogo znacheniya, zato potom beretsya neposredstvenno iz tablicy i ne
vychislyaetsya voobshche. Primer: podschet chisla edinichnyh bit v bajte. Napominayu: bajt
sostoit iz 8 bit.
A. Bogatyrev, 1992-95 - 45 - Si v UNIX
#include <stdio.h>
int nbits_table[256];
int countBits(unsigned char c){
int nbits = 0;
int bit;
for(bit = 0; bit < 8; bit++){
if(c & (1 << bit))
nbits++;
}
return nbits;
}
void generateTable(){
int c;
for(c=0; c < 256; c++){
nbits_table[ (unsigned char) c ] = countBits(c);
/* printf("%u=%d\n", c, nbits_table[ c & 0377 ]); */
}
}
int main(void){
int c;
unsigned long bits = 0L;
unsigned long bytes = 0L;
generateTable();
while((c = getchar()) != EOF){
bytes++;
bits += nbits_table[ (unsigned char) c ];
}
printf("%lu bajt\n", bytes);
printf("%lu edinichnyh bit\n", bits);
printf("%lu nulevyh bit\n", bytes*8 - bits);
return 0;
}
1.97. Napishite makros swap(x, y), obmenivayushchij znacheniyami dva svoih argumenta tipa
int.
#define swap(x,y) {int tmp=(x);(x)=(y);(y)=tmp;}
... swap(A, B); ...
Kak mozhno obojtis' bez vremennoj peremennoj? Vvidu nekotoroj kur'eznosti poslednego
sposoba, privodim otvet:
int x, y; /* A B */
x = x ^ y; /* A^B B */
y = x ^ y; /* A^B A */
x = x ^ y; /* B A */
Zdes' ispol'zuetsya tot fakt, chto A^A daet 0.
1.98. Napishite funkciyu swap(x, y) pri pomoshchi ukazatelej. Zamet'te, chto v otlichie ot
makrosa ee pridetsya vyzyvat' kak
A. Bogatyrev, 1992-95 - 46 - Si v UNIX
... swap(&A, &B); ...
Pochemu?
1.99. Primer ob®yasnyaet raznicu mezhdu formal'nym i fakticheskim parametrom. Termin
"formal'nyj" oznachaet, chto imya parametra mozhno proizvol'no zamenit' drugim (vo vsem
tele funkcii), t.e. samo imya ne sushchestvenno. Tak
f(x,y) { return(x + y); } i
f(muzh,zhena) { return(muzh + zhena); }
voploshchayut odnu i tu zhe funkciyu. "Fakticheskij" - oznachaet znachenie, davaemoe para-
metru v moment vyzova funkcii:
f(xyz, 43+1);
V Si eto oznachaet, chto formal'nym parametram (v kachestve lokal'nyh peremennyh) pris-
vaivayutsya nachal'nye znacheniya, ravnye znacheniyam fakticheskih parametrov:
x = xyz; y = 43 + 1; /*v tele f-cii ih mozhno menyat'*/
Pri vyhode iz funkcii formal'nye parametry (i lokal'nye peremennye) razopredelyayutsya
(i dazhe unichtozhayutsya, sm. sleduyushchij paragraf). Imena formal'nyh parametrov mogut
"perekryvat'" (delat' nevidimymi, override) odnoimennye global'nye peremennye na
vremya vypolneniya dannoj funkcii.
CHto pechataet programma?
char str[] = "stroka1";
char lin[] = "stroka2";
f(str) char str[]; /* formal'nyj parametr. */
{ printf( "%s %s\n", str, str ); }
main(){
char *s = lin;
/* fakticheskij parametr: */
f(str); /* massiv str */
f(lin); /* massiv lin */
f(s); /* peremennaya s */
f("stroka3"); /* konstanta */
f(s+2); /* znachenie vyrazheniya */
}
Obratite vnimanie, chto parametr str iz f(str) i massiv str[] - eto dve sovershenno
RAZNYE veshchi, hotya i nazyvayushchiesya odinakovo. Pereimenujte argument funkcii f i pere-
pishite ee v vide
f(ss) char ss[]; /* formal'nyj parametr. */
{ printf( "%s %s\n", ss, str ); }
CHto pechataetsya teper'? Sostav'te analogichnyj primer s celymi chislami.
1.100. Pogovorim bolee podrobno pro oblast' vidimosti imen.
int x = 12;
f(x){ int y = x*x;
if(x) f(x - 1);
}
main(){ int x=173, z=21; f(2); }
Lokal'nye peremennye i argumenty funkcii otvodyatsya v steke pri vyzove funkcii i
A. Bogatyrev, 1992-95 - 47 - Si v UNIX
unichtozhayutsya pri vyhode iz nee:
-+ +- vershina steka
|lokal y=0 |
|argument x=0 | f(0)
|---------------|---------
"kadr" |lokal y=1 |
frame |argument x=1 | f(1)
|---------------|---------
|lokal y=4 |
|argument x=2 | f(2)
|---------------|---------
|lokal z=21 |
auto: |lokal x=173 | main()
================================== dno steka
static: global x=12
==================================
Avtomaticheskie lokal'nye peremennye i argumenty funkcii vidimy tol'ko v tom vyzove
funkcii, v kotorom oni otvedeny; no ne vidimy ni v vyzyvayushchih, ni v vyzyvaemyh funk-
ciyah (t.e. vidimost' ih ogranichena ramkami svoego "kadra" steka). Staticheskie glo-
bal'nye peremennye vidimy v lyubom kadre, esli tol'ko oni ne "perekryty" (zasloneny)
odnoimennoj lokal'noj peremennoj (ili formalom) v dannom kadre.
CHto napechataet programma? Postarajtes' otvetit' na etot vopros ne vypolnyaya
programmu na mashine!
x1 x2 x3 x4 x5
int x = 12; /* x1 */ | . . . .
f(){ |___ . . .
int x = 8; /* x2, perekrytie */ : | . . .
printf( "f: x=%d\n", x ); /* x2 */ : | . . .
x++; /* x2 */ : | . . .
} :--+ . . .
g(x){ /* x3 */ :______ . .
printf( "g: x=%d\n", x ); /* x3 */ : | . .
x++; /* x3 */ : | . .
} :-----+ . .
h(){ :_________ .
int x = 4; /* x4 */ : | .
g(x); /* x4 */ : |___
{ int x = 55; } /* x5 */ : : |
printf( "h: x=%d\n", x ); /* x4 */ : |--+
} :--------+
main(){ |
f(); h(); |
printf( "main: x=%d\n", x ); /* x1 */ |
} ----
Otvet:
f: x=8
g: x=4
h: x=4
main: x=12
Obratite vnimanie na funkciyu g. Argumenty funkcii sluzhat kopiyami fakticheskih para-
metrov (t.e. yavlyayutsya lokal'nymi peremennymi funkcii, proinicializirovannymi znacheni-
yami fakticheskih parametrov), poetomu ih izmenenie ne privodit k izmeneniyu faktiches-
kogo parametra. CHtoby izmenyat' fakticheskij parametr, nado peredavat' ego adres!
A. Bogatyrev, 1992-95 - 48 - Si v UNIX
1.101. Poyasnim poslednyuyu frazu. (Vnimanie! Vozmozhno, chto dannyj punkt vam sleduet
chitat' POSLE glavy pro ukazateli). Pust' my hotim napisat' funkciyu, kotoraya obmeni-
vaet svoi argumenty x i y tak, chtoby vypolnyalos' x < y. V kachestve znacheniya funkciya
budet vydavat' (x+y)/2. Esli my napishem tak:
int msort(x, y) int x, y;
{ int tmp;
if(x > y){ tmp=x; x=y; y=tmp; }
return (x+y)/2;
}
int x=20, y=8;
main(){
msort(x,y); printf("%d %d\n", x, y); /* 20 8 */
}
to my ne dostignem zhelaemogo effekta. Zdes' perestavlyayutsya x i y, kotorye yavlyayutsya
lokal'nymi peremennymi, t.e. kopiyami fakticheskih parametrov. Poetomu vne funkcii eta
perestanovka nikak ne proyavlyaetsya!
CHtoby my mogli izmenit' argumenty, kopirovat'sya v lokal'nye peremennye dolzhny ne
sami znacheniya argumentov, a ih adresa:
int msort(xptr, yptr) int *xptr, *yptr;
{ int tmp;
if(*xptr > *yptr){tmp= *xptr;*xptr= *yptr;*yptr=tmp;}
return (*xptr + *yptr)/2;
}
int x=20, y=8, z;
main(){
z = msort(&x,&y);
printf("%d %d %d\n", x, y, z); /* 8 20 14 */
}
Obratite vnimanie, chto teper' my peredaem v funkciyu ne znacheniya x i y, a ih adresa &x
i &y.
Imenno poetomu (chtoby x smog izmenit'sya) standartnaya funkciya scanf() trebuet
ukazaniya adresov:
int x; scanf("%d", &x); /* no ne scanf("%d", x); */
Zametim, chto adres ot arifmeticheskogo vyrazheniya ili ot konstanty (a ne ot peremennoj)
vychislit' nel'zya, poetomu zakonny:
int xx=12, *xxptr = &xx, a[2] = { 13, 17 };
int *fy(){ return &y; }
msort(&x, &a[0]); msort(a+1, xxptr);
msort(fy(), xxptr);
no nezakonny
msort(&(x+1), &y); i msort(&x, &17);
Zametim eshche, chto pri rabote s adresami my mozhem napravit' ukazatel' v nevernoe mesto
i poluchit' nepredskazuemye rezul'taty:
msort(&xx - 20, a+40);
(ukazateli ukazyvayut neizvestno na chto).
Rezyume: esli argument sluzhit tol'ko dlya peredachi znacheniya V funkciyu - ego ne
nado (hotya i mozhno) delat' ukazatelem na peremennuyu, soderzhashchuyu trebuemoe znachenie
(esli tol'ko eto uzhe ne ukazatel'). Esli zhe argument sluzhit dlya peredachi znacheniya IZ
funkcii - on dolzhen byt' ukazatelem na peremennuyu vozvrashchaemogo tipa (luchshe
A. Bogatyrev, 1992-95 - 49 - Si v UNIX
vozvrashchat' znachenie kak znachenie funkcii - return-om, no inogda nado vozvrashchat' nes-
kol'ko znachenij - i etogo glavnogo "okoshka" ne hvataet).
Kontrol'nyj vopros: chto pechataet fragment?
int a=2, b=13, c;
int f(x, y, z) int x, *y, z;
{
*y += x; x *= *y; z--;
return (x + z - a);
}
main(){ c=f(a, &b, a+4); printf("%d %d %d\n",a,b,c); }
(Otvet: 2 15 33)
1.102. Formal'nye argumenty funkcii - eto takie zhe lokal'nye peremennye. Parametry
kak by opisany v samom vneshnem bloke funkcii:
char *func1(char *s){
int s; /* oshibka: povtornoe opredelenie imeni s */
...
}
int func2(int x, int y){
int z;
...
}
sootvetstvuet
int func2(){
int x = bezymyannyj_argument_1_so_steka;
int y = bezymyannyj_argument_2_so_steka;
int z;
...
}
Moral' takova: formal'nye argumenty mozhno smelo izmenyat' i ispol'zovat' kak lokal'nye
peremennye.
1.103. Vse parametry funkcii mozhno razbit' na 3 klassa:
- in - vhodnye;
- out - vyhodnye, sluzhashchie dlya vozvrata znacheniya iz funkcii; libo dlya izmeneniya
dannyh, nahodyashchihsya po etomu adresu;
- in/out - dlya peredachi znacheniya v funkciyu i iz funkcii.
Dva poslednih tipa parametrov dolzhny byt' ukazatelyami. Inogda (osobenno v prototipah
i v dokumentacii) byvaet polezno ukazyvat' klass parametra v vide kommentariya:
int f( /*IN*/ int x,
/*OUT*/ int *yp,
/*INOUT*/ int *zp){
*yp = ++x + ++(*zp);
return (*zp *= x) - 1;
}
int x=2, y=3, z=4, res;
main(){ res = f(x, &y, &z);
printf("res=%d x=%d y=%d z=%d\n",res,x,y,z);
/* 14 2 8 15 */
}
|to polezno potomu, chto inogda trudno ponyat' - zachem parametr opisan kak ukazatel'.
To li po nemu vydaetsya iz funkcii informaciya, to li eto prosto ukazatel' na dannye
(massiv), peredavaemye v funkciyu. V pervom sluchae ukazuemye dannye budut izmeneny, a
vo vtorom - net. V pervom sluchae ukazatel' dolzhen ukazyvat' na zarezervirovannuyu nami
A. Bogatyrev, 1992-95 - 50 - Si v UNIX
oblast' pamyati, v kotoroj budet razmeshchen rezul'tat. Primer na etu temu est' v glave
"Tekstovaya obrabotka" (funkciya bi_conv).
1.104. Izvesten takoj stil' oformleniya argumentov funkcii:
void func( int arg1
, char *arg2 /* argument 2 */
, char *arg3[]
, time_t time_stamp
){ ... }
Sut' ego v tom, chto zapyatye pishutsya v stolbik i v odnu liniyu s ( i ) skobkami dlya
argumentov. Pri takom stile legche dobavlyat' i udalyat' argumenty, chem pri versii s
zapyatoj v konce. |tot zhe stil' primenim, naprimer, k perechislimym tipam:
enum { red
, green
, blue
};
Napishite programmu, formatiruyushchuyu zagolovki funkcij takim obrazom.
1.105. V chem oshibka?
char *val(int x){
char str[20];
sprintf(str, "%d", x);
return str;
}
void main(){
int x = 5; char *s = val(x);
printf("The values:\n");
printf("%d %s\n", x, s);
}
Otvet: val vozvrashchaet ukazatel' na avtomaticheskuyu peremennuyu. Pri vyhode iz funkcii
val() ee lokal'nye peremennye (v chastnosti str[]) v steke unichtozhayutsya - ukazatel' s
teper' ukazyvaet na isporchennye dannye! Vozmozhnym resheniem problemy yavlyaetsya prevra-
shchenie str[] v staticheskuyu peremennuyu (hranimuyu ne v steke):
static char str[20];
Odnako takoj sposob ne pozvolit pisat' konstrukcii vida
printf("%s %s\n", val(1), val(2));
tak kak pod oba vyzova val() ispol'zuetsya odin i tot zhe bufer str[] i budet pecha-
tat'sya "1 1" libo "2 2", no ne "1 2". Bolee pravil'nym budet zadanie bufera dlya
rezul'tata val() kak argumenta:
char *val(int x, char str[]){
sprintf(str, "%d", x);
return str;
}
void main(){
int x=5, y=7;
char s1[20], s2[20];
printf("%s %s\n", val(x, s1), val(y, s2));
}
A. Bogatyrev, 1992-95 - 51 - Si v UNIX
1.106. Kakovy oshibki (ne sintaksicheskie) v programme|-?
main() {
double y; int x = 12;
y = sin (x);
printf ("%s\n", y);
}
Otvet:
- standartnaya bibliotechnaya funkciya sin() vozvrashchaet znachenie tipa double, no my
nigde ne informiruem ob etom kompilyator. Poetomu on schitaet po umolchaniyu, chto
eta funkciya vozvrashchaet znachenie tipa int i delaet v prisvaivanii y=sin(x) prive-
denie tipa int k tipu levogo operanda, t.e. k double. V rezul'tate vozvrashchaemoe
znachenie (a ono na samom dele - double) interpretiruetsya neverno (kak int), pod-
vergaetsya privedeniyu tipa (kotoroe portit ego), i rezul'tat poluchaetsya sover-
shenno ne takim, kak nado. Podobnaya zhe oshibka voznikaet pri ispol'zovanii funk-
cij, vozvrashchayushchih ukazatel', naprimer, funkcij malloc() i itoa(). Poetomu esli
my pol'zuemsya bibliotechnoj funkciej, vozvrashchayushchej ne int, my dolzhny predvari-
tel'no (do pervogo ispol'zovaniya) opisat' ee, naprimer|=:
extern double sin();
extern long atol();
extern char *malloc(), *itoa();
|to zhe otnositsya i k nashim sobstvennym funkciyam, kotorye my ispol'zuem prezhde,
chem opredelyaem (poskol'ku iz zagolovka funkcii kompilyator obnaruzhit, chto ona
vydaet ne celoe znachenie, uzhe posle togo, kak stransliruet obrashchenie k nej):
/*extern*/ char *f();
main(){
char *s;
s = f(1); puts(s);
}
char *f(n){ return "knights" + n; }
Funkcii, vozvrashchayushchie celoe, opisyvat' ne trebuetsya. Opisaniya dlya nekotoryh
standartnyh funkcij uzhe pomeshcheny v sistemnye include-fajly. Naprimer, opisaniya
dlya matematicheskih funkcij (sin, cos, fabs, ...) soderzhatsya v fajle
/usr/include/math.h. Poetomu my mogli by napisat' pered main
#include <math.h>
vmesto
extern double sin(), cos(), fabs();
- bibliotechnaya funkciya sin() trebuet argumenta tipa double, my zhe peredaem ej
argument tipa int (kotoryj koroche tipa double i imeet inoe vnutrennee predstav-
lenie). On budet nepravil'no prointerpretirovan funkciej, t.e. my vychislim
sinus otnyud' NE chisla 12. Sleduet pisat':
y = sin( (double) x );
i sin(12.0); vmesto sin(12);
____________________
|- Dlya translyacii programmy, ispol'zuyushchej standartnye matematicheskie funkcii sin,
cos, exp, log, sqrt, i.t.p. sleduet zadavat' klyuch kompilyatora -lm
cc file.c -o file -lm
|= Slovo extern ("vneshnyaya") ne yavlyaetsya obyazatel'nym, no yavlyaetsya priznakom horo-
shego tona - vy soobshchaete programmistu, chitayushchemu etu programmu, chto dannaya funkciya
realizovana v drugom fajle, libo voobshche yavlyaetsya standartnoj i beretsya iz biblioteki.
A. Bogatyrev, 1992-95 - 52 - Si v UNIX
- v printf my pechataem znachenie tipa double po nepravil'nomu formatu: sleduet
ispol'zovat' format %g ili %f (a dlya vvoda pri pomoshchi scanf() - %lf). Ochen'
chastoj oshibkoj yavlyaetsya pechat' znachenij tipa long po formatu %d vmesto %ld .
Pervyh dvuh problem v sovremennom Si udaetsya izbezhat' blagodarya zadaniyu prototipov
funkcij (o nih podrobno rasskazano nizhe, v konce glavy "Tekstovaya obrabotka"). Nap-
rimer, sin imeet prototip
double sin(double x);
Tretyaya problema (oshibka v formate) ne mozhet byt' lokalizovana sredstvami Si i imeet
bolee-menee priemlemoe reshenie lish' v yazyke C++ (streams).
1.107. Najdite oshibku:
int sum(x,y,z){ return(x+y+z); }
main(){
int s = sum(12,15);
printf("%d\n", s);
}
Zametim, chto esli by dlya funkcii sum() byl zadan prototip, to kompilyator pojmal by
etu nashu oploshnost'! Zamet'te, chto sejchas znachenie z v sum() nepredskazuemo. Esli by
my vyzyvali
s = sum(12,15,17,24);
to lishnie argumenty byli by prosto proignorirovany (no i tut mozhet byt' syurpriz -
argumenty mogli by ignorirovat'sya s LEVOGO konca spiska!).
A vot primer opasnoj oshibki, kotoraya ne lovitsya dazhe prototipami:
int x; scanf("%d%d", &x );
Vtoroe chislo po formatu %d budet schitano neizvestno po kakomu adresu i razrushit
pamyat' programmy. Ni odin kompilyator ne proveryaet sootvetstvie chisla %-ov v stroke
formata chislu argumentov scanf i printf.
1.108. CHto zdes' oznachayut vnutrennie (,,) v vyzove funkcii f() ?
f(x, y, z){
printf("%d %d %d\n", x, y, z);
}
main(){ int t;
f(1, (2, 3, 4), 5);
f(1, (t=3,t+1), 5);
}
Otvet: (2,3,4) - eto operator "zapyataya", vydayushchij znachenie poslednego vyrazheniya iz
spiska perechislennyh cherez zapyatuyu vyrazhenij. Zdes' budet napechatano 1 4 5. Kazhushchayasya
dvojstvennost' voznikaet iz-za togo, chto argumenty funkcii tozhe perechislyayutsya cherez
zapyatuyu, no eto sovsem drugaya sintaksicheskaya konstrukciya. Vot eshche primer:
int y = 2, x;
x = (y+4, y, y*2); printf("%d\n", x); /* 4 */
x = y+4, y, y*2 ; printf("%d\n", x); /* 6 */
x = (x=y+4, ++y, x*y); printf("%d\n", x); /* 18 */
Snachala obratim vnimanie na pervuyu stroku. |to - ob®yavlenie peremennyh x i y (prichem
y - s inicializaciej), poetomu zapyataya zdes' - ne OPERATOR, a prosto razdelitel'
ob®yavlyaemyh peremennyh! Dalee sleduyut tri stroki vypolnyaemyh operatorov. V pervom
sluchae vypolnilos' x=y*2; vo vtorom x=y+4 (t.k. prioritet u prisvaivaniya vyshe, chem u
A. Bogatyrev, 1992-95 - 53 - Si v UNIX
zapyatoj). Obratite vnimanie, chto vyrazhenie bez prisvaivaniya (kotoroe mozhet voobshche ne
imet' effekta ili imet' tol'ko pobochnyj effekt) vpolne zakonno:
x+y; ili z++; ili x == y+1; ili x;
V chastnosti, vse vyzovy funkcij-procedur imenno takovy (eto vyrazheniya bez operatora
prisvaivaniya, imeyushchie pobochnyj effekt):
f(12,x); putchar('Y');
v otlichie, skazhem, ot x=cos(0.5)/3.0; ili c=getchar();
Operator "zapyataya" razdelyaet vyrazheniya, a ne prosto operatory, poetomu esli hot'
odin iz perechislennyh operatorov ne vydaet znacheniya, to eto yavlyaetsya oshibkoj:
main(){ int i, x = 0;
for(i=1; i < 4; i++)
x++, if(x > 2) x = 2; /* ispol'zuj { ; } */
}
operator if ne vydaet znacheniya. Takzhe logicheski oshibochno ispol'zovanie funkcii tipa
void (ne vozvrashchayushchej znacheniya):
void f(){}
...
for(i=1; i < 4; i++)
x++, f();
hotya kompilyator mozhet dopustit' takoe ispol'zovanie.
Vot eshche odin primer togo, kak mozhno perepisat' odin i tot zhe fragment, primenyaya
raznye sintaksicheskie konstrukcii:
if( uslovie ) { x = 0; y = 0; }
if( uslovie ) x = 0, y = 0;
if( uslovie ) x = y = 0;
1.109. Najdite opechatku:
switch(c){
case 1:
x++; break;
case 2:
y++; break;
defalt:
z++; break;
}
Esli c=3, to z++ ne proishodit. Pochemu? (Potomu, chto defalt: - eto metka, a ne klyu-
chevoe slovo default).
1.110. Pochemu programma zaciklivaetsya i pechataet sovsem ne to, chto nazhato na klavia-
ture, a tol'ko 0 i 1?
while ( c = getchar() != 'e')
printf("%d %c\n, c, c);
Otvet: dannyj fragment dolzhen byl vyglyadet' tak:
while ((c = getchar()) != 'e')
printf("%d %c\n, c, c);
A. Bogatyrev, 1992-95 - 54 - Si v UNIX
Sravnenie v Si imeet vysshij prioritet, nezheli prisvaivanie! Moral': nado byt' vnima-
tel'nee k prioritetam operacij. Eshche odin primer na pohozhuyu temu:
vmesto
if( x & 01 == 0 ) ... if( c&0377 > 0300)...;
nado:
if( (x & 01) == 0 ) ... if((c&0377) > 0300)...;
I eshche primer s analogichnoj oshibkoj:
FILE *fp;
if( fp = fopen( "fajl", "w" ) == NULL ){
fprintf( stderr, "ne mogu pisat' v fajl\n");
exit(1);
}
fprintf(fp,"Good bye, %s world\n","cruel"); fclose(fp);
V etom primere fajl otkryvaetsya, no fp ravno 0 (logicheskoe znachenie!) i funkciya
fprintf() ne srabatyvaet (programma padaet po zashchite pamyati|-).
Isprav'te analogichnuyu oshibku (na prioritet operacij) v sleduyushchej funkcii:
/* kopirovanie stroki from v to */
char *strcpy( to, from ) register char *from, *to;
{
char *p = to;
while( *to++ = *from++ != '\0' );
return p;
}
1.111. Sravneniya s nulem (0, NULL, '\0') v Si prinyato opuskat' (hotya eto ne vsegda
sposobstvuet yasnosti).
if( i == 0 ) ...; --> if( !i ) ... ;
if( i != 0 ) ...; --> if( i ) ... ;
naprimer, vmesto
char s[20], *p ;
for(p=s; *p != '\0'; p++ ) ... ;
budet
for(p=s; *p; p++ ) ... ;
i vmesto
char s[81], *gets();
while( gets(s) != NULL ) ... ;
budet
while( gets(s)) ... ;
Perepishite strcpy v etom bolee lakonichnom stile.
____________________
|- "Padat'" - programmistskij zhargon. Oznachaet "avarijno zavershat'sya". "Zashchita pa-
myati" - obrashchenie po nekorrektnomu adresu. V UNIX takaya oshibka lovitsya apparatno, i
programma budet ubita odnim iz signalov: SIGBUS, SIGSEGV, SIGILL. Sistema soobshchit
nechto vrode "oshibka shiny". Znajte, chto eto ne oshibka apparatury i ne sboj, a VASHA
oshibka!
A. Bogatyrev, 1992-95 - 55 - Si v UNIX
1.112. Istinno li vyrazhenie
if( 2 < 5 < 4 )
Otvet: da! Delo v tom, chto Si ne imeet logicheskogo tipa, a vmesto "istina" i "lozh'"
ispol'zuet celye znacheniya "ne 0" i "0" (logicheskie operacii vydayut 1 i 0). Dannoe
vyrazhenie v uslovii if ekvivalentno sleduyushchemu:
((2 < 5) < 4)
Znacheniem (2 < 5) budet 1. Znacheniem (1 < 4) budet tozhe 1 (istina). Takim obrazom my
poluchaem sovsem ne to, chto ozhidalos'. Poetomu vmesto
if( a < x < b )
nado pisat'
if( a < x && x < b )
1.113. Dannaya programma dolzhna pechatat' kody vvodimyh simvolov. Najdite opechatku;
pochemu cikl srazu zavershaetsya?
int c;
for(;;) {
printf("Vvedite ocherednoj simvol:");
c = getchar();
if(c = 'e') {
printf("nazhato e, konec\n"); break;
}
printf( "Kod %03o\n", c & 0377 );
}
Otvet: v if imeetsya opechatka: ispol'zovano `=' vmesto `=='.
Prisvaivanie v Si (a takzhe operacii +=, -=, *=, i.t.p.) vydaet novoe znachenie
levoj chasti, poetomu sintaksicheskoj oshibki zdes' net! Napisannyj operator ravnosilen
c = 'e'; if( c ) ... ;
i, poskol'ku 'e'!= 0, to uslovie okazyvaetsya istinnym! |to eshche i sledstvie togo, chto
v Si net special'nogo logicheskogo tipa (istina/lozh'). Bud'te vnimatel'ny: kompilyator
ne schitaet oshibkoj ispol'zovanie operatora = vmesto == vnutri uslovij if i uslovij
ciklov (hotya nekotorye kompilyatory vydayut preduprezhdenie).
Eshche analogichnaya oshibka:
for( i=0; !(i = 15) ; i++ ) ... ;
(cikl ne vypolnyaetsya); ili
static char s[20] = " abc"; int i=0;
while(s[i] = ' ') i++;
printf("%s\n", &s[i]); /* dolzhno napechatat'sya abc */
(stroka zapolnyaetsya probelami i cikl ne konchaetsya).
To, chto operator prisvaivaniya imeet znachenie, ves'ma udobno:
int x, y, z; eto na samom dele
x = y = z = 1; x = (y = (z = 1));
A. Bogatyrev, 1992-95 - 56 - Si v UNIX
ili|-
y=f( x += 2 ); // vmesto x+=2; y=f(x);
if((y /= 2) > 0)...; // vmesto y/=2; if(y>0)...;
Vot primer uproshchennoj igry v "ochko" (uproshchennoj - t.k. ne uchityvaetsya ogranichennost'
chisla kart kazhdogo tipa v kolode (po 4 shtuki)):
#include <stdio.h>
main(){
int sum = 0, card; char answer[36];
srand( getpid()); /* randomizaciya */
do{ printf( "U vas %d ochkov. Eshche? ", sum);
if( *gets(answer) == 'n' ) break;
/* inache malovato budet */
printf( " %d ochkov\n",
card = 6 + rand() % (11 - 6 + 1));
} while((sum += card) < 21); /* SIC ! */
printf ( sum == 21 ? "ochko\n" :
sum > 21 ? "perebor\n":
"%d ochkov\n", sum);
}
Vot eshche primer, ispol'zuyushchijsya dlya podscheta pravil'nogo razmera tablicy. Obratite
vnimanie, chto prisvaivaniya ispol'zuyutsya v sravneniyah, v argumentah vyzova funkcii
(printf), t.e. vezde, gde dopustimo vyrazhenie:
#include <stdio.h>
int width = 20; /* nachal'noe znachenie shiriny polya */
int len; char str[512];
main(){
while(gets(str)){
if((len = strlen(str)) > width){
fprintf(stderr,"width uvelichit' do %d\n", width=len);
}
printf("|%*.*s|\n", -width, width, str);
}
}
Vyzyvaj etu programmu kak
a.out < vhodnojFajl > /dev/null
1.114. Pochemu programma "zavisaet" (na samom dele - zaciklivaetsya) ?
int x = 0;
while( x < 100 );
printf( "%d\n", x++ );
printf( "VSE\n" );
Ukazanie: gde konchaetsya cikl while?
Moral': ne nado stavit' ; gde popalo. Eshche moral': dazhe otstupy v oformlenii
programmy ne yavlyayutsya garantiej otsutstviya oshibok v gruppirovke operatorov.
1.115. Voobshche, prioritety operacij v Si chasto ne sootvetstvuyut ozhidaniyam nashego
zdravogo smysla. Naprimer, znacheniem vyrazheniya:
x = 1 << 2 + 1 ;
____________________
|- Konstrukciya //tekst, kotoraya budet izredka popadat'sya v dal'nejshem - eto kommen-
tarij v stile yazyka C++. Takoj kommentarij prostiraetsya ot simvola // do konca
stroki.
A. Bogatyrev, 1992-95 - 57 - Si v UNIX
budet 8, a ne 5, poskol'ku slozhenie vypolnitsya pervym. Moral': v zatrudnitel'nyh i
neochevidnyh sluchayah luchshe yavno ukazyvat' prioritety pri pomoshchi kruglyh skobok:
x = (1 << 2) + 1 ;
Eshche primer: uvelichivat' x na 40, esli ustanovlen flag, inache na 1:
int bigFlag = 1, x = 2;
x = x + bigFlag ? 40 : 1;
printf( "%d\n", x );
otvetom budet 40, a ne 42, poskol'ku eto
x = (x + bigFlag) ? 40 : 1;
a ne
x = x + (bigFlag ? 40 : 1);
kotoroe my imeli v vidu. Poetomu vokrug uslovnogo vyrazheniya ?: obychno pishut kruglye
skobki.
Zametim, chto () ukazyvayut tol'ko prioritet, no ne poryadok vychislenij. Tak, kom-
pilyator imeet polnoe pravo vychislit'
long a = 50, x; int b = 4;
x = (a * 100) / b;
/* delenie celochislennoe s ostatkom ! */
i kak x = (a * 100)/b = 5000/4 = 1250
i kak x = (a/b) * 100 = 12*100 = 1200
nevziraya na nashi skobki, poskol'ku i * i / imeyut odinakovyj prioritet (hotya eto
"pravo" eshche ne oznachaet, chto on obyazatel'no tak postupit). Takie operatory priho-
ditsya razbivat' na dva, t.e. vvodit' promezhutochnuyu peremennuyu:
{ long a100 = a * 100; x = a100 / b; }
1.116. Sostav'te programmu vychisleniya trigonometricheskoj funkcii. Nazvanie funkcii
i znachenie argumenta peredayutsya v kachestve parametrov funkcii main (sm. pro argv i
argc v glave "Vzaimodejstvie s UNIX"):
$ a.out sin 0.5
sin(0.5)=0.479426
(zdes' i dalee znachok $ oboznachaet priglashenie, vydannoe interpretatorom komand).
Dlya preobrazovaniya stroki v znachenie tipa double vospol'zujtes' standartnoj funkciej
atof().
char *str1, *str2, *str3; ...
extern double at