Si v UNIX
2.18. CHto oznachayut opisaniya?
int i; // celoe.
int *pi; // ukazatel' na celoe.
int *api[3]; // massiv iz 3h uk-lej na celye.
int (*pai)[3]; // ukazatel' na massiv iz 3h celyh.
// mozhno opisat' kak int **pai;
int fi(); // funkciya, vozvrashchayushchaya celoe.
int *fpi(); // f-ciya, vozvr. uk-l' na celoe.
int (*pfi)(); // uk-l' na f-ciyu, vozvrashchayushchuyu celoe.
int *(*pfpi)(); // uk-l' na f-ciyu, vozvr. uk-l' na int.
int (*pfpfi())(); // f-ciya, vozvrashchayushchaya ukazatel' na
// "funkciyu, vozvrashchayushchuyu celoe".
int (*fai())[3]; // f-ciya, vozvr. uk-l' na massiv
// iz 3h celyh. inache ee
// mozhno opisat' kak int **fai();
int (*apfi[3])(); // massiv iz 3h uk-lej na funkcii,
// vozvrashchayushchie celye.
Peremennye v Si opisyvayutsya v formate ih ispol'zovaniya. Tak opisanie
int (*f)();
oznachaet, chto f mozhno ispol'zovat' v vide
int value;
value = (*f)(1, 2, 3 /* spisok argumentov */);
Odnako iz takogo sposoba opisaniya tip samoj opisyvaemoj peremennoj i ego smysl
dovol'no neochevidny. Privedem priem (pozaimstvovannyj iz zhurnala "Communications of
the ACM"), pozvolyayushchij proyasnit' smysl opisaniya. Opisanie na Si perevoditsya v opisa-
nie v stile yazyka Algol-68. Dalee
ref TIP oznachaet "ukazatel' na TIP"
proc() TIP "funkciya, vozvrashchayushchaya TIP"
array of TIP "massiv iz elementov TIPa"
x: TIP "x imeet tip TIP"
Privedem neskol'ko primerov, iz kotoryh yasen i sposob preobrazovaniya:
int (*f())(); oznachaet
(*f())() : int
*f() : proc() int
f() : ref proc() int
f : proc() ref proc() int
to est' f - funkciya, vozvrashchayushchaya ukazatel' na funkciyu, vozvrashchayushchuyu celoe.
int (*f[3])(); oznachaet
(*f[])() : int
*f[] : proc() int
f[] : ref proc() int
f : array of ref proc() int
f - massiv ukazatelej na funkcii, vozvrashchayushchie celye. Obratno: opishem g kak ukaza-
tel' na funkciyu, vozvrashchayushchuyu ukazatel' na massiv iz 5i ukazatelej na funkcii, vozv-
rashchayushchie ukazateli na celye.
A. Bogatyrev, 1992-95 - 91 - Si v UNIX
g : ref p() ref array of ref p() ref int
*g : p() ref array of ref p() ref int
(*g)() : ref array of ref p() ref int
*(*g)() : array of ref p() ref int
(*(*g)())[5] : ref p() ref int
*(*(*g)())[5] : p() ref int
(*(*(*g)())[5])(): ref int
*(*(*(*g)())[5])(): int
int *(*(*(*g)())[5])();
V Si nevozmozhny funkcii, vozvrashchayushchie massiv:
proc() array of ...
a tol'ko
proc() ref array of ...
Samo nazvanie tipa (naprimer, dlya ispol'zovaniya v operacii privedeniya tipa) polucha-
etsya vycherkivaniem imeni peremennoj (a takzhe mozhno opustit' razmer massiva):
g = ( int *(*(*(*)())[])() ) 0;
2.19. Napishite funkciyu strcat(d,s), pripisyvayushchuyu stroku s k koncu stroki d.
Otvet:
char *strcat(d,s) register char *d, *s;
{ while( *d ) d++; /* ishchem konec stroki d */
while( *d++ = *s++ ); /* strcpy(d, s) */
return (d-1); /* konec stroki */
}
Cikl, pomechennyj "strcpy" - eto naibolee kratkaya zapis' operatorov
do{ char c;
c = (*d = *s); s++; d++;
} while(c != '\0');
Na samom dele strcat dolzhen po standartu vozvrashchat' svoj pervyj argument, kak i funk-
ciya strcpy:
char *strcat(d,s) register char *d, *s;
{ char *p = d;
while( *d ) d++;
strcpy(d, s); return p;
}
|ti dva varianta demonstriruyut, chto funkciya mozhet byt' realizovana raznymi sposobami.
Krome togo vidno, chto vmesto standartnoj bibliotechnoj funkcii my mozhem opredelit'
svoyu odnoimennuyu funkciyu, neskol'ko otlichayushchuyusya povedeniem ot standartnoj (kak vozv-
rashchaemoe znachenie v 1-om variante).
2.20. Napishite programmu, kotoraya ob容dinyaet i raspechatyvaet dve stroki, vvedennye s
terminala. Dlya vvoda strok ispol'zujte funkciyu gets(), a dlya ih ob容dineniya -
strcat(). V drugom variante ispol'zujte
sprintf(result,"%s%s",s1,s2);
2.21. Modificirujte predydushchuyu programmu takim obrazom, chtoby ona vydavala dlinu
(chislo simvolov) ob容dinennoj stroki. Ispol'zujte funkciyu strlen(). Privedem nes-
kol'ko versij realizacii strlen:
/* Pri pomoshchi indeksacii massiva */
A. Bogatyrev, 1992-95 - 92 - Si v UNIX
int strlen(s) char s[];
{ int length = 0;
for(; s[length] != '\0'; length++);
return (length);
}
/* Pri pomoshchi prodvizheniya ukazatelya */
int strlen(s) char *s;
{ int length;
for(length=0; *s; length++, s++);
return length;
}
/* Pri pomoshchi raznosti ukazatelej */
int strlen(register char *s)
{ register char *p = s;
while(*p) p++; /* ishchet konec stroki */
return (p - s);
}
Raznost' dvuh ukazatelej na odin i tot zhe tip - celoe chislo:
esli TYPE *p1, *p2;
to p2 - p1 = celoe chislo shtuk TYPE
lezhashchih mezhdu p2 i p1
esli p2 = p1 + n
to p2 - p1 = n
|ta raznost' mozhet byt' i otricatel'noj esli p2 < p1, to est' p2 ukazyvaet na bolee
levyj element massiva.
2.22. Napishite operator Si, kotoryj obrubaet stroku s do dliny n bukv. Otvet:
if( strlen(s) > n )
s[n] = '\0';
Pervoe sravnenie voobshche govorya izlishne. Ono napisano lish' na tot sluchaj, esli stroka
s koroche, chem n bukv i hranitsya v massive, kotoryj takzhe koroche n, t.e. ne imeet n-
ogo elementa (poetomu v nego nel'zya proizvodit' zapis' priznaka konca).
2.23. Napishite funkcii preobrazovaniya stroki, soderzhashchej izobrazhenie celogo chisla, v
samo eto chislo. V dvuh raznyh variantah argument-adres dolzhen ukazyvat' na pervyj
bajt stroki; na poslednij bajt. Otvet:
#define isdigit(c) ('0' <= (c) && (c) <= '9')
int atoi(s) register char *s;
{ register int res=0, neg=0;
for(;;s++){
switch(*s){
case ' ': case '\t': continue;
case '-': neg++;
case '+': s++;
} break;
}
while(isdigit(*s))
res = res * 10 + *s++ - '0';
return( neg ? -res : res );
}
int backatoi(s) register char *s;
{ int res=0, pow=1;
while(isdigit(*s)){
A. Bogatyrev, 1992-95 - 93 - Si v UNIX
res += (*s-- - '0') * pow;
pow *= 10;
}
if(*s == '-') res = -res;
return res;
}
2.24. Mozhno li dlya zaneseniya v massiv s stroki "hello" napisat'
char s[6]; s = "hello";
ili
char s[6], d[] = "hello"; s = d;
Otvet: net. Massivy v Si nel'zya prisvaivat' celikom. Dlya peresylki massiva bajt nado
ispol'zovat' funkciyu strcpy(s,d). Zdes' zhe my pytaemsya izmenit' adres s (imya massiva
- eto adres nachala pamyati, vydelennoj dlya hraneniya massiva), sdelav ego ravnym adresu
bezymyannoj stroki "hello" (ili massiva d vo vtorom sluchae). |tot adres yavlyaetsya
konstantoj i ne mozhet byt' izmenen!
Zametim odnako, chto opisanie massiva s inicializaciej vpolne dopustimo:
char s[6] = "hello";
ili
char s[6] = { 'h', 'e', 'l', 'l', 'o', '\0' };
ili
char s[] = "hello";
ili
char s[] = { "hello" };
V etom sluchae kompilyator rezerviruet pamyat' dlya hraneniya massiva i raspisyvaet ee
bajtami nachal'nogo znacheniya. Obratite vnimanie, chto stroka v dvojnyh kavychkah (esli
ee rassmatrivat' kak massiv bukv) imeet dlinu na edinicu bol'she, chem napisano bukv v
stroke, poskol'ku v konce massiva nahoditsya simvol '\0' - priznak konca, dobavlennyj
kompilyatorom. Esli by my napisali
char s[5] = "hello";
to kompilyator soobshchil by ob oshibke, poskol'ku dliny massiva (5) nedostatochno, chtoby
razmestit' 6 bajt. V tret'ej stroke primera napisano s[], chtoby kompilyator sam pos-
chital neobhodimuyu dlinu massiva.
Nakonec, vozmozhna situaciya, kogda massiv bol'she, chem hranyashchayasya v nem stroka.
Togda "lishnee" mesto soderzhit kakoj-to musor (v static-pamyati iznachal'no - bajty \0).
char s[12] = "hello";
soderzhit: h e l l o \0 ? ? ? ? ? ?
V programmah tekstovoj obrabotki pod "dlinoj stroki" obychno ponimayut kolichestvo bukv
v stroke NE schitaya zakryvayushchij bajt '\0'. Imenno takuyu dlinu schitaet standartnaya
funkciya strlen(s). Poetomu sleduet razlichat' takie ponyatiya kak "(tekushchaya) dlina
stroki" i "dlina massiva, v kotorom hranitsya stroka": sizeof(s). Dlya napisannogo
vyshe primera eti znacheniya ravny sootvetstvenno 5 i 12.
Sleduet takzhe otlichat' massivy ot ukazatelej:
char *sp = "bye bye";
sp = "hello";
budet vpolne zakonno, poskol'ku v dannom sluchae sp - ne imya massiva (t.e. konstanta,
ravnaya adresu nachala massiva), a ukazatel' (peremennaya, hranyashchaya adres nekotoroj
oblasti pamyati). Poskol'ku ukazatel' - eto peremennaya, to ee znachenie izmenyat'
mozhno: v dannom sluchae sp snachala soderzhala adres bezymyannogo massiva, v kotorom
nahoditsya "bye bye"; zatem my zanesli v sp adres bezymyannogo massiva, hranyashchego
A. Bogatyrev, 1992-95 - 94 - Si v UNIX
stroku "hello". Zdes' ne proishodit kopirovaniya massiva, a proishodit prosto prisva-
ivanie peremennoj sp novogo znacheniya adresa.
Predosterezhem ot vozmozhnoj nepriyatnosti:
char d[5]; char s[] = "abcdefgh";
strcpy(d, s);
Dliny massiva d prosto ne hvatit dlya hraneniya takoj dlinnoj stroki. Poskol'ku eto
nichem ne kontroliruetsya (ni kompilyatorom, ni samoj strcpy, ni vami yavnym obrazom), to
pri kopirovanii stroki "izbytochnye" bajty zapishutsya posle massiva d poverh drugih
dannyh, kotorye budut isporcheny. |to privedet k nepredskazuemym effektam.
Nekotorye vozmozhnosti dlya kontrolya za dlinoj strok-argumentov vam dayut funkcii
strncpy(d,s,len); strncat(d,s,len); strncmp(s1,s2,len). Oni peresylayut (sravnivayut)
ne bolee, chem len pervyh simvolov stroki s (strok s1, s2). Posmotrite v dokumenta-
ciyu! Napishite funkciyu strncmp (sravnenie strok po pervym len simvolam), posmotrev na
funkciyu strncpy:
char *strncpy(dst, src, n)
register char *dst, *src;
register int n;
{ char *save;
for(save=dst; --n >= 0; )
if( !(*dst++ = *src++)){
while(--n >= 0)
*dst++ = '\0';
return save;
}
return save;
}
Otmet'te, chto strncpy obladaet odnim nepriyatnym svojstvom: esli n <= strlen(src), to
stroka dst ne budet imet' na konce simvola '\0', to est' budet nahodit'sya v nekor-
rektnom (ne kanonicheskom) sostoyanii.
Otvet:
int strncmp(register char *s1, register char *s2, register int n)
{
if(s1 == s2)
return(0);
while(--n >= 0 && *s1 == *s2++)
if(*s1++ == '\0')
return(0);
return((n < 0)? 0: (*s1 - *--s2));
}
2.25. V chem oshibka?
#include <stdio.h> /* dlya putchar */
char s[] = "We don't need no education";
main(){ while(*s) putchar(*s++); }
Otvet: zdes' s - konstanta, k nej neprimenima operaciya ++. Nado napisat'
char *s = "We don't need no education";
sdelav s ukazatelem na bezymyannyj macciv. Ukazatel' uzhe mozhno izmenyat'.
2.26. Kakie iz privedennyh konstrukcij oboznachayut odno i to zhe?
A. Bogatyrev, 1992-95 - 95 - Si v UNIX
char a[] = ""; /* pustaya stroka */
char b[] = "\0";
char c = '\0';
char z[] = "ab";
char aa[] = { '\0' };
char bb[] = { '\0', '\0' };
char xx[] = { 'a', 'b' };
char zz[] = { 'a', 'b', '\0' };
char *ptr = "ab";
2.27. Najdite oshibki v opisanii simvol'noj stroki:
main() {
char mas[] = {'s', 'o', 'r', 't'}; /* "sort" ? */
printf("%s\n", mas);
}
Otvet: stroka dolzhna konchat'sya '\0' (v nashem sluchae printf ne obnaruzhiv simvola konca
stroki budet vydavat' i bajty, nahodyashchiesya v pamyati posle massiva mas, t.e. musor);
inicializirovannyj massiv ne mozhet byt' avtomaticheskim - trebuetsya static:
main() {
static char mas[] = {'s', 'o', 'r', 't', '\0'};
}
Zametim, chto
main(){ char *mas = "sort"; }
zakonno, t.k. sama stroka zdes' hranitsya v staticheskoj pamyati, a inicializiruetsya
lish' ukazatel' na etot massiv bajt.
2.28. V chem oshibka? Programma sobiraetsya iz dvuh fajlov: a.c i b.c komandoj
cc a.c b.c -o ab
a.c b.c
---------------------------------------------------
int n = 2; extern int n;
char s[] = "012345678"; extern char *s;
main(){ f(){
f(); s[n] = '+';
printf("%s\n", s ); }
}
Otvet: delo v tom, chto tipy (char *) - ukazatel', i char[] - massiv, oznachayut odno i
to zhe tol'ko pri ob座avlenii formal'nogo parametra funkcii:
f(char *arg){...} f(char arg[]){...}
eto budet lokal'naya peremennaya, soderzhashchaya ukazatel' na char (t.e. adres nekotorogo
bajta v pamyati). Vnutri funkcii my mozhem izmenyat' etu peremennuyu, naprimer arg++.
Dalee, i (char *) i char[] odinakovo ispol'zuyutsya, naprimer, oba eti tipa mozhno
indeksirovat': arg[i]. No vne funkcij oni ob座avlyayut raznye ob容kty! Tak char *p; eto
skalyarnaya peremennaya, hranyashchaya adres (ukazatel'):
-------- -------
p:| *--|----->| '0' | char
-------- | '1' | char
...
A. Bogatyrev, 1992-95 - 96 - Si v UNIX
togda kak char a[20]; eto adres nachala massiva (a vovse ne peremennaya):
-------
a:| '0' | char
| '1' | char
...
V nashem primere v fajle b.c my ob座avili vneshnij massiv s kak peremennuyu. V rezul'-
tate kompilyator budet interpretirovat' nachalo massiva s kak peremennuyu, soderzhashchuyu
ukazatel' na char.
-------
s:| '0' | \ eto budet vosprinyato kak
| '1' | / adres drugih dannyh.
| '2' |
...
I indeksirovat'sya budet uzhe |TOT adres! Rezul'tat - obrashchenie po nesushchestvuyushchemu
adresu. To, chto napisano u nas, ekvivalentno
char s[] = "012345678";
char **ss = s; /* s - kak by "massiv ukazatelej" */
/* pervye bajty s interpretiruyutsya kak ukazatel': */
char *p = ss[0];
p[2] = '+';
My zhe dolzhny byli ob座avit' v b.c
extern char s[]; /* razmer ukazyvat' ne trebuetsya */
Vot eshche odin analogichnyj primer, kotoryj poyasnit vam, chto proishodit (a zaodno poka-
zhet poryadok bajtov v long). Primer vypolnyalsya na IBM PC 80386, na kotoroj
sizeof(char *) = sizeof(long) = 4
a.c b.c
---------------------------------------------------
char s[20] = {1,2,3,4}; extern char *s;
main(){ f(){
/*pechat' ukazatelya kak long */
f(); printf( "%08lX\n", s );
} }
pechataetsya 04030201.
2.29. CHto napechataet programma?
static char str1[ ] = "abc";
static char str2[4];
strcpy( str2, str1 );
/* mozhno li napisat' str2 = str1; ? */
printf( str1 == str2 ? "ravno":"ne ravno" );
Kak nado pravil'no sravnivat' stroki? CHto na samom dele sravnivaetsya v dannom pri-
mere?
Otvet: sravnivayutsya adresa massivov, hranyashchih stroki. Tak
A. Bogatyrev, 1992-95 - 97 - Si v UNIX
char str1[2];
char str2[2];
main(){
printf( str1 < str2 ? "<":">");
}
pechataet <, a esli napisat'
char str2[2];
char str1[2];
to napechataetsya >.
2.30. Napishite programmu, sprashivayushchuyu vashe imya do teh por, poka vy ego pravil'no ne
vvedete. Dlya sravneniya strok ispol'zujte funkciyu strcmp() (ee realizaciya est' v glave
"Mobil'nost'").
2.31. Kakie znacheniya vozvrashchaet funkciya strcmp() v sleduyushchej programme?
#include <stdio.h>
main() {
printf("%d\n", strcmp("abc", "abc")); /* 0 */
printf("%d\n", strcmp("ab" , "abc")); /* -99 */
printf("%d\n", strcmp("abd", "abc")); /* 1 */
printf("%d\n", strcmp("abc", "abd")); /* -1 */
printf("%d\n", strcmp("abc", "abe")); /* -2 */
}
2.32. V kachestve itoga predydushchih zadach: pomnite, chto v Si stroki (a ne adresa) nado
sravnivat' kak
if( strcmp("abc", "bcd") < 0) ... ;
if( strcmp("abc", "bcd") == 0) ... ;
vmesto
if( "abc" < "bcd" ) ... ;
if( "abc" == "bcd" ) ... ;
i prisvaivat' kak
char d[80], s[80];
strcpy( d, s ); vmesto d = s;
2.33. Napishite programmu, kotoraya sortiruet po alfavitu i pechataet sleduyushchie klyuche-
vye slova yazyka Si:
int char double long
for while if
2.34. Vopros ne sovsem pro stroki, skoree pro cikl: chem ploha konstrukciya?
char s[] = "You're a smart boy, now shut up.";
int i, len;
for(i=0; i < strlen(s); i++)
putchar(s[i]);
Otvet: v sootvetstvii s semantikoj Si cikl razvernetsya primerno v
A. Bogatyrev, 1992-95 - 98 - Si v UNIX
i=0;
LOOP: if( !(i < strlen(s))) goto ENDLOOP;
putchar(s[i]);
i++;
goto LOOP;
ENDLOOP: ;
Zamet'te, chto hotya dlina stroki s ne menyaetsya, strlen(s) vychislyaetsya na KAZHDOJ itera-
cii cikla, sovershaya lishnyuyu rabotu! Bor'ba s etim takova:
for(i=0, len=strlen(s); i < len; i++ )
putchar(s[i]);
ili
for(i=0, len=strlen(s); len > 0; i++, --len )
putchar(s[i]);
Analogichno, v cikle
while( i < strlen(s))...;
funkciya tozhe budet vychislyat'sya pri kazhdoj proverke usloviya! |to, konechno, otnositsya k
lyuboj funkcii, ispol'zuemoj v uslovii, a ne tol'ko k strlen. (No, razumeetsya, sluchaj
kogda funkciya vozvrashchaet priznak "nado li prodolzhat' cikl" - sovsem drugoe delo:
takaya funkciya obyazana vychislyat'sya kazhdyj raz).
2.35. CHto napechataet sleduyushchaya programma?
#include <stdio.h>
main(){
static char str[] = "Do vstrechi v bufete";
char *pt;
pt = str; puts(pt); puts(++pt);
str[7] = '\0'; puts(str); puts(pt);
puts(++pt);
}
2.36. CHto napechataet sleduyushchaya programma?
main() {
static char name[] = "Konstantin";
char *pt;
pt = name + strlen(name);
while(--pt >= name)
puts(pt);
}
2.37. CHto napechataet sleduyushchaya programma?
char str1[] = "abcdef";
char str2[] = "xyz";
main(){
register char *a, *b;
a = str1; b = str2;
while( *b )
*a++ = *b++;
printf( "str=%s a=%s\n", str1, a );
a = str1; b = str2;
A. Bogatyrev, 1992-95 - 99 - Si v UNIX
while( *b )
*++a = *b++;
printf( "str=%s a=%s\n", str1, a );
}
Otvet:
str=xyzdef a=def
str=xxyzef a=zef
2.38. CHto pechataet programma?
char *s;
for(s = "Sitroen"; *s; s+= 2){
putchar(s[0]); if(!s[1]) break;
}
putchar('\n');
2.39. CHto napechataet programma? Rassmotrite prodvizhenie ukazatelya s, ukazatelej -
elementov massiva strs[]. Razberites' s poryadkom vypolneniya operacij. V kakih sluchayah
++ izmenyaet ukazatel', a v kakih - bukvu v stroke? Narisujte sebe kartinku, izobrazha-
yushchuyu sostoyanie ukazatelej - ona pomozhet vam rasputat' eti spagetti. Udelite razboru
etogo primera dostatochnoe vremya!
#include <stdio.h> /* opredelenie NULL */
/* Latinskij alfavit: abcdefghijklmnopqrstuvwxyz */
char *strs[] = {
"abcd","ABCD","0fpx","159",
"hello","-gop","A1479",NULL
};
main(){
char c, **s = strs, *p;
c = *++*s; printf("#1 %d %c %s\n", s-strs, c, *s);
c = **++s; printf("#2 %d %c %s\n", s-strs, c, *s);
c = **s++; printf("#3 %d %c %s\n", s-strs, c, *s);
c = ++**s; printf("#4 %d %c %s\n", s-strs, c, *s);
c = (**s)++; printf("#5 %d %c %s\n", s-strs, c, *s);
c = ++*++*s; printf("#6 %d %c %s\n", s-strs, c, *s);
c = *++*s++; printf("#7 %d %c %s %s\n",
s-strs, c, *s, strs[2]);
c = ++*++*s++; printf("#8 %d %c %s %s\n",
s-strs, c, *s, strs[3]);
c = ++*++*++s; printf("#9 %d %c %s\n", s-strs,c,*s);
c = ++**s++; printf("#10 %d %c %s\n",s-strs,c,*s);
p = *s; c = ++*(*s)++;
printf("#11 %d %c %s %s %s\n",s-strs,c,*s,strs[6],p);
c = ++*((*s)++); printf("#12 %d %c %s %s\n",
s-strs, c, *s, strs[6]);
c = (*++(*s))++; printf("#13 %d %c %s %s\n",
s-strs, c, *s, strs[6]);
for(s=strs; *s; s++)
printf("strs[%d]=\"%s\"\n", s-strs, *s);
putchar('\n');
}
Pechataetsya:
A. Bogatyrev, 1992-95 - 100 - Si v UNIX
#1 0 b bcd strs[0]="bcd"
#2 1 A ABCD strs[1]="ABCD"
#3 2 A 0fpx strs[2]="px"
#4 2 1 1fpx strs[3]="69"
#5 2 1 2fpx strs[4]="hello"
#6 2 g gpx strs[5]="iop"
#7 3 p 159 px strs[6]="89"
#8 4 6 hello 69
#9 5 h hop
#10 6 i A1479
#11 6 B 1479 1479 B1479
#12 6 2 479 479
#13 6 7 89 89
Uchtite, chto konstrukciya
char *strs[1] = { "hello" };
oznachaet, chto v strs[0] soderzhitsya ukazatel' na nachal'nyj bajt bezymyannogo massiva,
soderzhashchego stroku "hello". |tot ukazatel' mozhno izmenyat'! Poprobujte sostavit' eshche
podobnye primery iz *, ++, ().
2.40. CHto pechataet programma?
char str[25] = "Hi, ";
char *f(char **s){ int cnt;
for(cnt=0; **s != '\0'; (*s)++, ++cnt);
return("ny" + (cnt && (*s)[-1] == ' ') + (!cnt));
}
void main(void){ char *s = str;
if( *f(&s) == 'y') strcat(s, "dude");
else strcat(s, " dude");
printf("%s\n", str);
}
CHto ona napechataet, esli zadat'
char str[25]="Hi,"; ili char str[25]="";
2.41. V chem sostoit oshibka? (Lyubimaya oshibka nachinayushchih)
main(){
char *buf; /* ili char buf[]; */
gets( buf );
printf( "%s\n", buf );
}
Otvet: pamyat' pod stroku buf ne vydelena, ukazatel' buf ne proinicializirovan i smot-
rit neizvestno kuda. Nado bylo pisat' naprimer tak:
char buf[80];
ili
char mem[80], *buf = mem;
Obratite na etot primer osoboe vnimanie, poskol'ku, opisav ukazatel' (no nikuda ego
ne napraviv), novichki uspokaivayutsya, ne zabotyas' o vydelenii pamyati dlya hraneniya dan-
nyh. Ukazatel' dolzhen ukazyvat' na CHTO-TO, v chem mozhno hranit' dannye, a ne "viset'",
ukazyvaya "pal'cem v nebo"! Zapis' informacii po "visyachemu" ukazatelyu razrushaet pamyat'
programmy i privodit k skoromu (no chasto ne nemedlennomu i potomu tainstvennomu)
krahu.
A. Bogatyrev, 1992-95 - 101 - Si v UNIX
Vot programma, kotoraya takzhe ispol'zuet neinicializirovannyj ukazatel'. Na
mashine SPARCstation 20 eta programma ubivaetsya operacionnoj sistemoj s diagnostikoj
"Segmentation fault" (SIGSEGV). |to kak raz i znachit obrashchenie po ukazatelyu, ukazy-
vayushchemu "pal'cem v nebo".
main(){
int *iptr;
int ival = *iptr;
printf("%d\n", ival);
}
2.42. Dlya polucheniya stroki "Life is life" napisana programma:
main(){
char buf[ 60 ];
strcat( buf, "Life " );
strcat( buf, "is " );
strcat( buf, "life" );
printf( "%s\n", buf );
}
CHto okazhetsya v massive buf?
Otvet: v nachale massiva okazhetsya musor, poskol'ku avtomaticheskij massiv ne iniciali-
ziruetsya bajtami '\0', a funkciya strcat() pripisyvaet stroki k koncu stroki. Dlya isp-
ravleniya mozhno napisat'
*buf = '\0';
pered pervym strcat()-om, libo vmesto pervogo strcat()-a napisat'
strcpy( buf, "Life " );
2.43. Sostav'te makroopredelenie copystr(s1, s2) dlya kopirovaniya stroki s2 v stroku
s1.
2.44. Sostav'te makroopredelenie lenstr(s) dlya vychisleniya dliny stroki.
Mnogie sovremennye kompilyatory sami obrashchayutsya s podobnymi korotkimi (1-3 opera-
tora) standartnymi funkciyami kak s makrosami, to est' pri obrashchenii k nim generyat ne
vyzov funkcii, a podstavlyayut tekst ee tela v mesto obrashcheniya. |to delaet ob容ktnyj
kod neskol'ko "tolshche", no zato bystree. V rasshirennyh dialektah Si i v Si++ kompilya-
toru mozhno predlozhit' obrashchat'sya tak i s vashej funkciej - dlya etogo funkciyu sleduet
ob座avit' kak inline (takie funkcii nazyvayutsya eshche "intrinsic").
2.45. Sostav'te rekursivnuyu i nerekursivnuyu versii programmy invertirovaniya (zer-
kal'nogo otobrazheniya) stroki:
abcdef --> fedcba.
2.46. Sostav'te funkciyu index(s, t), vozvrashchayushchuyu nomer pervogo vhozhdeniya simvola t
v stroku s; esli simvol t v stroku ne vhodit, funkciya vozvrashchaet -1.
Perepishite etu funkciyu s ukazatelyami, chtoby ona vozvrashchala ukazatel' na pervoe
vhozhdenie simvola. Esli simvol v stroke otsutstvuet - vydavat' NULL. V UNIX System-V
takaya funkciya nazyvaetsya strchr. Vot vozmozhnyj otvet:
char *strchr(s, c) register char *s, c;
{ while(*s && *s != c) s++;
return *s == c ? s : NULL;
}
A. Bogatyrev, 1992-95 - 102 - Si v UNIX
Zamet'te, chto p=strchr(s,'\0'); vydaet ukazatel' na konec stroki. Vot primer ispol'-
zovaniya:
extern char *strchr();
char *s = "abcd/efgh/ijklm";
char *p = strchr(s, '/');
printf("%s\n", p==NULL ? "bukvy / net" : p);
if(p) printf("Indeks vhozhdeniya = s[%d]\n", p - s );
2.47. Napishite funkciyu strrchr(), ukazyvayushchuyu na poslednee vhozhdenie simvola.
Otvet:
char *strrchr(s, c) register char *s, c;
{ char *last = NULL;
do if(*s == c) last = s; while(*s++);
return last;
}
Vot primer ee ispol'zovaniya:
extern char *strrchr();
char p[] = "wsh"; /* etalon */
main(argc, argv) char *argv[];{
char *s = argv[1]; /* proveryaemoe imya */
/* poprobujte vyzyvat'
* a.out csh
* a.out /bin/csh
* a.out wsh
* a.out /usr/local/bin/wsh
*/
char *base =
(base = strrchr(s, '/')) ? base+1 : s;
if( !strcmp(p, base))
printf("Da, eto %s\n" , p);
else printf("Net, eto %s\n", base);
/* eshche bolee izoshchrennyj variant: */
if( !strcmp(p,(base=strrchr(s,'/')) ? ++base :
(base=s))
) printf("Yes %s\n", p);
else printf("No %s\n", base);
}
2.48. Napishite makros substr(to,from,n,len) kotoryj zapisyvaet v to kusok stroki
from nachinaya s n-oj pozicii i dlinoj len. Ispol'zujte standartnuyu funkciyu strncpy.
Otvet:
#define substr(to, from, n, len) strncpy(to, from+n, len)
ili bolee korrektnaya funkciya:
A. Bogatyrev, 1992-95 - 103 - Si v UNIX
char *substr(to, from, n, len) char *to, *from;
{
int lfrom = strlen(from);
if(n < 0 ){ len += n; n = 0; }
if(n >= lfrom || len <= 0)
*to = '\0'; /* pustaya stroka */
else{
/* dlina ostatka stroki: */
if(len > lfrom-n) len = lfrom - n;
strncpy(to, from+n, len);
to[len] = '\0';
}
return to;
}
2.49. Napishite funkciyu, proveryayushchuyu, okanchivaetsya li stroka na ".abc", i esli net -
pripisyvayushchuyu ".abc" k koncu. Esli zhe stroka uzhe imeet takoe okonchanie - nichego ne
delat'. |ta funkciya polezna dlya generacii imen fajlov s zadannym rasshireniem. Sde-
lajte rasshirenie argumentom funkcii.
Dlya sravneniya konca stroki s so strokoj p sleduet ispol'zovat':
int ls = strlen(s), lp = strlen(p);
if(ls >= lp && !strcmp(s+ls-lp, p)) ...sovpali...;
2.50. Napishite funkcii vstavki simvola c v ukazannuyu poziciyu stroki (s razdvizhkoj
stroki) i udaleniya simvola v zadannoj pozicii (so sdvizhkoj stroki). Stroka dolzhna
izmenyat'sya "na meste", t.e. nikuda ne kopiruyas'. Otvet:
/* udalenie */
char delete(s, at) register char *s;
{
char c;
s += at; if((c = *s) == '\0') return c;
while( s[0] = s[1] ) s++;
return c;
}
/* libo prosto strcpy(s+at, s+at+1); */
/* vstavka */
insert(s, at, c) char s[], c;
{
register char *p;
s += at; p = s;
while(*p) p++; /* na konec stroki */
p[1] = '\0'; /* zakryt' stroku */
for( ; p != s; p-- )
p[0] = p[-1];
*s = c;
}
2.51. Sostav'te programmu udaleniya simvola c iz stroki s v kazhdom sluchae, kogda on
vstrechaetsya.
Otvet:
A. Bogatyrev, 1992-95 - 104 - Si v UNIX
delc(s, c) register char *s; char c;
{
register char *p = s;
while( *s )
if( *s != c ) *p++ = *s++;
else s++;
*p = '\0'; /* ne zabyvajte zakryvat' stroku ! */
}
2.52. Sostav'te programmu udaleniya iz stroki S1 kazhdogo simvola, sovpadayushchego s
kakim-libo simvolom stroki S2.
2.53. Sostav'te funkciyu scopy(s,t), kotoraya kopiruet stroku s v t, pri etom simvoly
tabulyacii i perevoda stroki dolzhny zamenyat'sya na special'nye dvuhsimvol'nye posledo-
vatel'nosti "\n" i "\t". Ispol'zujte switch.
2.54. Sostav'te funkciyu, kotoraya "ukorachivaet" stroku, zamenyaya izobrazheniya specsim-
volov (vrode "\n") na sami eti simvoly ('\n'). Otvet:
extern char *strchr();
void unquote(s) char *s;
{ static char from[] = "nrtfbae",
to [] = "\n\r\t\f\b\7\33";
char c, *p, *d;
for(d=s; c = *s; s++)
if( c == '\\'){
if( !(c = *++s)) break;
p = strchr(from, c);
*d++ = p ? to[p - from] : c;
}else *d++ = c;
*d = '\0';
}
2.55. Napishite programmu, zamenyayushchuyu v stroke S vse vhozhdeniya podstroki P na stroku
Q, naprimer:
P = "ura"; Q = "oj";
S = "ura-ura-ura!";
Rezul'tat: "oj-oj-oj!"
2.56. Krome funkcij raboty so strokami (gde predpolagaetsya, chto massiv bajt zaversha-
etsya priznakom konca '\0'), v Si predusmotreny takzhe funkcii dlya raboty s massivami
bajt bez ogranichitelya. Dlya takih funkcij neobhodimo yavno ukazyvat' dlinu obrabatyvae-
mogo massiva. Napishite funkcii: peresylki massiva dlinoj n bajt memcpy(dst,src,n);
zapolneniya massiva simvolom c memset(s,c,n); poiska vhozhdeniya simvola v massiv
memchr(s,c,n); sravneniya dvuh massivov memcmp(s1,s2,n); Otvet:
#define REG register
char *memset(s, c, n) REG char *s, c;
{ REG char *p = s;
while( --n >= 0 ) *p++ = c;
return s;
}
char *memcpy(dst, src, n)
REG char *dst, *src;
REG int n;
{ REG char *d = dst;
A. Bogatyrev, 1992-95 - 105 - Si v UNIX
while( n-- > 0 ) *d++ = *src++;
return dst;
}
char *memchr(s, c, n) REG char *s, c;
{
while(n-- && *s++ != c);
return( n < 0 ? NULL : s-1 );
}
int memcmp(s1, s2, n)
REG char *s1, *s2; REG n;
{
while(n-- > 0 && *s1 == *s2)
s1++, s2++;
return( n < 0 ? 0 : *s1 - *s2 );
}
Est' takie standartnye funkcii.
2.57. Pochemu luchshe pol'zovat'sya standartnymi funkciyami raboty so strokami i pamyat'yu
(strcpy, strlen, strchr, memcpy, ...)?
Otvet: potomu, chto oni obychno realizovany postavshchikami sistemy |FFEKTIVNO, to
est' napisany ne na Si, a na assemblere s ispol'zovaniem specializirovannyh mashinnyh
komand i registrov. |to delaet ih bolee bystrymi. Napisannyj Vami ekvivalent na Si
mozhet ispol'zovat'sya dlya povysheniya mobil'nosti programmy, libo dlya vneseniya popravok
v standartnye funkcii.
2.58. Rassmotrim programmu, kopiruyushchuyu stroku samu v sebya:
#include <stdio.h>
#include <string.h>
char string[] = "abcdefghijklmn";
void main(void){
memcpy(string+2, string, 5);
printf("%s\n", string);
exit(0);
Ona pechataet abababahijklmn. My mogli by ozhidat', chto kusok dliny 5 simvolov "abcde"
budet skopirovan kak est': ab[abcde]hijklmn, a poluchili ab[ababa]hijklmn - cikliches-
koe povtorenie pervyh dvuh simvolov stroki... V chem delo? Delo v tom, chto kogda
oblasti istochnika (src) i poluchatelya (dst) perekryvayutsya, to v nekij moment *src
beretsya iz UZHE perezapisannoj ranee oblasti, to est' isporchennoj! Vot programma,
illyustriruyushchaya etu problemu:
A. Bogatyrev, 1992-95 - 106 - Si v UNIX
#include <stdio.h>
#include <string.h>
#include <ctype.h>
char string[] = "abcdefghijklmn";
char *src = &string[0];
char *dst = &string[2];
int n = 5;
void show(int niter, char *msg){
register length, i;
printf("#%02d %s\n", niter, msg);
length = src-string;
putchar('\t');
for(i=0; i < length+3; i++) putchar(' ');
putchar('S'); putchar('\n');
printf("\t...%s...\n", string);
length = dst-string;
putchar('\t');
for(i=0; i < length+3; i++) putchar(' ');
putchar('D'); putchar('\n');
}
void main(void){
int iter = 0;
while(n-- > 0){
show(iter, "pered");
*dst++ = toupper(*src++);
show(iter++, "posle");
}
exit(0);
}
Ona pechataet:
A. Bogatyrev, 1992-95 - 107 - Si v UNIX
#00 pered
S
...abcdefghijklmn...
D
#00 posle
S
...abAdefghijklmn...
D
#01 pered
S
...abAdefghijklmn...
D
#01 posle
S
...abABefghijklmn...
D
#02 pered
S
...abABefghijklmn...
D
#02 posle
S
...abABAfghijklmn...
D
#03 pered
S
...abABAfghijklmn...
D
#03 posle
S
...abABABghijklmn...
D
#04 pered
S
...abABABghijklmn...
D
#04 posle
S
...abABABAhijklmn...
D
Otrezki NE perekryvayutsya, esli odin iz nih lezhit libo celikom levee, libo celikom
pravee drugogo (n - dlina oboih otrezkov).
dst src src dst
######## @@@@@@@@ @@@@@@@@ ########
dst+n <= src ili src+n <= dst
dst <= src-n ili dst >= src+n
Otrezki perekryvayutsya v sluchae
! (dst <= src - n || dst >= src + n) =
(dst > src - n && dst < src + n)
Pri etom opasen tol'ko sluchaj dst > src. Takim obrazom opasnaya situaciya opisyvaetsya
usloviem
src < dst && dst < src + n
(esli dst==src, to voobshche nichego ne nado delat'). Resheniem yavlyaetsya kopirovanie "ot
A. Bogatyrev, 1992-95 - 108 - Si v UNIX
hvosta k golove":
void bcopy(register char *src, register char *dst,
register int n){
if(dst >= src){
dst += n-1;
src += n-1;
while(--n >= 0)
*dst-- = *src--;
}else{
while(n-- > 0)
*dst++ = *src++;
}
}
Ili, ogranichivayas' tol'ko opasnym sluchaem:
void bcopy(register char *src, register char *dst,
register int n){
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);
}
Programma
#include <stdio.h>
#include <string.h>
#include <ctype.h>
char string[] = "abcdefghijklmn";
char *src = &string[0];
char *dst = &string[2];
int n = 5;
void show(int niter, char *msg){
register length, i;
printf("#%02d %s\n", niter, msg);
length = src-string;
putchar('\t');
for(i=0; i < length+3; i++) putchar(' ');
putchar('S'); putchar('\n');
printf("\t...%s...\n", string);
length = dst-string;
putchar('\t');
for(i=0; i < length+3; i++) putchar(' ');
putchar('D'); putchar('\n');
}
A. Bogatyrev, 1992-95 - 109 - Si v UNIX
void main(void){
int iter = 0;
if(dst==src || n <= 0){
printf("Nichego ne nado delat'\n");
return;
}
if(src < dst && dst < src + n) {
dst += n-1;
src += n-1;
while(--n >= 0){
show(iter, "pered");
*dst-- = toupper(*src--);
show(iter++, "posle");
}
}else
while(n-- > 0){
show(iter, "pered");
*dst++ = toupper(*src++);
show(iter++, "posle");
}
exit(0);
}
Pechataet
A. Bogatyrev, 1992-95 - 110 - Si v UNIX
#00 pered
S
...abcd