Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Программирование Cи / Богатырев_Язык Си в системе Unix

.pdf
Скачиваний:
80
Добавлен:
25.04.2015
Размер:
1.19 Mб
Скачать

А. Богатырёв, 1992-96

- 91 -

Си в UNIX™

#00 перед

S

...abcdefghijklmn...

D

#00 после

S

...abAdefghijklmn...

D

#01 перед

S

...abAdefghijklmn...

D

#01 после

S

...abABefghijklmn...

D

#02 перед

S

...abABefghijklmn...

D

#02 после

S

...abABAfghijklmn...

D

#03 перед

S

...abABAfghijklmn...

D

#03 после

S

...abABABghijklmn...

D

#04 перед

S

...abABABghijklmn...

D

#04 после

S

...abABABAhijklmn...

D

Отрезки НЕ перекрываются, если один из них лежит либо целиком левее, либо целиком правее другого (n - длина обоих отрезков).

dst

src

 

src

dst

########

@@@@@@@@

 

@@@@@@@@

########

dst+n <= src

или

src+n

<= dst

dst <= src-n

или

dst >= src+n

Отрезки перекрываются в случае

!(dst <= src - n || dst >= src + n) = (dst > src - n && dst < src + n)

При этом опасен только случай dst > src. Таким образом опасная ситуация описывается условием

src < dst && dst < src + n

(если dst==src, то вообще ничего не надо делать). Решением является копирование "от хвоста к голове":

А. Богатырёв, 1992-96

- 92 -

Си в UNIX™

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++;

 

 

}

 

 

}

 

 

 

Или, ограничиваясь только опасным случаем:

 

 

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);

 

}

 

 

 

Программа

 

 

 

#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');

}

А. Богатырёв, 1992-96

- 93 -

Си в UNIX™

void main(void){

int iter = 0;

if(dst==src || n <= 0){

printf("Ничего не надо делать\n"); return;

}

if(src < dst && dst < src + n) { dst += n-1;

src += n-1; while(--n >= 0){

show(iter, "перед"); *dst-- = toupper(*src--);

show(iter++, "после");

}

}else

while(n-- > 0){

show(iter, "перед"); *dst++ = toupper(*src++);

show(iter++, "после");

}

exit(0);

}

Печатает

А. Богатырёв, 1992-96

- 94 -

Си в UNIX™

#00 перед

S

...abcdefghijklmn...

D

#00 после

S

...abcdefEhijklmn...

D

#01 перед

S

...abcdefEhijklmn...

D

#01 после

S

...abcdeDEhijklmn...

D

#02 перед

S

...abcdeDEhijklmn...

D

#02 после

S

...abcdCDEhijklmn...

D

#03 перед

S

...abcdCDEhijklmn...

D

#03 после

S

...abcBCDEhijklmn...

D

#04 перед

S

...abcBCDEhijklmn...

D

#04 после

S

...abABCDEhijklmn...

D

Теперь bcopy() - удобная функция для копирования и сдвига массивов, в частности массивов указателей. Пусть у нас есть массив строк (выделенных malloc-ом):

char *lines[NLINES];

Тогда циклическая перестановка строк выглядит так:

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;

}

Возможно, что написание по аналогии функции для копирования массивов элементов типа (void *) - обобщенных указателей - может оказаться еще понятнее и эффективнее. Такая функция - memmove - стандартно существует в UNIX SVR4. Заметьте, что порядок аргументов в ней обратный по отношению к bcopy. Следует отметить, что в SVR4 все функции mem... имеют указатели типа (void *) и счетчик типа size_t - тип для количества байт (вместо unsigned long); в частности длина файла имеет именно этот тип (смотри системные вызовы lseek и stat).

А. Богатырёв, 1992-96

- 95 -

Си в UNIX™

#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 - это тип для указателей на БАЙТ, фактически это (unsigned char *). Зачем вообще понадобилось использовать caddr_t? Затем, что для

void *pointer; int n;

значение

pointer + n

не определено и невычислимо, ибо sizeof(void) не имеет смысла - это не 0, а просто ошибка, диагностируемая компилятором!

2.59. Еще об опечатках: вот что бывает, когда вместо знака `=' печатается `-' (на клавиатуре они находятся рядом...).

#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"); /* подразумевалось ptr = ... */ *ptr = 'H';

printf("%s\n", ptr); free(ptr);

exit(0);

}

