ется признаком конца '\0'), в Си предусмотрены также функции для работы с массивами
байт без ограничителя. Для таких функций необходимо явно указывать длину обрабатывае-
мого массива. Напишите функции: пересылки массива длиной n байт memcpy(dst,src,n);
заполнения массива символом c memset(s,c,n); поиска вхождения символа в массив
memchr(s,c,n); сравнения двух массивов memcmp(s1,s2,n); Ответ:
#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;
А. Богатырев, 1992-95 - 105 - Си в 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 );
}
Есть такие стандартные функции.
2.57. Почему лучше пользоваться стандартными функциями работы со строками и памятью
(strcpy, strlen, strchr, memcpy, ...)?
Ответ: потому, что они обычно реализованы поставщиками системы ЭФФЕКТИВНО, то
есть написаны не на Си, а на ассемблере с использованием специализированных машинных
команд и регистров. Это делает их более быстрыми. Написанный Вами эквивалент на Си
может использоваться для повышения мобильности программы, либо для внесения поправок
в стандартные функции.
2.58. Рассмотрим программу, копирующую строку саму в себя:
#include
#include
char string[] = "abcdefghijklmn";
void main(void){
memcpy(string+2, string, 5);
printf("%s\n", string);
exit(0);
Она печатает abababahijklmn. Мы могли бы ожидать, что кусок длины 5 символов "abcde"
будет скопирован как есть: ab[abcde]hijklmn, а получили ab[ababa]hijklmn - цикличес-
кое повторение первых двух символов строки... В чем дело? Дело в том, что когда
области источника (src) и получателя (dst) перекрываются, то в некий момент *src
берется из УЖЕ перезаписанной ранее области, то есть испорченной! Вот программа,
иллюстрирующая эту проблему:
А. Богатырев, 1992-95 - 106 - Си в UNIX
#include
#include
#include
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, "перед");
*dst++ = toupper(*src++);
show(iter++, "после");
}
exit(0);
}
Она печатает:
А. Богатырев, 1992-95 - 107 - Си в 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-95 - 108 - Си в 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
#include
#include
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-95 - 109 - Си в 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-95 - 110 - Си в 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];
Тогда циклическая перестановка строк выглядит так:
А. Богатырев, 1992-95 - 111 - Си в 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;
}
Возможно, что написание по аналогии функции для копирования массивов элементов типа
(void *) - обобщенных указателей - может оказаться еще понятнее и эффективнее. Такая
функция - memmove - стандартно существует в UNIX SVR4. Заметьте, что порядок аргу-
ментов в ней обратный по отношению к bcopy. Следует отметить, что в SVR4 все функции
mem... имеют указатели типа (void *) и счетчик типа size_t - тип для количества байт
(вместо unsigned long); в частности длина файла имеет именно этот тип (смотри систем-
ные вызовы lseek и stat).
#include
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. Еще об опечатках: вот что бывает, когда вместо знака `=' печатается `-' (на
клавиатуре они находятся рядом...).
А. Богатырев, 1992-95 - 112 - Си в UNIX
#include
#include
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) бывает закрыта на запись, поэтому любое