Программирование Cи / Богатырев_Язык Си в системе Unix
.pdfА. Богатырёв, 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));
}