Новые книги

В этой книге поведение потребителей анализируется с помощью современной науки о принятии решений. Автор рассказывает о том, почему люди совершают покупки, что происходит в сознании покупателя, когда он принимает решение, и как можно использовать последние научные знания в своей маркетинговой деятельности.

На русском языке публикуется впервые.
With important new revelations into the Russian hacking of the 2016 Presidential campaigns

cite

“[Andrei Soldatov is] the single most prominent critic of Russia’s surveillance apparatus.”

text-author

—Edward Snowden

After the Moscow protests in 2011–2012, Vladimir Putin became terrified of the internet as a dangerous means for political mobilization and uncensored public debate. Only four years later, the Kremlin used that same platform to disrupt the 2016 presidential election in the United States. How did this transformation happen?

The Red Web is a groundbreaking history of the Kremlin’s massive online-surveillance state that exposes just how easily the internet can become the means for repression, control, and geopolitical warfare. In this bold, updated edition, Andrei Soldatov and Irina Borogan offer a perspective from Moscow with new and previously unreported details of the 2016 hacking operation, telling the story of how Russia came to embrace the disruptive potential of the web and interfere with democracy around the world.

A Library Journal Best Book of 2015

A NPR Great Read of 2015

Массивы, строки, указатели.

2.40.

Что печатает программа?
    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);
    }
Что она напечатает, если задать
    char str[25]="Hi,";   или    char str[25]="";

2.41.

В чем состоит ошибка? (Любимая ошибка начинающих)
      main(){
         char *buf;     /* или char buf[]; */
         gets( buf );
         printf( "%s\n", buf );
      }

Ответ: память под строку buf не выделена, указатель buf не проинициализирован и смотрит неизвестно куда. Надо было писать например так:

    char buf[80];
или
    char mem[80], *buf = mem;

Обратите на этот пример особое внимание, поскольку, описав указатель (но никуда его не направив), новички успокаиваются, не заботясь о выделении памяти для хранения данных. Указатель должен указывать на ЧТО-ТО, в чем можно хранить данные, а не "висеть", указывая "пальцем в небо"! Запись информации по "висячему" указателю разрушает память программы и приводит к скорому (но часто не немедленному и потому таинственному) краху.

Вот программа, которая также использует неинициализированный указатель. На машине SPARCstation 20 эта программа убивается операционной системой с диагностикой "Segmentation fault" (SIGSEGV). Это как раз и значит обращение по указателю, указывающему "пальцем в небо".

    main(){
            int *iptr;
            int  ival  = *iptr;
            printf("%d\n", ival);
    }

2.42.

Для получения строки "Life is life" написана программа:
    main(){
        char buf[ 60 ];
        strcat( buf, "Life " );
        strcat( buf, "is "   );
        strcat( buf, "life"  );
        printf( "%s\n", buf );
    }
Что окажется в массиве buf?

Ответ: в начале массива окажется мусор, поскольку автоматический массив не инициализируется байтами '\0', а функция strcat() приписывает строки к концу строки. Для исправления можно написать

            *buf = '\0';
перед первым strcat()-ом, либо вместо первого strcat()-а написать strcpy( buf, "Life " );

2.43.

Составьте макроопределение copystr(s1, s2) для копирования строки s2 в строку s1.

2.44.

Составьте макроопределение lenstr(s) для вычисления длины строки.

Многие современные компиляторы сами обращаются с подобными короткими (1-3 оператора) стандартными функциями как с макросами, то есть при обращении к ним генерят не вызов функции, а подставляют текст ее тела в место обращения. Это делает объектный код несколько "толще", но зато быстрее. В расширенных диалектах Си и в Си++ компилятору можно предложить обращаться так и с вашей функцией - для этого функцию следует объявить как inline (такие функции называются еще "intrinsic").

2.45.

Составьте рекурсивную и нерекурсивную версии программы инвертирования (зеркального отображения) строки:
            abcdef --> fedcba.

2.46.

Составьте функцию index(s, t), возвращающую номер первого вхождения символа t в строку s; если символ t в строку не входит, функция возвращает -1.

Перепишите эту функцию с указателями, чтобы она возвращала указатель на первое вхождение символа. Если символ в строке отсутствует - выдавать NULL. В UNIX System-V такая функция называется strchr. Вот возможный ответ:

    char *strchr(s, c) register char *s, c;
    {    while(*s && *s != c) s++;
         return *s == c ? s : NULL;
    }
