efghijklmn... D #00 posle S ...abcdefEhijklmn... D #01 pered S ...abcdefEhijklmn... D #01 posle S ...abcdeDEhijklmn... D #02 pered S ...abcdeDEhijklmn... D #02 posle S ...abcdCDEhijklmn... D #03 pered S ...abcdCDEhijklmn... D #03 posle S ...abcBCDEhijklmn... D #04 pered S ...abcBCDEhijklmn... D #04 posle S ...abABCDEhijklmn... D Teper' bcopy() - udobnaya funkciya dlya kopirovaniya i sdviga massivov, v chastnosti mas- sivov ukazatelej. Pust' u nas est' massiv strok (vydelennyh malloc-om): char *lines[NLINES]; Togda ciklicheskaya perestanovka strok vyglyadit tak: A. Bogatyrev, 1992-95 - 111 - Si v UNIX void scrollUp(){ char *save = lines[0]; bcopy((char *) lines+1, /* from */ (char *) lines, /* to */ sizeof(char *) * (NLINES-1)); lines[NLINES-1] = save; } void scrollDown(){ char *save = lines[NLINES-1]; bcopy((char *) &lines[0], /* from */ (char *) &lines[1], /* to */ sizeof(char *) * (NLINES-1)); lines[0] = save; } Vozmozhno, chto napisanie po analogii funkcii dlya kopirovaniya massivov elementov tipa (void *) - obobshchennyh ukazatelej - mozhet okazat'sya eshche ponyatnee i effektivnee. Takaya funkciya - memmove - standartno sushchestvuet v UNIX SVR4. Zamet'te, chto poryadok argu- mentov v nej obratnyj po otnosheniyu k bcopy. Sleduet otmetit', chto v SVR4 vse funkcii mem... imeyut ukazateli tipa (void *) i schetchik tipa size_t - tip dlya kolichestva bajt (vmesto unsigned long); v chastnosti dlina fajla imeet imenno etot tip (smotri sistem- nye vyzovy lseek i stat). #include <sys/types.h> void memmove(void *Dst, const void *Src, register size_t n){ register caddr_t src = (caddr_t) Src, dst = (caddr_t) Dst; if(dst==src || n <= 0) return; if(src < dst && dst < src + n) { dst += n-1; src += n-1; while(--n >= 0) *dst-- = *src--; }else memcpy(dst, src, n); } caddr_t - eto tip dlya ukazatelej na BAJT, fakticheski eto (unsigned char *). Zachem voobshche ponadobilos' ispol'zovat' caddr_t? Zatem, chto dlya void *pointer; int n; znachenie pointer + n ne opredeleno i nevychislimo, ibo sizeof(void) ne imeet smysla - eto ne 0, a prosto oshibka, diagnostiruemaya kompilyatorom! 2.59. Eshche ob opechatkah: vot chto byvaet, kogda vmesto znaka `=' pechataetsya `-' (na klaviature oni nahodyatsya ryadom...). A. Bogatyrev, 1992-95 - 112 - Si v UNIX #include <stdio.h> #include <strings.h> char *strdup(const char *s){ extern void *malloc(); return strcpy((char *)malloc(strlen(s)+1), s); } char *ptr; void main(int ac, char *av[]){ ptr - strdup("hello"); /* podrazumevalos' ptr = ... */ *ptr = 'H'; printf("%s\n", ptr); free(ptr); exit(0); } Delo v tom, chto zapis' (a chasto i chtenie) po *pointer, gde pointer==NULL, privodit k avarijnomu prekrashcheniyu programmy. V nashej programme ptr ostalos' ravnym NULL - ukaza- telem v nikuda. V operacionnoj sisteme UNIX na mashinah s apparatnoj zashchitoj pamyati, stranica pamyati, soderzhashchaya adres NULL (0) byvaet zakryta na zapis', poetomu lyuboe obrashchenie po zapisi v etu stranicu vyzyvaet preryvanie ot dispetchera pamyati i avarij- noe prekrashchenie processa. Sistema sama pomogaet lovit' vashi oshibki (no uzhe vo vremya vypolneniya programmy). |to OCHENX chastaya oshibka - zapis' po adresu NULL. MS DOS v takih sluchayah predpochitaet prosto zavisnut', i vy byvaete vynuzhdeny igrat' akkord iz treh klavish - Ctrl/Alt/Del, tak i ne ponyav v chem delo. 2.60. Raz uzh rech' zashla o funkcii strdup (kstati, eto standartnaya funkciya), privedem eshche odnu funkciyu dlya sohraneniya strok. char *savefromto(register char *from, char *upto) { char *ptr, *s; if((ptr = (char *) malloc(upto - from + 1)) == NULL) return NULL; for(s = ptr; from < upto; from++) *s++ = *from; *s = '\0'; return ptr; } Sam simvol (*upto) ne sohranyaetsya, a zamenyaetsya na '\0'. 2.61. Uproshchennyj analog funkcii printf. A. Bogatyrev, 1992-95 - 113 - Si v UNIX /* * Mashinno - nezavisimyj printf() (uproshchennyj variant). * printf - Formatnyj Vyvod. */ #include <stdio.h> #include <ctype.h> #include <varargs.h> #include <errno.h> #include <string.h> extern int errno; /* kod sistemnoj oshibki, format %m */ /* chtenie znacheniya chisla */ #define GETN(n,fmt) \ n = 0; \ while(isdigit(*fmt)){ \ n = n*10 + (*fmt - '0'); \ fmt++; \ } void myprintf(fmt, va_alist) register char *fmt; va_dcl { va_list ap; char c, *s; int i; int width, /* minimal'naya shirina polya */ prec, /* maks. dlina dannogo */ sign, /* vyravnivanie: 1 - vpravo, -1 - vlevo */ zero, /* shirina polya nachinaetsya s 0 */ glong; /* trebuetsya dlinnoe celoe */ va_start(ap); for(;;){ while((c = *fmt++) != '%'){ if( c == '\0' ) goto out; putchar(c); } sign = 1; zero = 0; glong = 0; if(*fmt == '-'){ sign = (-1); fmt++; } if(*fmt == '0'){ zero = 1; fmt++; } if(*fmt == '*'){ width = va_arg(ap, int); if(width < 0){ width = -width; sign = -sign; } fmt++; }else{ GETN(width, fmt); } width *= sign; if(*fmt == '.'){ if(*++fmt == '*'){ prec = va_arg(ap, int); fmt++; }else{ GETN(prec, fmt); } }else prec = (-1); /* proizvol'no */ if( *fmt == 'l' ){ glong = 1; fmt++; } A. Bogatyrev, 1992-95 - 114 - Si v UNIX switch(c = *fmt++){ case 'c': putchar(va_arg(ap, int)); break; case 's': prStr(width, prec, va_arg(ap, char *)); break; case 'm': prStr(width, prec, strerror(errno)); break; /* strerror preobrazuet kod oshibki v stroku-rasshifrovku */ case 'u': prUnsigned(width, glong ? va_arg(ap, unsigned long) : (unsigned long) va_arg(ap, unsigned int), 10 /* base */, zero); break; case 'd': prInteger(width, glong ? va_arg(ap, long) : (long) va_arg(ap, int), 10 /* base */, zero); break; case 'o': prUnsigned(width, glong ? va_arg(ap, unsigned long) : (unsigned long) va_arg(ap, unsigned int), 8 /* base */, zero); break; case 'x': prUnsigned(width, glong ? va_arg(ap, unsigned long) : (unsigned long) va_arg(ap, unsigned int), 16 /* base */, zero); break; case 'X': prUnsigned(width, glong ? va_arg(ap, unsigned long) : (unsigned long) va_arg(ap, unsigned int), -16 /* base */, zero); break; case 'b': prUnsigned(width, glong ? va_arg(ap, unsigned long) : (unsigned long) va_arg(ap, unsigned int), 2 /* base */, zero); break; case 'a': /* address */ prUnsigned(width, (long) (char *) va_arg(ap, char *), 16 /* base */, zero); break; case 'A': /* address */ prUnsigned(width, (long) (char *) va_arg(ap, char *), -16 /* base */, zero); break; case 'r': prRoman(width, prec, va_arg(ap, int)); break; case '%': putchar('%'); break; default: putchar(c); break; } } out: va_end(ap); } A. Bogatyrev, 1992-95 - 115 - Si v UNIX /* --------------------------------------------------------- */ int strnlen(s, maxlen) char *s; { register n; for( n=0; *s && n < maxlen; n++, s++ ); return n; } /* Pechat' stroki */ static prStr(width, prec, s) char *s; { int ln; /* skol'ko simvolov vyvodit' */ int toLeft = 0; /* k kakomu krayu prizhimat' */ if(s == NULL){ pr( "(NULL)", 6); return; } /* Izmerit' dlinu i obrubit' dlinnuyu stroku. * Delo v tom, chto stroka mozhet ne imet' \0 na konce, togda * strlen(s) mozhet privesti k obrashcheniyu v zapreshchennye adresa */ ln = (prec > 0 ? strnlen(s, prec) : strlen(s)); /* shirina polya */ if( ! width ) width = (prec > 0 ? prec : ln); if( width < 0){ width = -width; toLeft = 1; } if( width > ln){ /* dopolnit' pole probelami */ if(toLeft){ pr(s, ln); prSpace(width - ln, ' '); } else { prSpace(width - ln, ' '); pr(s, ln); } } else { pr(s, ln); } } /* Pechat' stroki dlinoj l */ static pr(s, ln) register char *s; register ln; { for( ; ln > 0 ; ln-- ) putchar( *s++ ); } /* Pechat' n simvolov c */ static prSpace(n, c) register n; char c;{ for( ; n > 0 ; n-- ) putchar( c ); } /* --------------------------------------------------------- */ static char *ds; /* Rimskie cifry */ static prRoman(w,p,n){ char bd[60]; ds = bd; if( n < 0 ){ n = -n; *ds++ = '-'; } prRdig(n,6); *ds = '\0'; prStr(w, p, bd); } A. Bogatyrev, 1992-95 - 116 - Si v UNIX static prRdig(n, d){ if( !n ) return; if( d ) prRdig( n/10, d - 2); tack(n%10, d); } static tack(n, d){ static char im[] = " MDCLXVI"; /* ..1000 500 100 50 10 5 1 */ if( !n ) return; if( 1 <= n && n <= 3 ){ repeat(n, im[d+2]); return; } if( n == 4 ) *ds++ = im[d+2]; if( n == 4 || n == 5 ){ *ds++ = im[d+1]; return; } if( 6 <= n && n <= 8 ){ *ds++ = im[d+1]; repeat(n - 5, im[d+2] ); return; } /* n == 9 */ *ds++ = im[d+2]; *ds++ = im[d]; } static repeat(n, c) char c; { while( n-- > 0 ) *ds++ = c; } /* --------------------------------------------------------- */ static char aChar = 'A'; static prInteger(w, n, base, zero) long n; { /* preobrazuem chislo v stroku */ char bd[128]; int neg = 0; /* < 0 */ if( n < 0 ){ neg = 1; n = -n; } if( base < 0 ){ base = -base; aChar = 'A'; } else { aChar = 'a'; } ds = bd; prUDig( n, base ); *ds = '\0'; /* Teper' pechataem stroku */ prIntStr( bd, w, zero, neg ); } A. Bogatyrev, 1992-95 - 117 - Si v UNIX static prUnsigned(w, n, base, zero) unsigned long n; { char bd[128]; if( base < 0 ){ base = -base; aChar = 'A'; } else { aChar = 'a'; } ds = bd; prUDig( n, base ); *ds = '\0'; /* Teper' pechataem stroku */ prIntStr( bd, w, zero, 0 ); } static prUDig( n, base ) unsigned long n; { unsigned long aSign; if((aSign = n/base ) > 0 ) prUDig( aSign, base ); aSign = n % base; *ds++ = (aSign < 10 ? '0' + aSign : aChar + (aSign - 10)); } static prIntStr( s, width, zero, neg ) char *s; { int ln; /* skol'ko simvolov vyvodit' */ int toLeft = 0; /* k kakomu krayu prizhimat' */ ln = strlen(s); /* dlina stroki s */ /* SHirina polya: vychislit', esli ne ukazano yavno */ if( ! width ){ width = ln; /* shirina polya */ if( neg ) width++; /* 1 simvol dlya minusa */ } if( width < 0 ){ width = -width; toLeft = 1; } if( ! neg ){ /* Polozhitel'noe chislo */ if(width > ln){ if(toLeft){ pr(s, ln); prSpace(width - ln, ' '); } else { prSpace(width - ln, zero ? '0' : ' '); pr(s, ln); } } else { pr(s, ln); } }else{ /* Otricatel'noe chislo */ if(width > ln){ /* Nado zapolnyat' ostavshuyusya chast' polya */ width -- ; /* width soderzhit odnu poziciyu dlya minusa */ if(toLeft){ putchar('-'); pr(s, ln); prSpace(width - ln, ' '); } else{ if( ! zero ){ prSpace(width - ln, ' '); putchar('-'); pr(s,ln); } else { putchar('-'); prSpace(width - ln, '0'); pr(s, ln); } } } else { putchar('-'); pr(s, ln); } } } A. Bogatyrev, 1992-95 - 118 - Si v UNIX /* --------------------------------------------------------- */ main(){ int i, n; static char s[] = "Hello, world!\n"; static char p[] = "Hello, world"; long t = 7654321L; myprintf( "%%abc%Y\n"); myprintf( "%s\n", "abs" ); myprintf( "%5s|\n", "abs" ); myprintf( "%-5s|\n", "abs" ); myprintf( "%5s|\n", "xyzXYZ" ); myprintf( "%-5s|\n", "xyzXYZ" ); myprintf( "%5.5s|\n", "xyzXYZ" ); myprintf( "%-5.5s|\n", "xyzXYZ" ); myprintf( "%r\n", 444 ); myprintf( "%r\n", 999 ); myprintf( "%r\n", 16 ); myprintf( "%r\n", 18 ); myprintf( "%r\n", 479 ); myprintf( "%d\n", 1234 ); myprintf( "%d\n", -1234 ); myprintf( "%ld\n", 97487483 ); myprintf( "%2d|%2d|\n", 1, -3 ); myprintf( "%-2d|%-2d|\n", 1, -3 ); myprintf( "%02d|%2d|\n", 1, -3 ); myprintf( "%-02d|%-2d|\n", 1, -3 ); myprintf( "%5d|\n", -12 ); myprintf( "%05d|\n", -12 ); myprintf( "%-5d|\n", -12 ); myprintf( "%-05d|\n", -12 ); for( i = -6; i < 6; i++ ) myprintf( "width=%2d|%0*d|%0*d|%*d|%*d|\n", i, i, 123, i, -123, i, 123, i, -123); myprintf( "%s at location %a\n", s, s ); myprintf( "%ld\n", t ); n = 1; t = 1L; for( i=0; i < 34; i++ ){ myprintf( "for %2d |%016b|%d|%u|\n\t |%032lb|%ld|%lu|\n", i, n, n, n, t, t, t ); n *= 2; t *= 2; } myprintf( "%8x %8X\n", 7777, 7777 ); myprintf( "|%s|\n", p ); myprintf( "|%10s|\n", p ); myprintf( "|%-10s|\n", p ); myprintf( "|%20s|\n", p ); myprintf( "|%-20s|\n", p ); myprintf( "|%20.10s|\n", p ); myprintf( "|%-20.10s|\n", p ); myprintf( "|%.10s|\n", p ); } A. Bogatyrev, 1992-95 - 119 - Si v UNIX Vydacha etoj programmy: %abcY abs abs| abs | xyzXYZ| xyzXYZ| xyzXY| xyzXY| CDXLIV CMXCIX XVI XVIII CDLXXIX 1234 -1234 97487483 1|-3| 1 |-3| 01|-3| 1 |-3| -12| -0012| -12 | -12 | width=-6|123 |-123 |123 |-123 | width=-5|123 |-123 |123 |-123 | width=-4|123 |-123|123 |-123| width=-3|123|-123|123|-123| width=-2|123|-123|123|-123| width=-1|123|-123|123|-123| width= 0|123|-123|123|-123| width= 1|123|-123|123|-123| width= 2|123|-123|123|-123| width= 3|123|-123|123|-123| width= 4|0123|-123| 123|-123| width= 5|00123|-0123| 123| -123| Hello, world! at location 400980 7654321 for 0 |0000000000000001|1|1| |00000000000000000000000000000001|1|1| for 1 |0000000000000010|2|2| |00000000000000000000000000000010|2|2| for 2 |0000000000000100|4|4| |00000000000000000000000000000100|4|4| for 3 |0000000000001000|8|8| |00000000000000000000000000001000|8|8| for 4 |0000000000010000|16|16| |00000000000000000000000000010000|16|16| for 5 |0000000000100000|32|32| |00000000000000000000000000100000|32|32| for 6 |0000000001000000|64|64| |00000000000000000000000001000000|64|64| for 7 |0000000010000000|128|128| |00000000000000000000000010000000|128|128| for 8 |0000000100000000|256|256| |00000000000000000000000100000000|256|256| for 9 |0000001000000000|512|512| |00000000000000000000001000000000|512|512| for 10 |0000010000000000|1024|1024| A. Bogatyrev, 1992-95 - 120 - Si v UNIX |00000000000000000000010000000000|1024|1024| for 11 |0000100000000000|2048|2048| |00000000000000000000100000000000|2048|2048| for 12 |0001000000000000|4096|4096| |00000000000000000001000000000000|4096|4096| for 13 |0010000000000000|8192|8192| |00000000000000000010000000000000|8192|8192| for 14 |0100000000000000|16384|16384| |00000000000000000100000000000000|16384|16384| for 15 |1000000000000000|32768|32768| |00000000000000001000000000000000|32768|32768| for 16 |10000000000000000|65536|65536| |00000000000000010000000000000000|65536|65536| for 17 |100000000000000000|131072|131072| |00000000000000100000000000000000|131072|131072| for 18 |1000000000000000000|262144|262144| |00000000000001000000000000000000|262144|262144| for 19 |10000000000000000000|524288|524288| |00000000000010000000000000000000|524288|524288| for 20 |100000000000000000000|1048576|1048576| |00000000000100000000000000000000|1048576|1048576| for 21 |1000000000000000000000|2097152|2097152| |00000000001000000000000000000000|2097152|2097152| for 22 |10000000000000000000000|4194304|4194304| |00000000010000000000000000000000|4194304|4194304| for 23 |100000000000000000000000|8388608|8388608| |00000000100000000000000000000000|8388608|8388608| for 24 |1000000000000000000000000|16777216|16777216| |00000001000000000000000000000000|16777216|16777216| for 25 |10000000000000000000000000|33554432|33554432| |00000010000000000000000000000000|33554432|33554432| for 26 |100000000000000000000000000|67108864|67108864| |00000100000000000000000000000000|67108864|67108864| for 27 |1000000000000000000000000000|134217728|134217728| |00001000000000000000000000000000|134217728|134217728| for 28 |10000000000000000000000000000|268435456|268435456| |00010000000000000000000000000000|268435456|268435456| for 29 |100000000000000000000000000000|536870912|536870912| |00100000000000000000000000000000|536870912|536870912| for 30 |1000000000000000000000000000000|1073741824|1073741824| |01000000000000000000000000000000|1073741824|1073741824| for 31 |10000000000000000000000000000000|-2147483648|2147483648| |10000000000000000000000000000000|-2147483648|2147483648| for 32 |0000000000000000|0|0| |00000000000000000000000000000000|0|0| for 33 |0000000000000000|0|0| |00000000000000000000000000000000|0|0| 1e61 1E61 |Hello, world| |Hello, world| |Hello, world| | Hello, world| |Hello, world | | Hello, wor| |Hello, wor | |Hello, wor| 2.62. Rassmotrim programmu summirovaniya vektorov: A. Bogatyrev, 1992-95 - 121 - Si v UNIX int A[1024], B[1024], C[1024]; ... for(i=0; i < 1024; i++) C[i] = A[i] + B[i]; A pochemu by ne for(i=1024-1; i >=0 ; --i) ...; A pochemu by ne v proizvol'nom poryadke? foreach i in (0..1023) ...; Dannyj primer pokazyvaet, chto nekotorye operacii obladayut vrozhdennym paralellizmom, ved' vse 1024 slozhenij mozhno bylo by vypolnyat' parallel'no! Odnako tupoj kompilyator budet skladyvat' ih imenno v tom poryadke, v kotorom vy emu veleli. Tol'ko samye sov- remennye kompilyatory na mnogoprocessornyh sistemah umeyut avtomaticheski rasparalleli- vat' takie cikly. Sam yazyk Si ne soderzhit sredstv ukazaniya parallel'nosti (razve chto snova - biblioteki i sistemnye vyzovy dlya etogo). A. Bogatyrev, 1992-95 - 122 - Si v UNIX 3. Mobil'nost' i mashinnaya zavisimost' programm. Problemy s russkimi bukvami. Programma schitaetsya mobil'noj, esli ona bez kakih-libo izmenenij ee ishodnogo teksta (libo posle nastrojki nekotoryh konstant pri pomoshchi #define i #ifdef) transli- ruetsya i rabotaet na raznyh tipah mashin (s raznoj razryadnost'yu, sistemoj komand, arhitekturoj, periferiej) pod upravleniem operacionnyh sistem odnogo semejstva. Zame- tim, chto mobil'nymi mogut byt' tol'ko ishodnye teksty programm, ob®ektnye moduli dlya raznyh processorov, estestvenno, nesovmestimy! 3.1. Napishite programmu, pechatayushchuyu razmer tipov dannyh char, short, int, long, float, double, (char *) v bajtah. Ispol'zujte dlya etogo vstroennuyu operaciyu sizeof. 3.2. Sostav'te mobil'nuyu programmu, vyyasnyayushchuyu znacheniya sleduyushchih velichin dlya lyuboj mashiny, na kotoroj rabotaet programma: 1) Naibol'shee dopustimoe znakovoe celoe. 2) Naibol'shee bezznakovoe celoe. 3) Naibol'shee po absolyutnoj velichine otricatel'noe celoe. 4) Tochnost' znacheniya |x|, otlichayushchegosya ot 0, gde x - veshchestvennoe chislo. 5) Naimen'shee znachenie e, takoe chto mashina razlichaet chisla 1 i 1+e (dlya veshchestven- nyh chisel). 3.3. Sostav'te mobil'nuyu programmu, vyyasnyayushchuyu dlinu mashinnogo slova |VM (chislo bitov v peremennoj tipa int). Ukazanie: dlya etogo mozhno ispol'zovat' bitovye sdvigi. 3.4. Nado li pisat' v svoih programmah opredeleniya #define EOF (-1) #define NULL ((char *) 0) /* ili ((void *)0) */ Otvet: NET. Vo-pervyh, eti konstanty uzhe opredeleny v include-fajle, podklyuchaemom po direktive #include <stdio.h> poetomu pravil'nee napisat' imenno etu direktivu. Vo-vtoryh, eto bylo by prosto nep- ravil'no: konkretnye znacheniya etih konstant na dannoj mashine (v dannoj realizacii sistemy) mogut byt' drugimi! CHtoby priderzhivat'sya teh soglashenij, kotoryh priderzhiva- yutsya vse standartnye funkcii dannoj realizacii, vy DOLZHNY brat' eti konstanty iz <stdio.h>. Po toj zhe prichine sleduet pisat' #include <fcntl.h> int fd = open( imyaFajla, O_RDONLY); /* O_WRONLY, O_RDWR */ vmesto int fd = open( imyaFajla, 0); /* 1, 2 */ 3.5. Pochemu mozhet zavershat'sya po zashchite pamyati sleduyushchaya programma? #include <sys/types.h> #include <stdio.h> time_t t; extern time_t time(); ... t = time(0); /* uznat' tekushchee vremya v sekundah s 1 YAnv. 1970 g.*/ Otvet: delo v tom, chto prototip sistemnogo vyzova time() eto: time_t time( time_t *t ); to est' argument dolzhen byt' ukazatelem. My zhe vmesto ukazatelya napisali v kachestve A. Bogatyrev, 1992-95 - 123 - Si v UNIX argumenta 0 (tipa int). Na mashine IBM PC AT 286 ukazatel' - eto 2 slova, a celoe - odno. Nedostayushchee slovo budet vzyato iz steka proizvol'no. V rezul'tate time() polu- chaet v kachestve argumenta ne nulevoj ukazatel', a musor. Pravil'no budet napisat': t = time(NULL); libo (po opredeleniyu time()) time( &t ); a eshche bolee korrektno tak: t = time((time_t *)NULL); Moral': vezde, gde trebuetsya nulevoj ukazatel', sleduet pisat' NULL (ili yavnoe prive- denie nulya k tipu ukazatelya), a ne prosto 0. 3.6. Najdite oshibku: void f(x, s) long x; char *s; { printf( "%ld %s\n", x, s ); } void main(){ f( 12, "hello" ); } |ta programma rabotaet na IBM PC 386, no ne rabotaet na IBM PC 286. Otvet. Zdes' voznikaet ta zhe problema, chto i v primere pro sin(12). Delo v tom, chto f trebuet pervyj argument tipa long (4 bajta na IBM PC 286), my zhe peredaem ej int (2 bajta). V itoge v x popadaet nevernoe znachenie; no bolee togo, nedostayushchie bajty otbirayutsya u sleduyushchego argumenta - s. V itoge i adres stroki stanovitsya nepra- vil'nym, programma obrashchaetsya po nesushchestvuyushchemu adresu i padaet. Na IBM PC 386 i int i long imeyut dlinu 4 bajta, poetomu tam eta oshibka ne proyavlyaetsya! Opyat'-taki, eto povod dlya ispol'zovaniya prototipov funkcij (kogda vy prochitaete pro nih - vernites' k etomu primeru!). Napishite prototip void f(long x, char *s); i oshibki ne budet. V dannom primere my ispol'zovali tip void, kotorogo ne sushestvovalo v rannih versiyah yazyka Si. |tot tip oznachaet, chto funkciya ne vozvrashchaet znacheniya (to est' yavlyaetsya "proceduroj" v smysle yazykov Pascal ili Algol). Esli my ne napishem slovo void pered f, to kompilyator budet schitat' funkciyu f vozvrashchayushchej celoe (int), hotya eta funkciya nichego ne vozvrashchaet (v nej net operatora return). V bol'shinstve sluchaev eto ne prineset vreda i programma budet rabotat'. No zato esli my napishem int x = f((long) 666, "good bye" ); to x poluchit nepredskazuemoe znachenie. Esli zhe f opisana kak void, to napisannyj ope- rator zastavit kompilyator soobshchit' ob oshibke. Tip (void *) oznachaet ukazatel' na chto ugodno (ponyatno, chto k takomu ukazatelyu operacii [], *, -> neprimenimy: snachala sleduet yavno privesti ukazatel' k soderzha- tel'nomu tipu "ukazatel' na tip"). V chastnosti, sejchas stalo prinyato schitat', chto funkciya dinamicheskogo vydeleniya pamyati (memory allocation) malloc() (kotoraya otvodit v kuche|= oblast' pamyati zakazannogo razmera i vydaet ukazatel' na nee) imeet prototip: ____________________ |- V dannoj knige slova "ukazatel'" i "ssylka" upotreblyayutsya v odnom i tom zhe smysle. Esli vy obratites' k yazyku Si++, to obnaruzhite, chto tam eti dva termina (pointer i reference) oznachayut raznye ponyatiya (hotya i shodnye). ____________________ A. Bogatyrev, 1992-95 - 124 - Si v UNIX void *malloc(unsigned size); /* size bajt */ char *s = (char *) malloc( strlen(buf)+1 ); struct ST *p = (struct ST *) malloc( sizeof(struct ST)); /* ili sizeof(*p) */ hotya ran'she prinyato bylo char *malloc(); 3.7. Pogovorim pro operator sizeof. Otmetim rasprostranennuyu oshibku, kogda sizeof prinimayut za funkciyu. |to ne tak! sizeof vychislyaetsya kompilyatorom pri translyacii programmy, a ne programmoj vo vremya vypolneniya. Pust' char a[] = "abcdefg"; char *b = "hijklmn"; Togda sizeof(a) est' 8 (bajt \0 na konce - schitaetsya) sizeof(b) est' 2 na PDP-11 (razmer ukazatelya) strlen(a) est' 7 strlen(b) est' 7 Esli my sdelaem b = "This ia a new line"; strcpy(a, "abc"); to vse ravno sizeof(b) ostanetsya ravno 2 sizeof(a) 8 Takim obrazom sizeof vydaet kolichestvo zarezervirovannoj dlya peremennoj pamyati (v bajtah), nezavisimo ot tekushchego ee soderzhimogo. Operaciya sizeof primenima dazhe k vyrazheniyam. V etom sluchae ona soobshchaet nam, kakov budet razmer u rezul'tata etogo vyrazheniya. Samo vyrazhenie pri etom ne vychislya- etsya, tak v double f(){ printf( "Hi!\n"); return 12.34; } main(){ int x = 2; long y = 4; printf( "%u\n", sizeof(x + y + f())); } budet napechatano znachenie, sovpadayushchee s sizeof(double), a fraza "Hi!" ne budet nape- chatana. Kogda operator sizeof primenyaetsya k peremennoj (a ne k imeni tipa), mozhno ne pisat' kruglye skobki: sizeof(char *); no sizeof x; 3.8. Napishite ob®edinenie, v kotorom mozhet hranit'sya libo ukazatel', libo celoe, libo dejstvitel'noe chislo. Otvet: union all{ char *s; int i; double f; ____________________ |= "Kucha" (heap, pool) - oblast' staticheskoj pamyati, uvelichivayushchayasya po mere nadob- nosti, i prednaznachennaya kak raz dlya hraneniya dinamicheski otvedennyh dannyh. A. Bogatyrev, 1992-95 - 125 - Si v UNIX } x; x.i = 12 ; printf("%d\n", x.i); x.f = 3.14; printf("%f\n", x.f); x.s = "Hi, there"; printf("%s\n", x.s); printf("int=%d double=%d (char *)=%d all=%d\n", sizeof(int), sizeof(double), sizeof(char *), sizeof x); V dannom primere vy obnaruzhite, chto razmer peremennoj x raven maksimal'nomu iz razme- rov tipov int, double, char *. Esli vy hotite ispol'zovat' odnu i tu zhe peremennuyu dlya hraneniya dannyh raznyh tipov, to dlya polucheniya mobil'noj programmy vy dolzhny pol'zovat'sya tol'ko ob®edineni- yami i nikogda ne privyazyvat'sya k dline slova i predstavleniyu etih tipov dannyh na konkretnoj ZVM! Ran'she, kogda programmisty ne dumali o mobil'nosti, oni pisali prog- rammy, gde v odnoj peremenoj tipa int hranili v zavisimosti ot nuzhdy to celye znache- niya, to ukazateli (eto bylo na mashinah PDP i VAX). Uvy, takie programmy okazalis' neperenosimy na mashiny, na kotoryh sizeof(int) != sizeof(char *), bolee togo, oni okazalis' ves'ma tumanny dlya ponimaniya ih drugimi lyud'mi. Ne sledujte etomu stilyu (takoj stil' amerikancy nazyvayut "poor style"), bolee togo, vsemi silami izbegajte ego! Sravnite dva primera, ispol'zuyushchie dva stilya programmirovaniya. Pervyj stil' ne tak ploh, kak tol'ko chto opisannyj, no vse zhe my rekomenduem ispol'zovat' tol'ko vto- roj: /* STILX PERVYJ: YAVNYE PREOBRAZOVANIYA TIPOV */ typedef void *PTR; /* universal'nyj ukazatel' */ struct a { int x, y; PTR pa; } A; struct b { double u, v; PTR pb; } B; #define Aptr(p) ((struct a *)(p)) #define Bptr(p) ((struct b *)(p)) PTR ptr1, ptr2; main(){ ptr1 = &A; ptr2 = &B; Bptr(ptr2)->u = Aptr(ptr1)->x = 77; printf("%f %d\n", B.u, A.x); } /* STILX VTOROJ: OB'EDINENIE */ /* predvaritel'noe ob®yavlenie: */ extern struct a; extern struct b; /* universal'nyj tip dannyh: */ typedef union everything { int i; double d; char *s; struct a *ap; struct b *bp; } ALL; struct a { int x, y; ALL pa; } A; struct b { double u, v; ALL pb; } B; ALL ptr1, ptr2, zz; main(){ ptr1.ap = &A; ptr2.bp = &B; zz.i = 77; ptr2.bp->u = ptr1.ap->x = zz.i; printf("%f %d\n", B.u, A.x); } 3.9. Dlya vydeleniya klassov simvolov (naprimer cifr), sleduet pol'zovat'sya makrosami iz include-fajla <ctype.h> Tak vmesto if( '0' <= c && c <= '9' ) ... A. Bogatyrev, 1992-95 - 126 - Si v UNIX sleduet ispol'zovat' #include <ctype.h> ..... if(isdigit(c)) ... i vmesto if((c >='a' && c <= 'z') || (c >= 'A' && c <= 'Z')) ... nado if(isalpha(c)) ... Delo v tom, chto sravneniya < i > zavisyat ot raspolozheniya bukv v ispol'zuemoj kodi- rovke. No naprimer, v kodirovke KOI-8 russkie bukvy raspolozheny NE v alfavitnom poryadke. Vsledstvie etogo, esli dlya char c1, c2; c1 < c2 to eto eshche ne znachit, chto bukva c1 predshestvuet bukve c2 v alfavite! Leksikografiches- koe sravnenie trebuet special'noj perekodirovki bukv k "uporyadochennoj" kodirovke. Analogichno, sravnenie if( c >= 'a' && c <= 'ya' ) skoree vsego ne dast ozhidaemogo rezul'tata. Makroopredeleniya zhe v <ctype.h> ispol'- zuyut massiv flagov dlya kazhdoj bukvy kodirovki, i potomu ne zavisyat ot poryadka bukv (i rabotayut bystree). Ideya realizacii takova: extern unsigned char _ctype[]; /*massiv flagov*/ #define US(c) (sizeof(c)==sizeof(char)?((c)&0xFF):(c)) /* podavlenie rasshireniya znakovogo bita */ /* F L A G I */ #define _U 01 /* uppercase: bol'shaya bukva */ #define _L 02 /* lowercase: malaya bukva */ #define _N 04 /* number: cifra */ #define _S 010 /* space: probel */ /* ... est' i drugie flagi ... */ #define isalpha(c) ((_ctype+1)[US(c)] & (_U|_L) ) #define isupper(c) ((_ctype+1)[US(c)] & _U ) #define islower(c) ((_ctype+1)[US(c)] & _L ) #define isdigit(c) ((_ctype+1)[US(c)] & _N ) #define isalnum(c) ((_ctype+1)[US(c)] & (_U|_L|_N)) #define tolower(c) ((c) + 'a' - 'A' ) #define toupper(c) ((c) + 'A' - 'a' ) gde massiv _ctype[] zapolnen zaranee (eto proinicializirovannye staticheskie dannye) i hranitsya v standartnoj biblioteke Si. Vot ego fragment: unsigned char _ctype[256 /* razmer alfavita */ + 1] = { /* EOF kod (-1) */ 0, ... /* '1' kod 061 0x31 */ _N, ... /* 'A' kod 0101 0x41 */ _U, ... /* 'a' kod 0141 0x61 */ _L, ... }; A. Bogatyrev, 1992-95 - 127 - Si v UNIX Vyigrysh v skorosti poluchaetsya vot pochemu: esli my opredelim|- #define isalpha(c) (((c) >= 'a' && (c) <= 'z') || \ ((c) >= 'A' && (c) <= 'Z')) to etot operator sostoit iz 7 operacij. Esli zhe my ispol'zuem isalpha iz <ctype.h> (kak opredeleno vyshe) - my ispol'zuem tol'ko dve operacii: indeksaciyu i proverku bitovoj maski &. Operacii _ctype+1 i _U|_L vychislyayutsya do konstant eshche pri kompilya- cii, i poetomu ne vyzyvayut generacii mashinnyh komand. Opredelennye vyshe toupper i tolower rabotayut verno lish' v kodirovke ASCII|=, v kotoroj vse latinskie bukvy raspolozheny podryad i po alfavitu. Obratite vnimanie, chto tolower imeet smysl primenyat' tol'ko k bol'shim bukvam, a toupper - tol'ko k malen'- kim: if( isupper(c) ) c = tolower(c); Sushchestvuet eshche cherezvychajno poleznyj makros isspace(c), kotoryj mozhno bylo by oprede- lit' kak #define isspace(c) (c==' ' ||c=='\t'||c=='\f'|| \ c=='\n'||c=='\r') ili #define isspace(c) (strchr(" \t\f\n\r",(c)) != NULL) Na samom dele on, konechno, realizovan cherez flagi v _ctype[]. On ispol'zuetsya dlya opredeleniya simvolov-probelov, sluzhashchih zapolnitelyami promezhutkov mezhdu slovami teksta. Est' eshche dva neredko ispol'zuemyh makrosa: isprint(c), proveryayushchij, yavlyaetsya li c PECHATNYM simvolom, t.e. imeyushchim izobrazhenie na ekrane; i iscntrl(c), oznachayushchij, chto simvol c yavlyaetsya upravlyayushchim, t.e. pri ego vyvode na terminal nichego ne izobra- zitsya, no terminal proizvedet nekotoroe dejstvie, vrode ochistki ekrana ili peremeshche- niya kursora v kakom-to napravlenii. Oni nuzhny, kak pravilo, dlya otobrazheniya upravlya- yushchih ("kontrolovskih") simvolov v special'nom pechatnom vide, vrode ^A dlya koda '\01'. Zadanie: issledujte kodirovku i <ctype.h> na vashej mashine. Napishite funkciyu leksikograficheskogo sravneniya bukv i strok. Ukazanie: pust' bukvy imeyut takie kody (eto ne sootvetstvuet real'nosti!): bukva: a b v g d e kod: 1 4 2 5 3 0 nuzhno: 0 1 2 3 4 5 Togda ideya funkcii Ctou perekodirovki k uporyadochennomu alfavitu takova: unsigned char UU[] = { 5, 0, 2, 4, 1, 3 }; /* v dejstvitel'nosti - 256 elementov: UU[256] */ Ctou(c) unsigned char c; { return UU[c]; } int strcmp(s1, s2) char *s1, *s2; { /* Proignorirovat' sovpadayushchie nachala strok */ while(*s1 && *s1 == *s2) s1++, s2++; /* Vernut' raznost' [ne]sovpavshih simvolov */ return Ctou(*s1) - Ctou(*s2); ____________________ |- Obratite vnimanie, chto simvol \ v konce stroki makroopredeleniya pozvolyaet pro- dolzhit' makros na sleduyushchej stroke, poetomu makros mozhet sostoyat' iz mnogih strok. |= ASCII - American Standard Code for Information Interchange - naibolee rasprost- ranennaya v mire kodirovka (Amerikanskij standart). A. Bogatyrev, 1992-95 - 128 - Si v UNIX } Razberites' s principom formirovaniya massiva UU. 3.10. V sovremennyh UNIX-ah s podderzhkoj razlichnyh yazykov tablica ctype zagruzhaetsya iz nekotoryh sistemnyh fajlov - dlya kazhdogo yazyka svoya. Dlya kakogo yazyka - vybira- etsya po soderzhimomu peremennoj okruzheniya LANG. Esli peremennaya ne zadana - ispol'zu- etsya znachenie "C", anglijskij yazyk. Zagruzka tablic dolzhna proishodit' yavno, vyzovom ... #include <locale.h> ... main(){ setlocale(LC_ALL, ""); ... vse ostal'noe ... } 3.11. Vernemsya k nashej lyubimoj probleme so znakovym bitom u tipa char. #include <stdio.h> #include <locale.h> #include <ctype.h> int main(int ac, char *av[]){ char c;