Дело в том, что запись (а часто и чтение) по *pointer, где pointer==NULL, приводит к аварийному прекращению программы. В нашей программе ptr осталось равным NULL - указателем в никуда. В операционной системе UNIX на машинах с аппаратной защитой памяти, страница памяти, содержащая адрес NULL (0) бывает закрыта на запись, поэтому любое обращение по записи в эту страницу вызывает прерывание от диспетчера памяти и аварийное прекращение процесса. Система сама помогает ловить ваши ошибки (но уже во время выполнения программы). Это ОЧЕНЬ частая ошибка - запись по адресу NULL. MS DOS в таких случаях предпочитает просто зависнуть, и вы бываете вынуждены играть аккорд из трех клавиш - Ctrl/Alt/Del, так и не поняв в чем дело.

2.60. Раз уж речь зашла о функции strdup (кстати, это стандартная функция), приведем еще одну функцию для сохранения строк.

А. Богатырёв, 1992-96

- 96 -

Си в UNIX™

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;

}

Сам символ (*upto) не сохраняется, а заменяется на '\0'.

2.61. Упрощенный аналог функции printf.

/*

*Машинно - независимый printf() (упрощенный вариант).

*printf - Форматный Вывод.

*/

#include <stdio.h> #include <ctype.h> #include <varargs.h> #include <errno.h> #include <string.h>

extern int errno;

/* код системной ошибки, формат %m */

/* чтение

значения числа */

 

#define GETN(n,fmt)

 

\

n

= 0;

 

\

while(isdigit(*fmt)){

\

 

n = n*10 + (*fmt - '0'); \

 

fmt++;

 

\

}

 

 

 

А. Богатырёв, 1992-96

- 97 -

Си в UNIX™

void myprintf(fmt, va_alist) register char *fmt; va_dcl

{

va_list ap;

char c, *s; int i;

int width, /* минимальная ширина поля */ prec, /* макс. длина данного */

sign, /* выравнивание: 1 - вправо, -1 - влево */ zero, /* ширина поля начинается с 0 */

glong; /* требуется длинное целое */

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); /* произвольно */

if( *fmt == 'l' ){ glong = 1; fmt++;

}

А. Богатырёв, 1992-96

- 98 -

Си в 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 преобразует код ошибки в строку-расшифровку */ 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);

 

}

 

/* ---------------------------------------------------------

*/

int strnlen(s, maxlen) char *s;

 

{

 

register n;

 

for( n=0; *s && n < maxlen; n++, s++ );

 

return n;

 

}

 

А. Богатырёв, 1992-96

- 99 -

Си в UNIX™

/* Печать строки */

static prStr(width, prec, s) char *s;

{

int ln;

/*

сколько символов выводить */

int toLeft = 0; /*

к какому краю прижимать */

if(s == NULL){ pr(

"(NULL)", 6); return; }

/* Измерить длину и обрубить длинную строку.

*Дело в том, что строка может не иметь \0 на конце, тогда

*strlen(s) может привести к обращению в запрещенные адреса */ ln = (prec > 0 ? strnlen(s, prec) : strlen(s));

/* ширина поля */

 

if( ! width ) width = (prec > 0 ? prec : ln);

 

if( width < 0){ width = -width; toLeft = 1; }

 

if( width > ln){

 

 

 

/* дополнить поле пробелами */

 

 

if(toLeft){ pr(s, ln); prSpace(width - ln, ' ');

}

 

else

 

{ prSpace(width - ln, ' '); pr(s, ln);

}

}

else

 

{ pr(s, ln);

}

}

 

 

 

 

/* Печать строки длиной l */

 

static pr(s, ln) register char *s; register ln;

 

{

 

 

 

 

for( ; ln > 0 ; ln-- )

 

 

putchar( *s++ );

 

}

 

 

 

 

/* Печать n символов c */

 

static prSpace(n, c) register n; char c;{

 

for( ; n > 0 ; n-- )

 

 

putchar( c );

 

 

}

 

 

 

 

/*

---------------------------------------------------------

 

 

*/

static char *ds;

 

 

 

/* Римские цифры */

 

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);

 

}

 

 

 

 

static prRdig(n,

d){

 

 

if( !n ) return;

 

 

if( d ) prRdig( n/10, d - 2);

 

 

tack(n%10,

d);

 

}

А. Богатырёв, 1992-96

- 100 -

Си в UNIX™

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;

{

 

 

 

 

/* преобразуем число в строку */

 

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'; /* Теперь печатаем строку */

prIntStr( bd, w, zero, neg );

}

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'; /* Теперь печатаем строку */

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));

}