American National Standards Institute, razrabotavshij standart na yazyk Si
i ego okruzhenie.
A. Bogatyrev, 1992-95 - 20 - Si v UNIX
#include <stdio.h>
main(){
int max, min, x, n;
for( n=0; scanf("%d", &x) != EOF; n++)
if( n == 0 ) min = max = x;
else{
if( x > max ) max = x;
if( x < min ) min = x;
}
printf( "Vveli %d chisel: min=%d max=%d\n",
n, min, max);
}
Napishite analogichnuyu programmu dlya poiska maksimuma i minimuma sredi elementov mas-
siva, iznachal'no min=max=array[0];
1.49. Napishite programmu, kotoraya sortiruet massiv zadannyh chisel po vozrastaniyu
(ubyvaniyu) metodom puzyr'kovoj sortirovki. Kogda vy stanete bolee opytny v Si, napi-
shite sortirovku metodom SHella.
/*
* Sortirovka po metodu SHella.
* Sortirovke podvergaetsya massiv ukazatelej na dannye tipa obj.
* v------.-------.------.-------.------0
* ! ! ! !
* * * * *
* elementy tipa obj
* Programma vzyata iz knigi Kernigana i Ritchi.
*/
#include <stdio.h>
#include <string.h>
#include <locale.h>
#define obj char
static shsort (v,n,compare)
int n; /* dlina massiva */
obj *v[]; /* massiv ukazatelej */
int (*compare)(); /* funkciya sravneniya sosednih elementov */
{
int g, /* rasstoyanie, na kotorom proishodit sravnenie */
i,j; /* indeksy sravnivaemyh elementov */
obj *temp;
for( g = n/2 ; g > 0 ; g /= 2 )
for( i = g ; i < n ; i++ )
for( j = i-g ; j >= 0 ; j -= g )
{
if((*compare)(v[j],v[j+g]) <= 0)
break; /* uzhe v pravil'nom poryadke */
/* obmenyat' ukazateli */
temp = v[j]; v[j] = v[j+g]; v[j+g] = temp;
/* V kachestve uprazhneniya mozhete napisat'
* pri pomoshchi curses-a programmu,
* vizualiziruyushchuyu process sortirovki:
* naprimer, izobrazhayushchuyu etu perestanovku
* elementov massiva */
}
}
A. Bogatyrev, 1992-95 - 21 - Si v UNIX
/* sortirovka strok */
ssort(v) obj **v;
{
extern less(); /* funkciya sravneniya strok */
int len;
/* podschet chisla strok */
len=0;
while(v[len]) len++;
shsort(v,len,less);
}
/* Funkciya sravneniya strok.
* Vernut' celoe men'she nulya, esli a < b
* nol', esli a == b
* bol'she nulya, esli a > b
*/
less(a,b) obj *a,*b;
{
return strcoll(a,b);
/* strcoll - analog strcmp,
* no s uchetom alfavitnogo poryadka bukv.
*/
}
char *strings[] = {
"YAsha", "Fedya", "Kolya",
"Grisha", "Serezha", "Misha",
"Andrej Ivanovich", "Vas'ka",
NULL
};
int main(){
char **next;
setlocale(LC_ALL, "");
ssort( strings );
/* raspechatka */
for( next = strings ; *next ; next++ )
printf( "%s\n", *next );
return 0;
}
1.50. Realizujte algoritm bystroj sortirovki.
A. Bogatyrev, 1992-95 - 22 - Si v UNIX
/* Algoritm bystroj sortirovki. Rabota algoritma "animiruetsya"
* (animate-ozhivlyat') pri pomoshchi biblioteki curses.
* cc -o qsort qsort.c -lcurses -ltermcap
*/
#include "curses.h"
#define N 10 /* dlina massiva */
/* massiv, podlezhashchij sortirovke */
int target [N] = {
7, 6, 10, 4, 2,
9, 3, 8, 5, 1
};
int maxim; /* maksimal'nyj element massiva */
/* quick sort */
qsort (a, from, to)
int a[]; /* sortiruemyj massiv */
int from; /* levyj nachal'nyj indeks */
int to; /* pravyj konechnyj indeks */
{
register i, j, x, tmp;
if( from >= to ) return;
/* chislo elementov <= 1 */
i = from; j = to;
x = a[ (i+j) / 2 ]; /* znachenie iz serediny */
do{
/* suzhenie vpravo */
while( a[i] < x ) i++ ;
/* suzhenie vlevo */
while( x < a[j] ) j--;
if( i <= j ){ /* obmenyat' */
tmp = a[i]; a[i] = a[j] ; a[j] = tmp;
i++; j--;
demochanges(); /* vizualizaciya */
}
} while( i <= j );
/* Teper' obe chasti soshlis' v odnoj tochke.
* Dlina levoj chasti = j - from + 1
* pravoj = to - i + 1
* Vse chisla v levoj chasti men'she vseh chisel v pravoj.
* Teper' nado prosto otsortirovat' kazhduyu chast' v otdel'nosti.
* Snachala sortiruem bolee korotkuyu (dlya ekonomii pamyati
* v steke ). Rekursiya:
*/
if( (j - from) < (to - i) ){
qsort( a, from, j );
qsort( a, i, to );
} else {
qsort( a, i, to );
qsort( a, from, j );
}
}
A. Bogatyrev, 1992-95 - 23 - Si v UNIX
int main (){
register i;
initscr(); /* zapusk curses-a */
/* poisk maksimal'nogo chisla v massive */
for( maxim = target[0], i = 1 ; i < N ; i++ )
if( target[i] > maxim )
maxim = target[i];
demochanges();
qsort( target, 0, N-1 );
demochanges();
mvcur( -1, -1, LINES-1, 0);
/* kursor v levyj nizhnij ugol */
endwin(); /* zavershit' rabotu s curses-om */
return 0;
}
#define GAPY 2
#define GAPX 20
/* narisovat' kartinku */
demochanges(){
register i, j;
int h = LINES - 3 * GAPY - N;
int height;
erase(); /* zachistit' okno */
attron( A_REVERSE );
/* risuem matricu uporyadochennosti */
for( i=0 ; i < N ; i++ )
for( j = 0; j < N ; j++ ){
move( GAPY + i , GAPX + j * 2 );
addch( target[i] >= target[j] ? '*' : '.' );
addch( ' ' );
/* Risovat' '*' esli elementy
* idut v nepravil'nom poryadke.
* Vozmozhen variant proverki target[i] > target[j]
*/
}
attroff( A_REVERSE );
/* massiv */
for( i = 0 ; i < N ; i++ ){
move( GAPY + i , 5 );
printw( "%4d", target[i] );
height = (long) h * target[i] / maxim ;
for( j = 2 * GAPY + N + (h - height) ;
j < LINES - GAPY; j++ ){
move( j, GAPX + i * 2 );
addch( '|' );
}
}
refresh(); /* proyavit' kartinku */
sleep(1);
}
A. Bogatyrev, 1992-95 - 24 - Si v UNIX
1.51. Realizujte privedennyj fragment programmy bez ispol'zovaniya operatora goto i
bez metok.
if ( i > 10 ) goto M1;
goto M2;
M1: j = j + i; flag = 2; goto M3;
M2: j = j - i; flag = 1;
M3: ;
Zamet'te, chto pomechat' mozhno tol'ko operator (mozhet byt' pustoj); poetomu ne mozhet
vstretit'sya fragment
{ ..... Label: } a tol'ko { ..... Label: ; }
1.52. V kakom sluchae opravdano ispol'zovanie operatora goto?
Otvet: pri vyhode iz vlozhennyh ciklov, t.k. operator break pozvolyaet vyjti
tol'ko iz samogo vnutrennego cikla (na odin uroven').
1.53. K kakomu if-u otnositsya else?
if(...) ... if(...) ... else ...
Otvet: ko vtoromu (k blizhajshemu predshestvuyushchemu, dlya kotorogo net drugogo else).
Voobshche zhe luchshe yavno rasstavlyat' skobki (dlya yasnosti):
if(...){ ... if(...) ... else ... }
if(...){ ... if(...) ... } else ...
1.54. Makroopredelenie, ch'e telo predstavlyaet soboj posledovatel'nost' operatorov v
{...} skobkah (blok), mozhet vyzvat' problemy pri ispol'zovanii ego v uslovnom opera-
tore if s else-chast'yu:
#define MACRO { x=1; y=2; }
if(z) MACRO;
else .......;
My poluchim posle makrorasshireniya
if(z) { x=1; y=2; } /* konec if-a */ ;
else .......; /* else ni k chemu ne otnositsya */
to est' sintaksicheski oshibochnyj fragment, tak kak dolzhno byt' libo
if(...) odin_operator;
else .....
libo
if(...){ posledovatel'nost'; ...; operatorov; }
else .....
gde tochka-s-zapyatoj posle } ne nuzhna. S etim yavleniem boryutsya, oformlyaya blok {...} v
vide do{...}while(0)
#define MACRO do{ x=1; y=2; }while(0)
Telo takogo "cikla" vypolnyaetsya edinstvennyj raz, pri etom my poluchaem pravil'nyj
tekst:
A. Bogatyrev, 1992-95 - 25 - Si v UNIX
if(z) do{ x=1; y=2; }while(0);
else .......;
1.55. V chem oshibka (dlya znayushchih yazyk "Paskal'")?
int x = 12;
if( x < 20 and x > 10 ) printf( "O'K\n");
else if( x > 100 or x < 0 ) printf( "Bad x\n");
else printf( "x=%d\n", x);
Napishite
#define and &&
#define or ||
1.56. Pochemu programma zaciklivaetsya? My hotim podschitat' chislo probelov i tabulya-
cij v nachale stroki:
int i = 0;
char *s = " 3 spaces";
while(*s == ' ' || *s++ == '\t')
printf( "Probel %d\n", ++i);
Otvet: logicheskie operacii || i && vypolnyayutsya sleva napravo; kak tol'ko kakoe-to
uslovie v || okazyvaetsya istinnym (a v && lozhnym) - dal'nejshie usloviya prosto ne
vychislyayutsya. V nashem sluchae uslovie *s==' ' srazu zhe verno, i operaciya s++ iz vtorogo
usloviya ne vypolnyaetsya! My dolzhny byli napisat' hotya by tak:
while(*s == ' ' || *s == '\t'){
printf( "Probel %d\n", ++i); s++;
}
S drugoj storony, eto svojstvo || i && cherezvychajno polezno, naprimer:
if( x != 0.0 && y/x < 1.0 ) ... ;
Esli by my ne vstavili proverku na 0, my mogli by poluchit' delenie na 0. V dannom zhe
sluchae pri x==0 delenie prosto ne budet vychislyat'sya. Vot eshche primer:
int a[5], i;
for(i=0; i < 5 && a[i] != 0; ++i) ...;
Esli i vyjdet za granicu massiva, to sravnenie a[i] s nulem uzhe ne budet vychislyat'sya,
t.e. popytki prochest' element ne vhodyashchij v massiv ne proizojdet.
|to svojstvo && pozvolyaet pisat' dovol'no neochevidnye konstrukcii, vrode
if((cond) && f());
chto okazyvaetsya ekvivalentnym
if( cond ) f();
Voobshche zhe
if(C1 && C2 && C3) DO;
ekvivalentno
if(C1) if(C2) if(C3) DO;
i dlya "ili"
A. Bogatyrev, 1992-95 - 26 - Si v UNIX
if(C1 || C2 || C3) DO;
ekvivalentno
if(C1) goto ok;
else if(C2) goto ok;
else if(C3){ ok: DO; }
Vot eshche primer, pol'zuyushchijsya etim svojstvom ||
#include <stdio.h>
main(argc, argv) int argc; char *argv[];
{ FILE *fp;
if(argc < 2 || (fp=fopen(argv[1], "r")) == NULL){
fprintf(stderr, "Plohoe imya fajla\n");
exit(1); /* zavershit' programmu */
}
...
}
Esli argc==1, to argv[1] ne opredeleno, odnako v etom sluchae popytki otkryt' fajl s
imenem argv[1] prosto ne budet predprinyato!
Nizhe priveden eshche odin soderzhatel'nyj primer, predstavlyayushchij soboj odnu iz voz-
mozhnyh shem napisaniya "dvuyazychnyh" programm, t.e. vydayushchih soobshcheniya na odnom iz dvuh
yazykov po vashemu zhelaniyu. Proveryaetsya peremennaya okruzheniya MSG (ili LANG):
YAZYK:
1) "MSG=engl" anglijskij
2) MSG net v okruzhenii anglijskij
3) "MSG=rus" russkij
Pro okruzhenie i funkciyu getenv() smotri v glave "Vzaimodejstvie s UNIX", pro strchr()
- v glave "Massivy i stroki".
#include <stdio.h>
int _ediag = 0; /* yazyk diagnostik: 1-russkij */
extern char *getenv(), *strchr();
#define ediag(e,r) (_ediag?(r):(e))
main(){ char *s;
_ediag = ((s=getenv("MSG")) != NULL &&
strchr("rRrR", *s) != NULL);
printf(ediag("%d:english\n", "%d:russkij\n"), _ediag);
}
Esli peremennaya MSG ne opredelena, to s==NULL i funkciya strchr(s,...) ne vyzyvaetsya
(ee pervyj frgument ne dolzhen byt' NULL-om). Zdes' ee mozhno bylo by uproshchenno zame-
nit' na *s=='r'; togda esli s ravno NULL, to obrashchenie *s bylo by nezakonno (obrashche-
nie po ukazatelyu NULL daet nepredskazuemye rezul'taty i, skoree vsego, vyzovet krah
programmy).
1.57. Inogda logicheskoe uslovie mozhno sdelat' bolee ponyatnym, ispol'zuya pravila de-
Morgana:
a && b = ! ( !a || !b )
a || b = ! ( !a && !b )
a takzhe uchityvaya, chto
! !a = a
! (a == b) = (a != b)
Naprimer:
A. Bogatyrev, 1992-95 - 27 - Si v UNIX
if( c != 'a' && c != 'b' && c != 'c' )...;
prevrashchaetsya v
if( !(c == 'a' || c == 'b' || c == 'c')) ...;
1.58. Primer, v kotorom ispol'zuyutsya pobochnye effekty vychisleniya vyrazhenij. Obychno
znachenie vyrazheniya prisvaivaetsya nekotoroj peremennoj, no eto ne neobhodimo. Poetomu
mozhno ispol'zovat' svojstva vychisleniya && i || v vyrazheniyah (hotya eto ne est' samyj
ponyatnyj sposob napisaniya programm, skoree nekotoryj rod izvrashcheniya). Ogranichenie tut
takovo: vse chasti vyrazheniya dolzhny vozvrashchat' znacheniya.
#include <stdio.h>
extern int errno; /* kod sistemnoj oshibki */
FILE *fp;
int openFile(){
errno = 0;
fp = fopen("/etc/inittab", "r");
printf("fp=%x\n", fp);
return(fp == NULL ? 0 : 1);
}
int closeFile(){
printf("closeFile\n");
if(fp) fclose(fp);
return 0;
}
int die(int code){
printf("exit(%d)\n", code);
exit(code);
return 0;
}
void main(){
char buf[2048];
if( !openFile()) die(errno); closeFile();
openFile() || die(errno); closeFile();
/* esli fajl otkrylsya, to die() ne vychislyaetsya */
openFile() ? 0 : die(errno); closeFile();
if(openFile()) closeFile();
openFile() && closeFile();
/* vychislit' closeFile() tol'ko esli openFile() udalos' */
openFile() && (printf("%s", fgets(buf, sizeof buf, fp)), closeFile());
}
V poslednej stroke ispol'zovan operator "zapyataya": (a,b,c) vozvrashchaet znachenie vyra-
zheniya c.
1.59. Napishite funkciyu, vychislyayushchuyu summu massiva zadannyh chisel.
1.60. Napishite funkciyu, vychislyayushchuyu srednee znachenie massiva zadannyh chisel.
1.61. CHto budet napechatano v rezul'tate raboty sleduyushchego cikla?
for ( i = 36; i > 0; i /= 2 )
printf ( "%d%s", i,
i==1 ? ".\n":", ");
A. Bogatyrev, 1992-95 - 28 - Si v UNIX
Otvet: 36, 18, 9, 4, 2, 1.
1.62. Najdite oshibki v sleduyushchej programme:
main {
int i, j, k(10);
for ( i = 0, i <= 10, i++ ){
k[i] = 2 * i + 3;
for ( j = 0, j <= i, j++ )
printf ("%i\n", k[j]);
}
}
Obratite vnimanie na format %i, sushchestvuet li takoj format? Est' li eto tot format,
po kotoromu sleduet pechatat' znacheniya tipa int?
1.63. Napishite programmu, kotoraya raspechatyvaet elementy massiva. Napishite prog-
rammu, kotoraya raspechatyvaet elementy massiva po 5 chisel v stroke.
1.64. Sostav'te programmu schityvaniya strok simvolov iz standartnogo vvoda i pechati
nomera vvedennoj stroki, adresa stroki v pamyati |VM, znacheniya stroki, dliny stroki.
1.65. Stilisticheskoe zamechanie: v operatore return vozvrashchaemoe vyrazhenie ne obyaza-
tel'no dolzhno byt' v ()-skobkah. Delo v tom, chto return - ne funkciya, a operator.
return vyrazhenie;
return (vyrazhenie);
Odnako esli vy vyzyvaete funkciyu (naprimer, exit) - to argumenty dolzhny byt' v krug-
lyh skobkah: exit(1); no ne exit 1;
1.66. Izbegajte situacii, kogda funkciya v raznyh vetvyah vychisleniya to vozvrashchaet
nekotoroe znachenie, to ne vozvrashchaet nichego:
int func (int x) {
if( x > 10 ) return x*2;
if( x == 10 ) return (10);
/* a zdes' - neyavnyj return; bez znacheniya */
}
pri x < 10 funkciya vernet nepredskazuemoe znachenie! Mnogie kompilyatory raspoznayut
takie situacii i vydayut preduprezhdenie.
1.67. Napishite programmu, zaprashivayushchuyu vashe imya i "privetstvuyushchuyu" vas. Napishite
funkciyu chteniya stroki. Ispol'zujte getchar() i printf().
Otvet:
#include <stdio.h> /* standard input/output */
main(){
char buffer[81]; int i;
printf( "Vvedite vashe imya:" );
while((i = getstr( buffer, sizeof buffer )) != EOF){
printf( "Zdravstvuj, %s\n", buffer );
printf( "Vvedite vashe imya:" );
}
}
getstr( s, maxlen )
char *s; /* kuda pomestit' stroku */
int maxlen; /* dlina bufera:
A. Bogatyrev, 1992-95 - 29 - Si v UNIX
maks. dlina stroki = maxlen-1 */
{ int c; /* ne char! (pochemu ?) */
register int i = 0;
maxlen--; /* rezerviruem bajt pod konechnyj '\0' */
while(i < maxlen && (c = getchar()) != '\n'
&& c != EOF )
s[i++] = c;
/* obratite vnimanie, chto sam simvol '\n'
* v stroku ne popadet */
s[i] = '\0'; /* priznak konca stroki */
return (i == 0 && c == EOF) ? EOF : i;
/* vernem dlinu stroki */
}
Vot eshche odin variant funkcii chteniya stroki: v nashem primere ee sleduet vyzyvat' kak
fgetstr(buffer,sizeof(buffer),stdin);
|to podpravlennyj variant standartnoj funkcii fgets (v nej stroki @1 i @2 obmenyany
mestami).
char *fgetstr(char *s, int maxlen, register FILE *fp){
register c; register char *cs = s;
while(--maxlen > 0 && (c = getc(fp)) != EOF){
if(c == '\n') break; /* @1 */
*cs++ = c; /* @2 */
}
if(c == EOF && cs == s) return NULL;
/* Zamet'te, chto pri EOF stroka s ne menyaetsya! */
*cs = '\0'; return s;
}
Issledujte povedenie etih funkcij, kogda vhodnaya stroka slishkom dlinnaya (dlinnee max-
len). Zamechanie: vmesto nashej "rukopisnoj" funkcii getstr() my mogli by ispol'zovat'
standartnuyu bibliotechnuyu funkciyu gets(buffer).
1.68. Ob®yasnite, pochemu d stalo otricatel'nym i pochemu %X pechataet bol'she F, chem v
ishodnom chisle? Primer vypolnyalsya na 32-h bitnoj mashine.
main(){
unsigned short u = 65535; /* 16 bit: 0xFFFF */
short d = u; /* 15 bit + znakovyj bit */
printf( "%X %d\n", d, d); /* FFFFFFFF -1 */
}
Ukazanie: rassmotrite dvoichnoe predstavlenie chisel (smotri prilozhenie). Kakie prive-
deniya tipov zdes' proishodyat?
1.69. Pochemu 128 prevratilos' v otricatel'noe chislo?
main()
{
/*signed*/ char c = 128; /* bity: 10000000 */
unsigned char uc = 128;
int d = c; /* ispol'zuetsya 32-h bitnyj int */
printf( "%d %d %x\n", c, d, d );
/* -128 -128 ffffff80 */
d = uc;
printf( "%d %d %x\n", uc, d, d );
/* 128 128 80 */
}
A. Bogatyrev, 1992-95 - 30 - Si v UNIX
Otvet: pri privedenii char k int rasshirilsya znakovyj bit (7-oj), zanyav vsyu starshuyu
chast' slova. Znakovyj bit int-a stal raven 1, chto yavlyaetsya priznakom otricatel'nogo
chisla. To zhe budet proishodit' so vsemi znacheniyami c iz diapazona 128..255 (soderzha-
shchimi bit 0200). Pri privedenii unsigned char k int znakovyj bit ne rasshiryaetsya.
Mozhno bylo postupit' eshche i tak:
printf( "%d\n", c & 0377 );
Zdes' c privoditsya k tipu int (potomu chto pri ispol'zovanii v argumentah funkcii tip
char VSEGDA privoditsya k tipu int), zatem &0377 zanulit starshij bajt poluchennogo
celogo chisla (sostoyashchij iz bitov 1), snova prevrativ chislo v polozhitel'noe.
1.70. Pochemu
printf("%d\n", '\377' == 0377 );
printf("%d\n", '\xFF' == 0xFF );
pechataet 0 (lozh')? Otvet: po toj zhe prichine, po kotoroj
printf("%d %d\n", '\377', 0377);
pechataet -1 255, a imenno: char '\377' privoditsya v vyrazheniyah k celomu rasshireniem
znakovogo bita (a 0377 - uzhe celoe).
1.71. Rassmotrim programmu
#include <stdio.h>
int main(int ac, char **av){
int c;
while((c = getchar()) != EOF)
switch(c){
case 'y': printf("Bukva y\n"); break;
case 'j': printf("Bukva j\n"); break;
default: printf("Bukva s kodom %d\n", c); break;
}
return 0;
}
Ona rabotaet tak:
% a.out
jfyv
Bukva s kodom 202
Bukva s kodom 198
Bukva s kodom 217
Bukva s kodom 215
Bukva s kodom 10
^D
%
Vypolnyaetsya vsegda default, pochemu ne vypolnyayutsya case 'y' i case 'j'?
Otvet: russkie bukvy imeyut vos'moj bit (levyj) ravnyj 1. V case takoj bajt pri-
voditsya k tipu int rasshireniem znakovogo bita. V itoge poluchaetsya otricatel'noe
chislo. Primer:
void main(void){
int c = 'j';
printf("%d\n", c);
}
pechataet -54
A. Bogatyrev, 1992-95 - 31 - Si v UNIX
Resheniem sluzhit podavlenie rasshireniya znakovogo bita:
#include <stdio.h>
/* Odno iz dvuh */
#define U(c) ((c) & 0xFF)
#define UC(c) ((unsigned char) (c))
int main(int ac, char **av){
int c;
while((c = getchar()) != EOF)
switch(c){
case U('y'): printf("Bukva y\n"); break;
case UC('j'): printf("Bukva j\n"); break;
default: printf("Bukva s kodom %d\n", c); break;
}
return 0;
}
Ona rabotaet pravil'no:
% a.out
jfyv
Bukva j
Bukva s kodom 198
Bukva y
Bukva s kodom 215
Bukva s kodom 10
^D
%
Vozmozhno takzhe ispol'zovanie kodov bukv:
case 0312:
no eto gorazdo menee naglyadno. Podavlenie znakovogo bita neobhodimo takzhe i v opera-
torah if:
int c;
...
if(c == 'j') ...
sleduet zamenit' na
if(c == UC('j')) ...
Sleva zdes' - signed int, pravuyu chast' kompilyator tozhe privodit k signed int. Priho-
ditsya yavno govorit', chto sprava - unsigned.
1.72. Rassmotrim programmu, kotoraya dolzhna napechatat' chisla ot 0 do 255. Dlya etih
chisel v kachestve schetchika dostatochen odin bajt:
int main(int ac, char *av[]){
unsigned char ch;
for(ch=0; ch < 256; ch++)
printf("%d\n", ch);
return 0;
}
Odnako eta programma zaciklivaetsya, poskol'ku v moment, kogda ch==255, eto znachenie
men'she 256. Sleduyushchim shagom vypolnyaetsya ch++, i ch stanovitsya ravno 0, ibo dlya char
A. Bogatyrev, 1992-95 - 32 - Si v UNIX
vychisleniya vedutsya po modulyu 256 (2 v 8 stepeni). To est' v dannom sluchae 255+1=0
Reshenij sushchestvuet dva: pervoe - prevratit' unsigned char v int. Vtoroe - vsta-
vit' yavnuyu proverku na poslednee znachenie diapazona.
int main(int ac, char *av[]){
unsigned char ch;
for(ch=0; ; ch++){
printf("%d\n", ch);
if(ch == 255) break;
}
return 0;
}
1.73. Podumajte, pochemu dlya
unsigned a, b, c;
a < b + c ne ekvivalentno a - b < c
(pervoe - bolee korrektno). Namek v vide primera (on vypolnyalsya na 32-bitnoj mashine):
a = 1; b = 3; c = 2;
printf( "%u\n", a - b ); /* 4294967294, hotya v
normal'noj arifmetike 1 - 3 = -2 */
printf( "%d\n", a < b + c ); /* 1 */
printf( "%d\n", a - b < c ); /* 0 */
Mogut li unsigned chisla byt' otricatel'nymi?
1.74. Dan tekst:
short x = 40000;
printf("%d\n", x);
Pechataetsya -25536. Ob®yasnite effekt. Ukazanie: kakovo naibol'shee predstavimoe korot-
koe celoe (16 bitnoe)? CHto na samom dele okazalos' v x? (lishnie sleva bity - obruba-
yutsya).
1.75. Pochemu v primere
double x = 5 / 2;
printf( "%g\n", x );
znachenie x ravno 2 a ne 2.5 ?
Otvet: proizvoditsya celochislennoe delenie, zatem v prisvaivanii celoe chislo 2
privoditsya k tipu double. CHtoby poluchilsya otvet 2.5, nado pisat' odnim iz sleduyushchih
sposobov:
double x = 5.0 / 2;
x = 5 / 2.0;
x = (double) 5 / 2;
x = 5 / (double) 2;
x = 5.0 / 2.0;
to est' v vyrazhenii dolzhen byt' hot' odin operand tipa double.
Ob®yasnite, pochemu sleduyushchie tri operatora vydayut takie znacheniya:
A. Bogatyrev, 1992-95 - 33 - Si v UNIX
double g = 9.0;
int t = 3;
double dist = g * t * t / 2; /* 40.5 */
dist = g * (t * t / 2); /* 36.0 */
dist = g * (t * t / 2.0); /* 40.5 */
V kakih sluchayah delenie celochislennoe, v kakih - veshchestvennoe? Pochemu?
1.76. Stranslirujte primer na mashine s dlinoj slova int ravnoj 16 bit:
long n = 1024 * 1024;
long nn = 512 * 512;
printf( "%ld %ld\n", n, nn );
Pochemu pechataetsya 0 0 a ne 1048576 262144?
Otvet: rezul'tat umnozheniya (2**20 i 2**18) - eto celoe chislo; odnako ono slishkom
veliko dlya sohraneniya v 16 bitah, poetomu starshie bity obrubayutsya. Poluchaetsya 0.
Zatem v prisvaivanii eto uzhe obrublennoe znachenie privoditsya k tipu long (32 bita) -
eto vse ravno budet 0.
CHtoby poluchit' korrektnyj rezul'tat, nado chtoby vyrazhenie sprava ot = uzhe imelo
tip long i srazu sohranyalos' v 32 bitah. Dlya etogo ono dolzhno imet' hot' odin operand
tipa long:
long n = (long) 1024 * 1024;
long nn = 512 * 512L;
1.77. Najdite oshibku v operatore:
x - = 4; /* vychest' iz x chislo 4 */
Otvet: mezhdu `-' i `=' ne dolzhno byt' probela. Operaciya vida
x @= expr;
oznachaet
x = x @ expr;
(gde @ - odna iz operacij + - * / % ^ >> << & |), prichem x zdes' vychislyaetsya edinst-
vennyj raz (t.e. takaya forma ne tol'ko koroche i ponyatnee, no i ekonomichnee).
Odnako imeetsya tonkoe otlichie a=a+n ot a+=n; ono zaklyuchaetsya v tom, skol'ko raz
vychislyaetsya a. V sluchae a+=n edinozhdy; v sluchae a=a+n dva raza.
A. Bogatyrev, 1992-95 - 34 - Si v UNIX
#include <stdio.h>
static int x = 0;
int *iaddr(char *msg){
printf("iaddr(%s) for x=%d evaluated\n", msg, x);
return &x;
}
int main(){
static int a[4];
int *p, i;
printf( "1: "); x = 0; (*iaddr("a"))++;
printf( "2: "); x = 0; *iaddr("b") += 1;
printf( "3: "); x = 0; *iaddr("c") = *iaddr("d") + 1;
for(i=0, p = a; i < sizeof(a)/sizeof(*a); i++) a[i] = 0;
*p++ += 1;
for(i=0; i < sizeof(a)/sizeof(*a); i++)
printf("a[%d]=%d ", i, a[i]);
printf("offset=%d\n", p - a);
for(i=0, p = a; i < sizeof(a)/sizeof(*a); i++) a[i] = 0;
*p++ = *p++ + 1;
for(i=0; i < sizeof(a)/sizeof(*a); i++)
printf("a[%d]=%d ", i, a[i]);
printf("offset=%d\n", p - a);
return 0;
}
Vydacha:
1: iaddr(a) for x=0 evaluated
2: iaddr(b) for x=0 evaluated
3: iaddr(d) for x=0 evaluated
iaddr(c) for x=0 evaluated
a[0]=1 a[1]=0 a[2]=0 a[3]=0 offset=1
a[0]=1 a[1]=0 a[2]=0 a[3]=0 offset=2
Zamet'te takzhe, chto
a[i++] += z;
eto
a[i] = a[i] + z; i++;
a vovse ne
a[i++] = a[i++] + z;
1.78. Operaciya y = ++x; ekvivalentna
y = (x = x+1, x);
a operaciya y = x++; ekvivalentna
y = (tmp = x, x = x+1, tmp);
ili
y = (x += 1) - 1;
gde tmp - vremennaya psevdoperemennaya togo zhe tipa, chto i x. Operaciya `,' vydaet
A. Bogatyrev, 1992-95 - 35 - Si v UNIX
znachenie poslednego vyrazheniya iz perechislennyh (podrobnee sm. nizhe).
Pust' x=1. Kakie znacheniya budut prisvoeny x i y posle vypolneniya operatora
y = ++x + ++x + ++x;
1.79. Pust' i=4. Kakie znacheniya budut prisvoeny x i i posle vypolneniya operatora
x = --i + --i + --i;
1.80. Pust' x=1. Kakie znacheniya budut prisvoeny x i y posle vypolneniya operatora
y = x++ + x++ + x++;
1.81. Pust' i=4. Kakie znacheniya budut prisvoeny i i y posle vypolneniya operatora
y = i-- + i-- + i--;
1.82. Korrektny li operatory
char *p = "Jabberwocky"; char s[] = "0123456789?";
int i = 0;
s[i] = p[i++]; ili *p = *++p;
ili s[i] = i++;
ili dazhe *p++ = f( *p );
Otvet: net, standart ne predusmatrivaet, kakaya iz chastej prisvaivaniya vychislyaetsya
pervoj: levaya ili pravaya. Poetomu vse mozhet rabotat' tak, kak my i podrazumevali, no
mozhet i inache! Kakoe i ispol'zuetsya v s[i]: 0 ili uzhe 1 (++ uzhe sdelan ili net), to
est'
int i = 0; s[i] = i++; eto
s[0] = 0; ili zhe s[1] = 0; ?
Kakoe p budet ispol'zovano v levoj chasti *p: uzhe prodvinutoe ili staroe? Eshche bolee
eta ideya dramatizirovana v
s[i++] = p[i++];
Zametim eshche, chto v
int i=0, j=0;
s[i++] = p[j++];
takoj problemy ne voznikaet, poskol'ku indeksy oboih v chastyah prisvaivaniya nezavi-
simy. Zato analogichnaya problema vstaet v
if( a[i++] < b[i] )...;
Poryadok vychisleniya operandov ne opredelen, poetomu neyasno, chto budet sdelano prezhde:
vzyato znachenie b[i] ili znachenie a[i++] (togda budet vzyato b[i+1] ). Nado pisat'
tak, chtoby ne polagat'sya na osobennosti vashego kompilyatora:
if( a[i] < b[i+1] )...; ili *p = *(p+1);
i++; ++p;
A. Bogatyrev, 1992-95 - 36 - Si v UNIX
Tverdo usvojte, chto i++ i ++i ne tol'ko vydayut znacheniya i i i+1 sootvetstvenno,
no i izmenyayut znachenie i. Poetomu eti operatory NE NADO ispol'zovat' tam, gde po
smyslu trebuetsya i+1, a ne i=i+1. Tak dlya sravneniya sosednih elementov massiva
if( a[i] < a[i+1] ) ... ; /* verno */
if( a[i] < a[++i] ) ... ; /* neverno */
1.83. Poryadok vychisleniya operandov v binarnyh vyrazheniyah ne opredelen (chto ran'she
vychislyaetsya - levyj operand ili zhe pravyj ?). Tak primer
int f(x,s) int x; char *s;
{ printf( "%s:%d ", s, x ); return x; }
main(){
int x = 1;
int y = f(x++, "f1") + f(x+=2, "f2");
printf("%d\n", y);
}
mozhet pechatat' libo
f1:1 f2:4 5
libo
f2:3 f1:3 6
v zavisimosti ot osobennostej povedeniya vashego kompilyatora (kakaya iz dvuh f() vypol-
nitsya pervoj: levaya ili pravaya?). Eshche primer:
int y = 2;
int x = ((y = 4) * y );
printf( "%d\n", x );
Mozhet byt' napechatano libo 16, libo 8 v zavisimosti ot povedeniya kompilyatora, t.e.
dannyj operator nemobilen. Sleduet napisat'
y = 4; x = y * y;
1.84. Zakonen li operator
f(x++, x++); ili f(x, x++);
Otvet: Net, poryadok vychisleniya argumentov funkcij ne opredelen. Po toj zhe prichine my
ne mozhem pisat'
f( c = getchar(), c );
a dolzhny pisat'
c = getchar(); f(c, c);
(esli my imenno eto imeli v vidu). Vot eshche primer:
...
case '+':
push(pop()+pop()); break;
case '-':
push(pop()-pop()); break;
...
A. Bogatyrev, 1992-95 - 37 - Si v UNIX
sleduet zamenit' na
...
case '+':
push(pop()+pop()); break;
case '-':
{ int x = pop(); int y = pop();
push(y - x); break;
}
...
I eshche primer:
int x = 0;
printf( "%d %d\n", x = 2, x ); /* 2 0 libo 2 2 */
Nel'zya takzhe
struct pnt{ int x; int y; }arr[20]; int i=0;
...
scanf( "%d%d", & arr[i].x, & arr[i++].y );
poskol'ku i++ mozhet sdelat'sya ran'she, chem chtenie v x. Eshche primer:
main(){
int i = 3;
printf( "%d %d %d\n", i += 7, i++, i++ );
}
kotoryj pokazyvaet, chto na IBM PC |- i PDP-11 |= argumenty funkcij vychislyayutsya sprava
nalevo (primer pechataet 12 4 3). Vprochem, drugie kompilyatory mogut vychislyat' ih
sleva napravo (kak i podskazyvaet nam zdravyj smysl).
1.85. Programma pechataet libo x=1 libo x=0 v zavisimosti ot KOMPILYATORA - vychislya-
etsya li ran'she pravaya ili levaya chast' operatora vychitaniya:
#include <stdio.h>
void main(){
int c = 1;
int x = c - c++;
printf( "x=%d c=%d\n", x, c );
exit(0);
}
CHto vy imeli v vidu ?
left = c; right = c++; x = left - right;
ili
right = c++; left = c; x = left - right;
A esli kompilyator eshche i rasparallelit vychislenie left i right - to odna programma v
raznye momenty vremeni smozhet davat' raznye rezul'taty.
____________________
|- IBM ("Aj-bi-em") - International Buisiness Machines Corporation. Personal'nye
komp'yutery IBM PC postroeny na baze mikroprocessorov firmy Intel.
|= PDP-11 - (Programmed Data Processor) - komp'yuter firmy DEC (Digital Equipment
Corporation), u nas izvestnyj kak SM-1420. |ta zhe firma vypuskaet mashinu VAX.
A. Bogatyrev, 1992-95 - 38 - Si v UNIX
Vot eshche dostojnaya zadachka:
x = c-- - --c; /* c-----c */
1.86. Napishite programmu, kotoraya ustanavlivaet v 1 bit 3 i sbrasyvaet v 0 bit 6.
Bity v slove numeruyutsya s nulya sprava nalevo. Otvet:
int x = 0xF0;
x |= (1 << 3);
x &= ~(1 << 6);
V programmah chasto ispol'zuyut bitovye maski kak flagi nekotoryh parametrov (priznak -
est' ili net). Naprimer:
#define A 0x08 /* vhod svoboden */
#define B 0x40 /* vyhod svoboden */
ustanovka flagov : x |= A|B;
sbros flagov : x &= ~(A|B);
proverka flaga A : if( x & A ) ...;
proverka, chto oba flaga est': if((x & (A|B)) == (A|B))...;
proverka, chto oboih net : if((x & (A|B)) == 0 )...;
proverka, chto est' hot' odin: if( x & (A|B))...;
proverka, chto est' tol'ko A : if((x & (A|B)) == A)...;
proverka, v kakih flagah
razlichayutsya x i y : diff = x ^ y;
1.87. V programmah inogda trebuetsya ispol'zovat' "mnozhestvo": kazhdyj dopustimyj ele-
ment mnozhestva imeet nomer i mozhet libo prisutstvovat' v mnozhestve, libo otsutstvo-
vat'. CHislo vhozhdenij ne uchityvaetsya. Mnozhestva prinyato modelirovat' pri pomoshchi
bitovyh shkal:
#define SET(n,a) (a[(n)/BITS] |= (1L <<((n)%BITS)))
#define CLR(n,a) (a[(n)/BITS] &= ~(1L <<((n)%BITS)))
#define ISSET(n,a) (a[(n)/BITS] & (1L <<((n)%BITS)))
#define BITS 8 /* bits per char (bitov v bajte) */
/* Perechislimyj tip */
enum fruit { APPLE, PEAR, ORANGE=113,
GRAPES, RAPE=125, CHERRY};
/* shkala: n iz intervala 0..(25*BITS)-1 */
static char fr[25];
main(){
SET(GRAPES, fr); /* dobavit' v mnozhestvo */
if(ISSET(GRAPES, fr)) printf("here\n");
CLR(GRAPES, fr); /* udalit' iz mnozhestva */
}
1.88. Napishite programmu, raspechatyvayushchuyu vse vozmozhnye perestanovki massiva iz N
elementov. Algoritm budet rekursivnym, naprimer takim: v kachestve pervogo elementa
perestanovki vzyat' i-yj element massiva. Iz ostavshihsya elementov massiva (esli takie
est') sostavit' vse perestanovki poryadka N-1. Vydat' vse perestanovki poryadka N,
poluchayushchiesya sklejkoj i-ogo elementa i vseh (po ocheredi) perestanovok poryadka N-1.
Vzyat' sleduyushchee i i vse povtorit'.
Glavnaya problema zdes' - organizovat' ostavshiesya posle izvlecheniya i-ogo elementa
elementy massiva v udobnuyu strukturu dannyh (chtoby postoyanno ne kopirovat' massiv).
Mozhno ispol'zovat', naprimer, bitovuyu shkalu uzhe vybrannyh elementov. Vospol'zuemsya
dlya etogo makrosami iz predydushchego paragrafa:
A. Bogatyrev, 1992-95 - 39 - Si v UNIX
/* GENERATOR PERESTANOVOK IZ n |LEMENTOV PO m */
extern void *calloc(unsigned nelem, unsigned elsize);
/* Dinamicheskij vydelitel' pamyati, zachishchennoj nulyami.
* |to standartnaya bibliotechnaya funkciya.
* Obratnaya k nej - free();
*/
extern void free(char *ptr);
static int N, M, number;
static char *scale; /* shkala vybrannyh elementov */
int *res; /* rezul'tat */
/* ... tekst opredelenij SET, CLR, ISSET, BITS ... */
static void choose(int ind){
if(ind == M){ /* raspechatat' perestanovku */
register i;
printf("Rasstanovka #%04d", ++number);
for(i=0; i < M; i++) printf(" %2d", res[i]);
putchar('\n'); return;
} else
/* Vybrat' ocherednoj ind-tyj element perestanovki
* iz chisla eshche ne vybrannyh elementov.
*/
for(res[ind] = 0; res[ind] < N; ++res[ind])
if( !ISSET(res[ind], scale)) {
/* element eshche ne byl vybran */
SET(res[ind], scale); /* vybrat' */
choose(ind+1);
CLR(res[ind], scale); /* osvobodit' */
}
}
void arrange(int n, int m){
res = (int *) calloc(m, sizeof(int));
scale = (char *) calloc((n+BITS-1)/BITS, 1);
M = m; N = n; number = 0;
if( N >= M ) choose(0);
free((char *) res); free((char *) scale);
}
void main(int ac, char **av){
if(ac != 3){ printf("Arg count\n"); exit(1); }
arrange(atoi(av[1]), atoi(av[2]));
}
Programma dolzhna vydat' n!/(n-m)! rasstanovok, gde x! = 1*2*...*x - funkciya "fakto-
rial". Po opredeleniyu 0! = 1. Poprobujte peredelat' etu programmu tak, chtoby oche-
rednaya perestanovka pechatalas' po zaprosu:
res = init_iterator(n, m);
/* pechatat' varianty, poka o