Заметьте, что p=strchr(s,'\0'); выдает указатель на конец строки. Вот пример использования:
    extern char *strchr();
    char *s = "abcd/efgh/ijklm";
    char *p = strchr(s, '/');
    printf("%s\n", p==NULL ? "буквы / нет" : p);
    if(p) printf("Индекс вхождения = s[%d]\n", p - s );

2.47.

Напишите функцию strrchr(), указывающую на последнее вхождение символа.

Ответ:

    char *strrchr(s, c) register char *s, c;
    {     char *last = NULL;
          do if(*s == c) last = s; while(*s++);
          return last;
    }
Вот пример ее использования:
    extern char *strrchr();
    char p[] = "wsh";         /* эталон */
    main(argc, argv) char *argv[];{
        char *s = argv[1];    /* проверяемое имя */
        /* попробуйте вызывать
         * 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("Да, это %s\n" , p);
        else printf("Нет, это %s\n", base);
        /* еще более изощренный вариант: */
        if( !strcmp(p,(base=strrchr(s,'/')) ? ++base :
                                             (base=s))
          )  printf("Yes %s\n", p);
        else printf("No  %s\n", base);
    }

2.48.

Напишите макрос substr(to,from,n,len) который записывает в to кусок строки from начиная с n-ой позиции и длиной len. Используйте стандартную функцию strncpy.

Ответ:

    #define substr(to, from, n, len) strncpy(to, from+n, len)
или более корректная функция:
    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';  /* пустая строка */
       else{
            /* длина остатка строки: */
            if(len > lfrom-n) len = lfrom - n;
            strncpy(to, from+n, len);
            to[len] = '\0';
       }
       return to;
    }

2.49.

Напишите функцию, проверяющую, оканчивается ли строка на ".abc", и если нет приписывающую ".abc" к концу. Если же строка уже имеет такое окончание - ничего не делать. Эта функция полезна для генерации имен файлов с заданным расширением. Сделайте расширение аргументом функции.

Для сравнения конца строки s со строкой p следует использовать:

    int ls = strlen(s), lp = strlen(p);
    if(ls >= lp && !strcmp(s+ls-lp, p)) ...совпали...;

2.50.

Напишите функции вставки символа c в указанную позицию строки (с раздвижкой строки) и удаления символа в заданной позиции (со сдвижкой строки). Строка должна изменяться "на месте", т.е. никуда не копируясь. Ответ:

    /* удаление */
    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;
    }
    /* либо просто strcpy(s+at, s+at+1); */
    /* вставка */
    insert(s, at, c) char s[], c;
    {
            register char *p;
            s += at; p = s;
            while(*p) p++;  /* на конец строки */
            p[1] = '\0';    /* закрыть строку  */
            for( ; p != s; p-- )
                    p[0] = p[-1];
            *s = c;
    }

2.51.

Составьте программу удаления символа c из строки s в каждом случае, когда он встречается.

Ответ:

    delc(s, c) register char *s; char c;
    {
       register char *p = s;
       while( *s )
         if( *s != c ) *p++ = *s++;
         else           s++;
       *p = '\0'; /* не забывайте закрывать строку ! */
    }

2.52.

Составьте программу удаления из строки S1 каждого символа, совпадающего с каким-либо символом строки S2.

2.53.

Составьте функцию scopy(s,t), которая копирует строку s в t, при этом символы табуляции и перевода строки должны заменяться на специальные двухсимвольные последовательности "\n" и "\t". Используйте switch.

2.54.

Составьте функцию, которая "укорачивает" строку, заменяя изображения спецсимволов (вроде "\n") на сами эти символы ('\n'). Ответ:
    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.

Напишите программу, заменяющую в строке S все вхождения подстроки P на строку Q, например:

         P = "ура"; Q = "ой";
         S = "ура-ура-ура!";
            Результат: "ой-ой-ой!"

2.56.

Кроме функций работы со строками (где предполагается, что массив байт завершается признаком конца '\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;
          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 <stdio.h>
    #include <string.h>
    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 берется из УЖЕ перезаписанной ранее области, то есть испорченной! Вот программа, иллюстрирующая эту проблему:

    #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,   "перед");
                      *dst++ = toupper(*src++);
                    show(iter++, "после");
            }
            exit(0);
    }
Она печатает:
    #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, то вообще ничего не надо делать). Решением является копирование "от хвоста к голове":
    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');
    }
    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);
    }
Печатает
    #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).

    #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, так и не поняв в чем дело.

© Copyright А. Богатырев, 1992-95
Си в UNIX

Назад | Содержание | Вперед