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