HTML page

2.  Массивы, строки, указатели.
     Массив представляет собой агрегат из нескольких  переменных  одного  и  того  же
типа. Массив с именем a из LENGTH элементов типа TYPE объявляется так:

    TYPE a[LENGTH];

Это соответствует тому, что объявляются переменные типа TYPE со специальными  именами
a[0],  a[1],  ...,  a[LENGTH-1].   Каждый  элемент массива имеет свой номер - индекс.
Доступ к x-ому элементу массива осуществляется при помощи операции индексации:

    int x = ... ;      /* целочисленный индекс   */
    TYPE value = a[x]; /* чтение x-ого элемента  */
         a[x] = value; /* запись в x-тый элемент */

В качестве индекса может использоваться любое  выражение,  выдающее  значение  целого
типа:  char, short, int, long.  Индексы элементов массива в Си начинаются с 0 (а не с
1), и индекс последнего элемента массива из LENGTH элементов -  это  LENGTH-1  (а  не
LENGTH).  Поэтому цикл по всем элементам массива - это

    TYPE a[LENGTH]; int indx;
    for(indx=0; indx <&lt; LENGTH; indx++)
       ...a[indx]...;

indx < LENGTH  равнозначно  indx <= LENGTH-1.   Выход  за  границы  массива  (попытка
чтения/записи  несуществующего элемента) может привести к непредсказуемым результатам
и поведению программы.  Отметим, что это одна из самых распространенных ошибок.
     Статические массивы можно объявлять с  инициализацией,  перечисляя  значения  их
элементов  в  {}  через  запятую.   Если задано меньше элементов, чем длина массива -
остальные элементы считаются нулями:

    int a10[10] = { 1, 2, 3, 4 }; /* и 6 нулей */

Если при описании массива с инициализацией не указать его размер, он будет  подсчитан
компилятором:

    int a3[] = { 1, 2, 3 }; /* как бы a3[3] */

     В большинстве современных компьютеров (с фон-Неймановской  архитектурой)  память
представляет  собой массив байт.  Когда мы описываем некоторую переменную или массив,
в памяти выделяется непрерывная область для  хранения  этой  переменной.   Все  байты
памяти  компьютера  пронумерованы.   Номер байта, с которого начинается в памяти наша
переменная, называется адресом этой переменной (адрес может  иметь  и  более  сложную
структуру,  чем  просто  целое  число - например состоять из номера сегмента памяти и
номера байта в этом сегменте).  В Си адрес переменной можно получить с помощью опера-
ции  взятия  адреса &. Пусть у нас есть переменная var, тогда &var - ее адрес.  Адрес
нельзя присваивать целой переменной;  для  хранения  адресов  используются  указатели
(смотри ниже).
     Данное может занимать несколько подряд идущих байт.   Размер  в  байтах  участка
памяти,  требуемого для хранения значения типа TYPE, можно узнать при помощи операции
sizeof(TYPE), а размер  переменной  -  при  помощи  sizeof(var).  Всегда  выполняется
sizeof(char)==1.   В  некоторых машинах адреса переменных (а также агрегатов данных -
массивов и структур) кратны sizeof(int)  или  sizeof(double)  -  это  так  называемое
"выравнивание (alignment) данных на границу типа int".  Это позволяет делать доступ к
данным более быстрым (аппаратура работает эффективнее).
     Язык Си предоставляет нам средство для работы  с  адресами  данных  -  указатели
(pointer)[*].  Указатель физически - это адрес некоторой переменной ("указуемой"  пере-
менной).   Отличие  указателей от машинных адресов состоит в том, что указатель может
содержать адреса данных только определенного типа.  Указатель ptr, который может ука-
зывать на данные типа TYPE, описывается так:

    TYPE  var;     /* переменная       */
    TYPE *ptr;     /* объявление ук-ля */
          ptr = & var;

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

В данном случае мы занесли в указательную переменную ptr адрес переменной var.  Будем
говорить,  что  указатель ptr указывает на переменную var (или, что ptr установлен на
var).  Пусть TYPE равно int, и у нас есть массив и указатели:

    int  array[LENGTH], value;
    int *ptr, *ptr1;

Установим указатель на x-ый элемент массива

    ptr = & array[x];

Указателю можно присвоить значение другого указателя на такой же  тип.  В  результате
оба указателя будут указывать на одно и то же место в памяти: ptr1 = ptr;
     Мы можем изменять указуемую переменную при помощи операции *

    *ptr = 128;   /* занести 128 в указуемую перем. */
    value = *ptr; /* прочесть указуемую переменную  */

В данном случае мы заносим и затем читаем значение переменной  array[x],  на  которую
поставлен указатель, то есть

    *ptr  означает сейчас  array[x]

Таким образом, операция * (значение по адресу)  оказывается  обратной  к  операции  &
(взятие адреса):

    & (*ptr) == ptr    и    * (&value) == value

Операция * объясняет смысл описания TYPE *ptr; оно означает, что  значение  выражения
*ptr  будет иметь тип TYPE. Название же типа самого указателя - это (TYPE *). В част-
ности, TYPE может сам быть указательным типом - можно объявить  указатель  на  указа-
тель, вроде char **ptrptr;
     Имя массива - это константа, представляющая собой указатель на 0-ой элемент мас-
сива.   Этот указатель отличается от обычных тем, что его нельзя изменить (установить
на другую переменную), поскольку он сам хранится не в переменной, а  является  просто
некоторым постоянным адресом.

        массив           указатель
           ____________       _____
    array: | array[0] |   ptr:| * |
           | array[1] |         |
           | array[2] |<--------- сейчас равен &array[2]
           |  ...     |

Следствием такой интерпретации имен массивов является то, что для того  чтобы  поста-
вить указатель на начало массива, надо писать

    ptr = array;  или  ptr = &array[0];
            но не
    ptr = &array;

Операция & перед одиноким именем массива не нужна и недопустима!
     Такое родство указателей и массивов позволяет нам применять операцию *  к  имени
массива: value = *array; означает то же самое, что и value = array[0];
     Указатели - не целые числа!  Хотя физически это и номера байтов, адресная  ариф-
метика  отличается  от  обычной.  Так,  если  дан  указатель TYPE *ptr; и номер байта
(адрес), на который указывает ptr, равен byteaddr, то

    ptr = ptr + n; /* n - целое, может быть и < 0 */

заставит ptr указывать не на байт номер byteaddr + n, а на байт номер



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

    byteaddr + (n * sizeof(TYPE))

то есть прибавление единицы к указателю продвигает адрес не на 1 байт,  а  на  размер
указываемого  указателем  типа данных!  Пусть указатель ptr указывает на x-ый элемент
массива array.  Тогда после

    TYPE *ptr2 = array + L;  /* L - целое */
    TYPE *ptr1 = ptr   + N;  /* N - целое */
          ptr += M;          /* M - целое */

указатели указывают на

    ptr1 == &array[x+N]   и   ptr  == &array[x+M]
    ptr2 == &array[L]

Если мы теперь рассмотрим цепочку равенств

    *ptr2 = *(array + L) = *(&array[L]) =
              array[L]

то получим
ОСНОВНОЕ ПРАВИЛО: пусть ptr - указатель или имя массива. Тогда  операции  индексации,
взятия  значения  по  адресу,  взятия адреса и прибавления целого к указателю связаны
соотношениями:

     ptr[x]  тождественно *(ptr+x)
    &ptr[x]  тождественно   ptr+x

(тождества верны в обе стороны), в том числе при x==0 и x < 0. Так что, например,

    ptr[-1] означает  *(ptr-1)
    ptr[0]  означает  *ptr

Указатели можно индексировать подобно массивам.  Рассмотрим пример:

            /* индекс:     0    1    2    3    4   */
    double  numbers[5] = { 0.0, 1.0, 2.0, 3.0, 4.0 };
    double *dptr   = &numbers[2];
    double  number =  dptr[2];  /* равно 4.0 */

    numbers: [0]   [1]   [2]   [3]   [4]
                          |
            [-2]  [-1]   [0]   [1]   [2]
                         dptr

поскольку

    если dptr    = &numbers[x] = numbers + x
    то   dptr[i] = *(dptr + i) =
                 = *(numbers + x + i) = numbers[x + i]

     Указатель на один тип можно преобразовать в указатель на другой тип: такое  пре-
образование  не вызывает генерации каких-либо машинных команд, но заставляет компиля-
тор изменить параметры адресной арифметики, а также операции выборки данного по  ука-
зателю (собственно, разница в указателях на данные разных типов состоит только в раз-
мерах указуемых типов; а также в генерации команд `->&gt;' для  выборки  полей  структур,
если указатель - на структурный тип).
     Целые (int или long) числа иногда можно преобразовывать в указатели.  Этим поль-
зуются  при написании драйверов устройств для доступа к регистрам по физическим адре-
сам, например:




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

    unsigned short *KISA5 = (unsigned short *) 0172352;

Здесь возникают два тонких момента:
1.   Как уже было сказано, адреса данных часто выравниваются  на  границу  некоторого
     типа.   Мы  же  можем  задать  невыровненное  целое значение.  Такой адрес будет
     некорректен.
2.   Структура адреса, поддерживаемая процессором, может не  соответствовать  формату
     целых (или длинных целых) чисел. Так обстоит дело с IBM PC 8086/80286, где адрес
     состоит из пары short int чисел, хранящихся в памяти подряд.  Однако весь  адрес
     (если  рассматривать  эти  два числа как одно длинное целое) не является обычным
     long-числом, а вычисляется более сложным способом: адресная пара  SEGMENT:OFFSET
     преобразуется так

    unsigned short SEGMENT, OFFSET; /*16 бит: [0..65535]*/
    unsigned long  ADDRESS = (SEGMENT << 4) + OFFSET;
      получается 20-и битный физический адрес ADDRESS

     Более того, на машинах с диспетчером памяти, адрес, хранимый в указателе,  явля-
     ется "виртуальным" (т.е. воображаемым, ненастоящим) и может не совпадать с физи-
     ческим адресом, по которому данные хранятся в памяти компьютера.  В памяти может
     одновременно  находиться  несколько программ, в каждой из них будет своя система
     адресации ("адресное пространство"), отсчитывающая виртуальные адреса с нуля  от
     начала  области  памяти, выделенной данной программе. Преобразование виртуальных
     адресов в физические выполняется аппаратно.
В Си принято соглашение, что указатель (TYPE *)0 означает "указатель ни на  что".  Он
является  просто  признаком,  используемым для обозначения несуществующего адреса или
конца цепочки указателей, и имеет специальное обозначение NULL.   Обращение  (выборка
или  запись данных) по этому указателю считается некорректным (кроме случая, когда вы
пишете машинно-зависимую программу и работаете с физическими адресами).
     Отметим, что указатель можно направить в неправильное место - на участок памяти,
содержащий  данные  не  того  типа,  который  задан в описании указателя; либо вообще
содержащий неизвестно что:

    int i = 2, *iptr = &i;
    double x = 12.76;
      iptr += 7;  /* куда же он указал ?! */
      iptr = (int *) &x;  i = *iptr;

Само присваивание указателю некорректного значения еще не  является  ошибкой.  Ошибка
возникнет  лишь  при  обращении  к  данным  по этому указателю (такие ошибки довольно
тяжело искать!).

     При передаче имени массива в качестве параметра функции, как аргумент передается
не  копия  САМОГО  МАССИВА  (это заняло бы слишком много места), а копия АДРЕСА 0-ого
элемента этого массива (т.е. указатель на начало массива).

    f(int x   ){ x++;     }
    g(int xa[]){ xa[0]++; }
    int a[2] = { 1, 1 }; /* объявление с инициализацией */
    main(){
     f(a[0]); printf("%d\n",a[0]); /* a[0] осталось равно 1*/
     g(a   ); printf("%d\n",a[0]); /* a[0] стало равно 2   */
    }

В f() в качестве аргумента передается копия элемента a[0] (и изменение этой копии  не
приводит  к  изменению  самого  массива  - аргумент x является локальной переменной в
f()), а в g() таким локалом является АДРЕС массива a -  но  не  сам  массив,  поэтому
xa[0]++  изменяет  сам  массив  a  (зато,  например, xa++ внутри g() изменило бы лишь
локальную указательную переменную xa, но не адрес массива a).
     Заметьте, что поскольку массив передается как указатель на его начало, то размер
массива  в  объявлении  аргумента  можно  не указывать.  Это позволяет одной функцией



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

обрабатывать массивы разной длины:

    вместо    Fun(int xa[5]) { ... }
    можно     Fun(int xa[] ) { ... }
    или даже  Fun(int *xa  ) { ... }

Если функция должна знать длину массива - передавайте ее как дополнительный аргумент:

     int sum( int a[], int len ){
       int s=0, i;
       for(i=0; i < len; i++) s += a[i];
       return( s );
     }
     ... int arr[10] = { ... };
     ... int sum10 = sum(arr, 10); ...

Количество элементов в массиве TYPE arr[N]; можно вычислить специальным образом, как

    #define LENGTH (sizeof(arr) / sizeof(arr[0]))
            или
    #define LENGTH (sizeof(arr) / sizeof(TYPE))

Оба способа выдадут число, равное N.  Эти конструкции обычно употребляются для вычис-
ления длины массивов, задаваемых в виде

    TYPE arr[] = { ....... };

без явного указания размера.  sizeof(arr)  выдает  размер  всего  массива  в  байтах.
sizeof(arr[0])  выдает размер одного элемента.  И все это не зависит от типа элемента
(просто потому, что все элементы массивов имеют одинаковый размер).

     Строка в Си - это последовательность байт (букв,  символов,  литер,  character),
завершающаяся  в  конце специальным признаком - байтом '\0'. Этот признак добавляется
компилятором автоматически, когда мы задаем строку в  виде  "строка".   Длина  строки
(т.е.  число литер, предшествующих '\0') нигде явно не хранится. Длина строки ограни-
чена лишь размером массива, в котором сохранена строка, и может изменяться в процессе
работы  программы в пределах от 0 до длины массива-1.  При передаче строки в качестве
аргумента в функцию, функции не требуется знать длину строки, т.к. передается  указа-
тель на начало массива, а наличие ограничителя '\0' позволяет обнаружить конец строки
при ее просмотре.
     С массивами байт можно  использовать  следующую  конструкцию,  задающую  массивы
(строки) одинакового размера:

    char stringA [ITSSIZE];
    char stringB [sizeof stringA];

В данном разделе мы в основном будем рассматривать строки и указатели на символы.

2.1.  Операции взятия адреса объекта и разыменования указателя - взаимно обратны.

    TYPE  objx;
    TYPE *ptrx = &objx;  /* инициализируем адресом objx */

    *(&objx) = objx;
    &(*ptrx) = ptrx;

Вот пример того, как  можно  заменить  условный  оператор  условным  выражением  (это
удастся не всегда):

    if(c) a = 1;
    else  b = 1;



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

Предупреждение: такой стиль не способствует понятности программы и даже  компактности
ее кода.

    #include <stdio.h>
    int main(int ac, char *av[]){
            int a, b, c;

            a = b = c = 0;
            if(av[1])  c = atoi(av[1]);

            *(c ? &a : &b) = 1;     /* !!! */

            printf("cond=%d a=%d b=%d\n", c, a, b);
            return 0;
    }


2.2.  Каким образом инициализируются по умолчанию внешние и статические массивы? Ини-
циализируются  ли  по умолчанию автоматические массивы?  Каким образом можно присваи-
вать значения элементам массива, относящегося к любому классу памяти?

2.3.  Пусть задан массив int arr[10]; что тогда означают выражения:

      arr[0]        *arr            *arr + 2
      arr[2]        *(arr + 2)       arr
     &arr[2]         arr+2


2.4.  Правильно ли написано увеличение величины, на которую указывает указатель a, на
единицу?

    *a++;

Ответ: нет, надо:

    (*a)++;   или    *a += 1;


2.5.  Дан фрагмент текста:

    char a[] = "xyz";
    char *b  = a + 1;

Чему равны

    b[-1]       b[2]      "abcd"[3]

(Ответ: 'x', '\0', 'd' )
     Можно ли написать a++ ? То же про b++ ?  Можно ли написать b=a ?  a=b  ?   (нет,
да, да, нет)

2.6.  Ниже приведена программа, вычисляющая среднее значение элементов массива

      int arr [] = {1, 7, 4, 45, 31, 20, 57, 11};
      main () {
         int i; long sum;

         for ( i = 0, sum = 0L;
               i < (sizeof(arr)/sizeof(int)); i++ )
                    sum += arr[i];
         printf ("Среднее значение = %ld\n", sum/8)



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

      }

Перепишите указанную программу с применением указателей.

2.7.  Что напечатается в результате работы программы?

         char arr[] = {'С', 'Л', 'А', 'В', 'А'};
         main () {
            char *pt; int i;

            pt = arr + sizeof(arr) - 1;
            for( i = 0; i < 5; i++, pt--  )
                 printf("%c %c\n", arr[i], *pt);
         }

Почему массив arr[] описан вне функции main()?  Как внести его  в  функцию  main()  ?
Ответ: написать внутри main
  static char arr[]=...

2.8.  Можно ли писать на Си так:

            f( n, m ){
                    int x[n]; int y[n*2];
                    int z[n * m];
                    ...
            }

Ответ: к сожалению нельзя (Си - это не Algol).  При отведении памяти  для  массива  в
качестве  размера должна быть указана константа или выражение, которое может быть еще
во время компиляции вычислено до целочисленной константы, т.е. массивы имеют фиксиро-
ванную длину.

2.9.  Предположим, что у нас есть описание массива

            static int mas[30][100];

a)   выразите адрес mas[22][56] иначе
b)   выразите адрес mas[22][0] двумя способами
c)   выразите адрес mas[0][0] тремя способами

2.10.  Составьте программу инициализации двумерного массива a[10][10],  выборки  эле-
ментов  с a[5][5] до a[9][9] и их распечатки.  Используйте доступ к элементам по ука-
зателю.

2.11.  Составьте функцию вычисления скалярного  произведения  двух  векторов.   Длина
векторов задается в качестве одного из аргументов.

2.12.  Составьте функцию умножения двумерных матриц a[][] * b[][].

2.13.  Составьте функцию умножения трехмерных матриц a[][][] * b[][][].

2.14.  Для тех, кто программировал на языке Pascal: какая допущена ошибка?

            char a[10][20];
            char c;
            int x,y;
              ...
            c = a[x,y];

Ответ: многомерные массивы в Си надо индексировать так:




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

            c = a[x][y];

В написанном же примере мы имеем в качестве индекса выражение  x,y  (оператор  "запя-
тая") со значением y, т.е.

            c = a[y];

Синтаксической ошибки нет, но смысл совершенно изменился!

2.15.  Двумерные массивы в памяти представляются как одномерные. Например, если

    int a[N][M];

то конструкция a[y][x] превращается при компиляции в одномерную конструкцию, подобную
такой:

    int a[N * M]; /* массив развернут построчно */
    #define a_yx(y, x)   a[(x) + (y) * M]

то есть

    a[y][x] есть *(&a[0][0] + y * M + x)

Следствием этого является то, что компилятор для генерации  индексации  двумерных  (и
более)  массовов  должен  знать M - размер массива по 2-ому измерению (а также 3-ему,
4-ому, и.т.д.).  В частности, при передаче многомерного массива в функцию

    f(arr) int arr[N][M]; { ... }   /* годится    */
    f(arr) int arr[] [M]; { ... }   /* годится    */
    f(arr) int arr[] [];  { ... }   /* не годится */

    f(arr) int (*arr)[M]; { ... }   /* годится    */
    f(arr) int  *arr [M]; { ... }   /* не годится:
                  это уже не двумерный массив,
                  а одномерный массив указателей  */

А также при описании внешних массивов:

    extern int a[N][M];     /* годится */
    extern int a[ ][M];     /* годится */
    extern int a[ ][ ];     /* не годится: компилятор
              не сможет сгенерить операцию индексации */

Вот как, к примеру, должна выглядеть работа  с  двумерным  массивом  arr[ROWS][COLS],
отведенным при помощи malloc();

    void f(int array[][COLS]){
            int x, y;
            for(y=0; y < ROWS; y++)
                for(x=0; x < COLS; x++)
                    array[y][x] = 1;
    }
    void main(){
            int *ptr = (int *) malloc(sizeof(int) * ROWS * COLS);
            f( (int (*) [COLS]) ptr);
    }


2.16.  Как описывать ссылки (указатели) на двумерные массивы?  Рассмотрим такую прог-
рамму:




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

    #include <stdio.h>
    #define First  3
    #define Second 5

    char arr[First][Second] = {
            "ABC.",
            { 'D', 'E', 'F', '?', '\0' },
            { 'G', 'H', 'Z', '!', '\0' }
    };

    char (*ptr)[Second];

    main(){
            int i;

            ptr = arr;      /* arr и ptr теперь взаимозаменимы */
            for(i=0; i < First; i++)
                    printf("%s\t%s\t%c\n", arr[i], ptr[i], ptr[i][2]);
    }

Указателем здесь является ptr. Отметим, что у  него  задана  размерность  по  второму
измерению:  Second, именно для того, чтобы компилятор мог правильно вычислить двумер-
ные индексы.
     Попробуйте сами объявить

    char (*ptr)[4];
    char (*ptr)[6];
    char **ptr;

и увидеть, к  каким  невеселым  эффектам  это  приведет  (компилятор,  кстати,  будет
ругаться;  но  есть вероятность, что он все же странслирует это для вас.  Но работать
оно будет плачевно).  Попробуйте также использовать ptr[x][y].
     Обратите также внимание на инициализацию строк в нашем примере.   Строка  "ABC."
равносильна объявлению

            { 'A', 'B', 'C', '.', '\0' },


2.17.  Массив s моделирует двумерный  массив  char  s[H][W];  Перепишите  пример  при
помощи     указателей,    избавьтесь    от    операции    умножения.    Прямоугольник
(x0,y0,width,height) лежит целиком внутри (0,0,W,H).

    char s[W*H]; int x,y; int x0,y0,width,height;
    for(x=0; x < W*H; x++) s[x] = '.';
         ...
    for(y=y0; y < y0+height; y++)
      for(x=x0; x < x0+width; x++)
          s[x + W*y] = '*';

Ответ:

    char s[W*H]; int i,j; int x0,y0,width,height;
    char *curs;
         ...
    for(curs = s + x0 + W*y0, i=0;
        i < height; i++, curs += W-width)
      for(j=0; j < width; j++)
            *curs++ = '*';

Такая оптимизация возможна в некоторых функциях из главы "Работа с видеопамятью".




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

2.18.  Что означают описания?

    int i;            // целое.
    int *pi;          // указатель на целое.
    int *api[3];      // массив из 3х ук-лей на целые.
    int (*pai)[3];    // указатель на массив из 3х целых.
                      // можно описать как    int **pai;
    int fi();         // функция, возвращающая целое.
    int *fpi();       // ф-ция, возвр. ук-ль на целое.
    int (*pfi)();     // ук-ль на ф-цию, возвращающую целое.
    int *(*pfpi)();   // ук-ль на ф-цию, возвр. ук-ль на int.
    int (*pfpfi())(); // ф-ция, возвращающая указатель на
                      // "функцию, возвращающую целое".
    int (*fai())[3];  // ф-ция, возвр. ук-ль на массив
                      // из 3х целых. иначе ее
                      // можно описать как    int **fai();
    int (*apfi[3])(); // массив из 3х ук-лей на функции,
                      // возвращающие целые.

Переменные в Си описываются в формате их использования.  Так описание

    int (*f)();

означает, что f можно использовать в виде

    int value;
    value = (*f)(1, 2, 3 /* список аргументов */);

Однако из такого способа описания  тип  самой  описываемой  переменной  и  его  смысл
довольно  неочевидны.  Приведем прием (позаимствованный из журнала "Communications of
the ACM"), позволяющий прояснить смысл описания.  Описание на Си переводится в описа-
ние в стиле языка Algol-68.  Далее

    ref      ТИП    означает  "указатель на ТИП"
    proc()   ТИП              "функция, возвращающая ТИП"
    array of ТИП              "массив из элементов ТИПа"
    x:       ТИП              "x имеет тип ТИП"

Приведем несколько примеров, из которых ясен и способ преобразования:

    int (*f())();     означает
            (*f())()  :                    int
             *f()     :             proc() int
              f()     :         ref proc() int
              f       :  proc() ref proc() int

то есть f - функция, возвращающая указатель на функцию, возвращающую целое.

    int (*f[3])();    означает
            (*f[])()  :                      int
             *f[]     :               proc() int
              f[]     :           ref proc() int
              f       :  array of ref proc() int

f - массив указателей на функции, возвращающие целые.  Обратно: опишем g  как  указа-
тель  на функцию, возвращающую указатель на массив из 5и указателей на функции, возв-
ращающие указатели на целые.







А. Богатырев, 1992-95                  - 91 -                               Си в 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])();

В Си невозможны функции, возвращающие массив:

    proc() array of ...
            а только
    proc() ref array of ...

Само название типа (например, для использования в операции приведения  типа)  получа-
ется вычеркиванием имени переменной (а также можно опустить размер массива):

            g = ( int *(*(*(*)())[])() ) 0;


2.19.  Напишите функцию strcat(d,s), приписывающую строку s к концу строки d.
Ответ:

     char *strcat(d,s) register char *d, *s;
     {  while( *d ) d++;      /* ищем конец строки d */
        while( *d++ = *s++ ); /* strcpy(d, s)        */
        return (d-1);         /* конец строки        */
     }

Цикл, помеченный "strcpy" - это наиболее краткая запись операторов

        do{ char c;
            c = (*d = *s); s++; d++;
        } while(c != '\0');

На самом деле strcat должен по стандарту возвращать свой первый аргумент, как и функ-
ция strcpy:

     char *strcat(d,s) register char *d, *s;
     {  char *p = d;
        while( *d ) d++;
        strcpy(d, s); return p;
     }

Эти два варианта демонстрируют, что функция может быть реализована разными способами.
Кроме  того  видно,  что  вместо стандартной библиотечной функции мы можем определить
свою одноименную функцию, несколько отличающуюся поведением от стандартной (как возв-
ращаемое значение в 1-ом варианте).

2.20.  Напишите программу, которая объединяет и распечатывает две строки, введенные с
терминала.  Для  ввода  строк  используйте  функцию  gets(),  а  для их объединения -
strcat(). В другом варианте используйте
  sprintf(result,"%s%s",s1,s2);

2.21.  Модифицируйте предыдущую программу таким образом,  чтобы  она  выдавала  длину
(число  символов)  объединенной строки.  Используйте функцию strlen().  Приведем нес-
колько версий реализации strlen:

    /* При помощи индексации массива */



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

    int strlen(s) char s[];
    {   int length = 0;
        for(; s[length] != '\0'; length++);
        return (length);
    }
    /* При помощи продвижения указателя */
    int strlen(s) char *s;
    {   int length;
        for(length=0; *s; length++, s++);
        return length;
    }
    /* При помощи разности указателей */
    int strlen(register char *s)
    {   register char *p = s;
        while(*p) p++;   /* ищет конец строки */
        return (p - s);
    }

Разность двух указателей на один и тот же тип - целое число:

    если TYPE *p1, *p2;
    то  p2 - p1 = целое число штук TYPE
                  лежащих между p2 и p1
    если p2 = p1 + n
    то   p2 - p1 = n

Эта разность может быть и отрицательной если p2 < p1, то есть p2 указывает  на  более
левый элемент массива.

2.22.  Напишите оператор Си, который обрубает строку s до длины n букв. Ответ:

    if( strlen(s) > n )


Первое сравнение вообще говоря излишне.  Оно написано лишь на тот случай, если строка
s  короче,  чем n букв и хранится в массиве, который также короче n, т.е. не имеет n-
ого элемента (поэтому в него нельзя производить запись признака конца).

2.23.  Напишите функции преобразования строки, содержащей изображение целого числа, в
само  это  число.  В  двух разных вариантах аргумент-адрес должен указывать на первый
байт строки; на последний байт. Ответ:

    #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)){



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

            res += (*s-- - '0') * pow;
            pow *= 10;
        }
        if(*s == '-') res = -res;
        return res;
    }


2.24.  Можно ли для занесения в массив s строки "hello" написать

            char s[6]; s = "hello";
                 или
            char s[6], d[] = "hello"; s = d;

Ответ: нет.  Массивы в Си нельзя присваивать целиком. Для пересылки массива байт надо
использовать функцию strcpy(s,d).  Здесь же мы пытаемся изменить адрес s (имя массива
- это адрес начала памяти, выделенной для хранения массива), сделав его равным адресу
безымянной  строки  "hello"  (или  массива  d во втором случае).  Этот адрес является
константой и не может быть изменен!
     Заметим однако, что описание массива с инициализацией вполне допустимо:

            char s[6] = "hello";
                    или
            char s[6] = { 'h', 'e', 'l', 'l', 'o', '\0' };
                    или
            char s[] = "hello";
                    или
            char s[] = { "hello" };

В этом случае компилятор резервирует память для хранения  массива  и  расписывает  ее
байтами  начального значения.  Обратите внимание, что строка в двойных кавычках (если
ее рассматривать как массив букв) имеет длину на единицу больше, чем написано букв  в
строке,  поскольку в конце массива находится символ '\0' - признак конца, добавленный
компилятором.  Если бы мы написали

            char s[5] = "hello";

то компилятор сообщил бы об ошибке, поскольку длины массива (5)  недостаточно,  чтобы
разместить  6 байт.  В третьей строке примера написано s[], чтобы компилятор сам пос-
читал необходимую длину массива.
     Наконец, возможна ситуация, когда массив больше, чем хранящаяся  в  нем  строка.
Тогда "лишнее" место содержит какой-то мусор (в static-памяти изначально - байты \0).

      char s[12] = "hello";
      содержит:     h e l l o \0 ? ? ? ? ? ?

В программах текстовой обработки под "длиной строки" обычно понимают количество  букв
в  строке  НЕ  считая  закрывающий  байт '\0'. Именно такую длину считает стандартная
функция strlen(s).  Поэтому следует различать  такие  понятия  как  "(текущая)  длина
строки"  и  "длина  массива,  в котором хранится строка": sizeof(s).  Для написанного
выше примера эти значения равны соответственно 5 и 12.
     Следует также отличать массивы от указателей:

            char *sp = "bye bye";
            sp = "hello";

будет вполне законно, поскольку в данном случае sp - не имя массива (т.е.  константа,
равная  адресу  начала  массива),  а  указатель (переменная, хранящая адрес некоторой
области памяти).  Поскольку указатель -  это  переменная,  то  ее  значение  изменять
можно:  в  данном  случае  sp  сначала содержала адрес безымянного массива, в котором
находится "bye bye"; затем мы занесли  в  sp  адрес  безымянного  массива,  хранящего



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

строку "hello".  Здесь не происходит копирования массива, а происходит просто присва-
ивание переменной sp нового значения адреса.
     Предостережем от возможной неприятности:

            char d[5]; char s[] = "abcdefgh";
            strcpy(d, s);

Длины массива d просто не хватит для хранения такой длинной  строки.   Поскольку  это
ничем не контролируется (ни компилятором, ни самой strcpy, ни вами явным образом), то
при копировании строки "избыточные" байты запишутся после  массива  d  поверх  других
данных, которые будут испорчены.  Это приведет к непредсказуемым эффектам.
     Некоторые возможности для контроля за длиной строк-аргументов вам  дают  функции
strncpy(d,s,len);  strncat(d,s,len); strncmp(s1,s2,len).  Они пересылают (сравнивают)
не более, чем len первых символов строки s (строк s1, s2).  Посмотрите  в  документа-
цию!  Напишите функцию strncmp (сравнение строк по первым len символам), посмотрев на
функцию 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;
    }

Отметьте, что strncpy обладает одним неприятным свойством: если n <= strlen(src),  то
строка  dst  не  будет иметь на конце символа '\0', то есть будет находиться в некор-
ректном (не каноническом) состоянии.
     Ответ:

    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.  В чем ошибка?

    #include <stdio.h>  /* для putchar */
    char s[] = "We don't need no education";
    main(){ while(*s) putchar(*s++); }

Ответ: здесь s - константа, к ней неприменима операция ++.  Надо написать

    char *s = "We don't need no education";

сделав s указателем на безымянный маccив. Указатель уже можно изменять.

2.26.  Какие из приведенных конструкций обозначают одно и то же?





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

    char a[]  = "";         /* пустая строка */
    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.  Найдите ошибки в описании символьной строки:

    main() {
       char mas[] = {'s', 'o', 'r', 't'};  /* "sort" ? */
       printf("%s\n", mas);
    }

Ответ: строка должна кончаться '\0' (в нашем случае printf не обнаружив символа конца
строки  будет  выдавать и байты, находящиеся в памяти после массива mas, т.е. мусор);
инициализированный массив не может быть автоматическим - требуется static:

    main() {
       static char mas[] = {'s', 'o', 'r', 't', '\0'};
    }

Заметим, что

    main(){    char *mas = "sort";   }

законно, т.к. сама строка здесь хранится в  статической  памяти,  а  инициализируется
лишь указатель на этот массив байт.

2.28.  В чем ошибка?  Программа собирается из двух файлов: a.c и b.c командой

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

Ответ: дело в том, что типы (char *) - указатель, и char[] - массив, означают одно  и
то же только при объявлении формального параметра функции:

    f(char *arg){...}    f(char arg[]){...}

это будет локальная переменная, содержащая указатель на char (т.е.  адрес  некоторого
байта  в  памяти).   Внутри функции мы можем изменять эту переменную, например arg++.
Далее, и (char *) и char[] одинаково  используются,  например,  оба  эти  типа  можно
индексировать: arg[i]. Но вне функций они объявляют разные объекты!  Так char *p; это
скалярная переменная, хранящая адрес (указатель):

      --------      -------
    p:|   *--|----->| '0' | char
      --------      | '1' | char
                      ...




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

тогда как char a[20]; это адрес начала массива (а вовсе не переменная):

                    -------
                  a:| '0' | char
                    | '1' | char
                      ...

В нашем примере в файле b.c мы объявили внешний массив s как переменную.   В  резуль-
тате  компилятор  будет  интерпретировать начало массива s как переменную, содержащую
указатель на char.

                    -------
                  s:| '0' |   \  это будет воспринято как
                    | '1' |   /  адрес других данных.
                    | '2' |
                      ...

И индексироваться будет уже ЭТОТ адрес!  Результат  -  обращение  по  несуществующему
адресу.  То, что написано у нас, эквивалентно

    char s[]  = "012345678";
    char **ss = s;      /* s - как бы "массив указателей"  */
         /* первые байты s интерпретируются как указатель: */
    char  *p  = ss[0];
         p[2] = '+';

Мы же должны были объявить в b.c

    extern char s[];  /* размер указывать не требуется */

Вот еще один аналогичный пример, который пояснит вам, что происходит (а заодно  пока-
жет порядок байтов в long).  Пример выполнялся на IBM PC 80386, на которой

           sizeof(char *) = sizeof(long) = 4


    a.c                       b.c
    ---------------------------------------------------
    char s[20] = {1,2,3,4};   extern char *s;
    main(){                   f(){
                                /*печать указателя как long */
      f();                       printf( "%08lX\n", s );
    }                         }

печатается 04030201.

2.29.  Что напечатает программа?

      static char str1[ ]  = "abc";
      static char str2[4];

      strcpy( str2, str1 );
      /* можно ли написать str2 = str1; ? */

      printf( str1 == str2 ? "равно":"не равно" );

Как надо правильно сравнивать строки? Что на самом деле сравнивается  в  данном  при-
мере?
     Ответ: сравниваются адреса массивов, хранящих строки. Так





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

    char str1[2];
    char str2[2];
    main(){
      printf( str1 < str2 ? "<":">");
    }

печатает <&lt;, а если написать

    char str2[2];
    char str1[2];

то напечатается >&gt;.

2.30.  Напишите программу, спрашивающую ваше имя до тех пор, пока вы его правильно не
введете. Для сравнения строк используйте функцию strcmp() (ее реализация есть в главе
"Мобильность").

2.31.  Какие значения возвращает функция strcmp() в следующей программе?

    #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.  В качестве итога предыдущих задач: помните, что в Си строки (а не адреса) надо
сравнивать как

    if( strcmp("abc", "bcd") <  0) ... ;
    if( strcmp("abc", "bcd") == 0) ... ;
               вместо
    if( "abc" <  "bcd" ) ... ;
    if( "abc" == "bcd" ) ... ;

и присваивать как

    char d[80], s[80];
    strcpy( d, s );      вместо    d = s;


2.33.  Напишите программу, которая сортирует по алфавиту и печатает следующие  ключе-
вые слова языка Си:

            int char double long
            for while if


2.34.  Вопрос не совсем про строки, скорее про цикл: чем плоха конструкция?

    char s[]  = "You're a smart boy, now shut up.";
    int i, len;
    for(i=0; i < strlen(s); i++)
             putchar(s[i]);

Ответ: в соответствии с семантикой Си цикл развернется примерно в





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

            i=0;
    LOOP:   if( !(i < strlen(s))) goto ENDLOOP;
              putchar(s[i]);
            i++;
            goto LOOP;
    ENDLOOP:         ;

Заметьте, что хотя длина строки s не меняется, strlen(s) вычисляется на КАЖДОЙ итера-
ции цикла, совершая лишнюю работу!  Борьба с этим такова:

    for(i=0, len=strlen(s); i < len; i++ )
             putchar(s[i]);
        или
    for(i=0, len=strlen(s); len > 0; i++, --len )
             putchar(s[i]);

Аналогично, в цикле

    while( i < strlen(s))...;

функция тоже будет вычисляться при каждой проверке условия! Это, конечно, относится к
любой функции, используемой в условии, а не только к strlen.  (Но, разумеется, случай
когда функция возвращает признак "надо ли продолжать  цикл"  -  совсем  другое  дело:
такая функция обязана вычисляться каждый раз).

2.35.  Что напечатает следующая программа?

    #include <stdio.h>
    main(){
        static char str[] = "До встречи в буфете";
        char *pt;

        pt = str; puts(pt); puts(++pt);
        str[7] = '\0'; puts(str); puts(pt);
        puts(++pt);
    }


2.36.  Что напечатает следующая программа?

    main() {
        static char name[] = "Константин";
        char *pt;
        pt = name + strlen(name);
        while(--pt >= name)
             puts(pt);
    }


2.37.  Что напечатает следующая программа?

        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;



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

            while( *b )
                   *++a = *b++;
            printf( "str=%s a=%s\n", str1, a );
        }

Ответ:

       str=xyzdef a=def
       str=xxyzef a=zef


2.38.  Что печатает программа?

    char *s;
    for(s = "Ситроен"; *s; s+= 2){
        putchar(s[0]); if(!s[1]) break;
    }
    putchar('\n');


2.39.  Что напечатает программа? Рассмотрите продвижение указателя  s,  указателей  -
элементов массива strs[]. Разберитесь с порядком выполнения операций. В каких случаях
++ изменяет указатель, а в каких - букву в строке? Нарисуйте себе картинку, изобража-
ющую  состояние указателей - она поможет вам распутать эти спагетти.  Уделите разбору
этого примера достаточное время!

    #include <stdio.h>      /* определение NULL */
    /* Латинский алфавит: 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');
    }

Печатается:






А. Богатырев, 1992-95                  - 100 -                              Си в 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

Учтите, что конструкция

    char *strs[1] = { "hello" };

означает, что в strs[0] содержится указатель на начальный байт  безымянного  массива,
содержащего строку "hello".  Этот указатель можно изменять!  Попробуйте составить еще
подобные примеры из *, ++, ().

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;

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



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

     Вот программа, которая  также  использует  неинициализированный  указатель.   На
машине  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;
    }




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

Заметьте, что 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)

или более корректная функция:












А. Богатырев, 1992-95                  - 103 -                              Си в 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';  /* пустая строка */
       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 в каждом случае,  когда  он
встречается.
Ответ:







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

    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;



А. Богатырев, 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 <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
берется из УЖЕ перезаписанной ранее области, то  есть  испорченной!   Вот  программа,
иллюстрирующая эту проблему:





















А. Богатырев, 1992-95                  - 106 -                              Си в 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,   "перед");
                      *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 <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-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 <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.  Еще об опечатках: вот что бывает, когда вместо знака `='  печатается  `-'  (на
клавиатуре они находятся рядом...).









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

    #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 (кстати, это стандартная функция), приведем
еще одну функцию для сохранения строк.

    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.



















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

    /*
     * Машинно - независимый 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++;                   \
            }


    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-95                  - 114 -                              Си в 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);
    }








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

    /* --------------------------------------------------------- */
    int strnlen(s, maxlen) char *s;
    {
            register n;
            for( n=0; *s && n < maxlen; n++, s++ );
            return n;
    }


    /* Печать строки */
    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);
    }




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

    static prRdig(n, d){
            if( !n ) return;
            if( d ) prRdig( n/10, d - 2);
            tack(n%10, d);
    }


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













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

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


    static prIntStr( s, width, zero, neg ) char *s;
    {
      int ln;         /* сколько символов выводить */
      int toLeft = 0; /* к какому краю прижимать   */

      ln = strlen(s);            /* длина строки s */

      /* Ширина поля: вычислить, если не указано явно */
      if( ! width ){
            width = ln;  /* ширина поля    */
            if( neg )   width++;         /* 1 символ для минуса */
      }
      if( width < 0 ){ width = -width; toLeft = 1; }

      if( ! neg ){  /* Положительное число */
        if(width > ln){
            if(toLeft){ pr(s, ln);              prSpace(width - ln, ' ');  }
            else      { prSpace(width - ln, zero ? '0' : ' '); pr(s, ln);  }
        }   else      { pr(s, ln);                                         }

      }else{        /* Отрицательное число */
        if(width > ln){
            /* Надо заполнять оставшуюся часть поля */

            width -- ; /* width содержит одну позицию для минуса */
            if(toLeft){ putchar('-'); pr(s, ln); prSpace(width - ln, ' ');  }
            else{
                    if( ! zero ){
                            prSpace(width - ln, ' '); putchar('-'); pr(s,ln);
                    } else {
                            putchar('-'); prSpace(width - ln, '0'); pr(s, ln);
                    }
            }
        }   else    {       putchar('-'); pr(s, ln);   }
      }
    }




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

    /* --------------------------------------------------------- */
    main(){
            int i, n;
            static char s[] = "Hello, world!\n";
            static char p[] = "Hello, world";
            long t = 7654321L;

            myprintf( "%%abc%Y\n");
            myprintf( "%s\n",        "abs" );
            myprintf( "%5s|\n",       "abs" );
            myprintf( "%-5s|\n",      "abs" );
            myprintf( "%5s|\n",       "xyzXYZ" );
            myprintf( "%-5s|\n",      "xyzXYZ" );
            myprintf( "%5.5s|\n",     "xyzXYZ" );
            myprintf( "%-5.5s|\n",    "xyzXYZ" );
            myprintf( "%r\n",       444 );
            myprintf( "%r\n",       999 );
            myprintf( "%r\n",       16 );
            myprintf( "%r\n",       18 );
            myprintf( "%r\n",       479 );
            myprintf( "%d\n",  1234 );
            myprintf( "%d\n",  -1234 );
            myprintf( "%ld\n",  97487483 );
            myprintf( "%2d|%2d|\n",   1, -3 );
            myprintf( "%-2d|%-2d|\n", 1, -3 );
            myprintf( "%02d|%2d|\n",   1, -3 );
            myprintf( "%-02d|%-2d|\n", 1, -3 );
            myprintf( "%5d|\n",   -12 );
            myprintf( "%05d|\n",  -12 );
            myprintf( "%-5d|\n",  -12 );
            myprintf( "%-05d|\n", -12 );


            for( i = -6; i < 6; i++ )
                 myprintf( "width=%2d|%0*d|%0*d|%*d|%*d|\n", i,
                   i,  123,  i, -123, i,  123, i, -123);
            myprintf( "%s at location %a\n", s, s );
            myprintf( "%ld\n", t );
            n = 1; t = 1L;
            for( i=0; i < 34; i++ ){
         myprintf( "for %2d   |%016b|%d|%u|\n\t |%032lb|%ld|%lu|\n",
                          i,       n, n, n,           t,  t,  t );
                    n *= 2;
                    t *= 2;
            }
            myprintf( "%8x %8X\n", 7777, 7777 );
            myprintf( "|%s|\n", p );
            myprintf( "|%10s|\n", p );
            myprintf( "|%-10s|\n", p );
            myprintf( "|%20s|\n", p );
            myprintf( "|%-20s|\n", p );
            myprintf( "|%20.10s|\n", p );
            myprintf( "|%-20.10s|\n", p );
            myprintf( "|%.10s|\n", p );
    }









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

         Выдача этой программы:
    %abcY
    abs
      abs|
    abs  |
    xyzXYZ|
    xyzXYZ|
    xyzXY|
    xyzXY|
    CDXLIV
    CMXCIX
    XVI
    XVIII
    CDLXXIX
    1234
    -1234
    97487483
     1|-3|
    1 |-3|
    01|-3|
    1 |-3|
      -12|
    -0012|
    -12  |
    -12  |
    width=-6|123   |-123  |123   |-123  |
    width=-5|123  |-123 |123  |-123 |
    width=-4|123 |-123|123 |-123|
    width=-3|123|-123|123|-123|
    width=-2|123|-123|123|-123|
    width=-1|123|-123|123|-123|
    width= 0|123|-123|123|-123|
    width= 1|123|-123|123|-123|
    width= 2|123|-123|123|-123|
    width= 3|123|-123|123|-123|
    width= 4|0123|-123| 123|-123|
    width= 5|00123|-0123|  123| -123|
    Hello, world!
     at location 400980
    7654321
    for  0   |0000000000000001|1|1|
             |00000000000000000000000000000001|1|1|
    for  1   |0000000000000010|2|2|
             |00000000000000000000000000000010|2|2|
    for  2   |0000000000000100|4|4|
             |00000000000000000000000000000100|4|4|
    for  3   |0000000000001000|8|8|
             |00000000000000000000000000001000|8|8|
    for  4   |0000000000010000|16|16|
             |00000000000000000000000000010000|16|16|
    for  5   |0000000000100000|32|32|
             |00000000000000000000000000100000|32|32|
    for  6   |0000000001000000|64|64|
             |00000000000000000000000001000000|64|64|
    for  7   |0000000010000000|128|128|
             |00000000000000000000000010000000|128|128|
    for  8   |0000000100000000|256|256|
             |00000000000000000000000100000000|256|256|
    for  9   |0000001000000000|512|512|
             |00000000000000000000001000000000|512|512|
    for 10   |0000010000000000|1024|1024|



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

             |00000000000000000000010000000000|1024|1024|
    for 11   |0000100000000000|2048|2048|
             |00000000000000000000100000000000|2048|2048|
    for 12   |0001000000000000|4096|4096|
             |00000000000000000001000000000000|4096|4096|
    for 13   |0010000000000000|8192|8192|
             |00000000000000000010000000000000|8192|8192|
    for 14   |0100000000000000|16384|16384|
             |00000000000000000100000000000000|16384|16384|
    for 15   |1000000000000000|32768|32768|
             |00000000000000001000000000000000|32768|32768|
    for 16   |10000000000000000|65536|65536|
             |00000000000000010000000000000000|65536|65536|
    for 17   |100000000000000000|131072|131072|
             |00000000000000100000000000000000|131072|131072|
    for 18   |1000000000000000000|262144|262144|
             |00000000000001000000000000000000|262144|262144|
    for 19   |10000000000000000000|524288|524288|
             |00000000000010000000000000000000|524288|524288|
    for 20   |100000000000000000000|1048576|1048576|
             |00000000000100000000000000000000|1048576|1048576|
    for 21   |1000000000000000000000|2097152|2097152|
             |00000000001000000000000000000000|2097152|2097152|
    for 22   |10000000000000000000000|4194304|4194304|
             |00000000010000000000000000000000|4194304|4194304|
    for 23   |100000000000000000000000|8388608|8388608|
             |00000000100000000000000000000000|8388608|8388608|
    for 24   |1000000000000000000000000|16777216|16777216|
             |00000001000000000000000000000000|16777216|16777216|
    for 25   |10000000000000000000000000|33554432|33554432|
             |00000010000000000000000000000000|33554432|33554432|
    for 26   |100000000000000000000000000|67108864|67108864|
             |00000100000000000000000000000000|67108864|67108864|
    for 27   |1000000000000000000000000000|134217728|134217728|
             |00001000000000000000000000000000|134217728|134217728|
    for 28   |10000000000000000000000000000|268435456|268435456|
             |00010000000000000000000000000000|268435456|268435456|
    for 29   |100000000000000000000000000000|536870912|536870912|
             |00100000000000000000000000000000|536870912|536870912|
    for 30   |1000000000000000000000000000000|1073741824|1073741824|
             |01000000000000000000000000000000|1073741824|1073741824|
    for 31   |10000000000000000000000000000000|-2147483648|2147483648|
             |10000000000000000000000000000000|-2147483648|2147483648|
    for 32   |0000000000000000|0|0|
             |00000000000000000000000000000000|0|0|
    for 33   |0000000000000000|0|0|
             |00000000000000000000000000000000|0|0|
        1e61     1E61
    |Hello, world|
    |Hello, world|
    |Hello, world|
    |        Hello, world|
    |Hello, world        |
    |          Hello, wor|
    |Hello, wor          |
    |Hello, wor|


2.62.  Рассмотрим программу суммирования векторов:





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

    int A[1024], B[1024], C[1024];
            ...
    for(i=0; i < 1024; i++) C[i] = A[i] + B[i];

А почему бы не

    for(i=1024-1; i >=0 ; --i) ...;

А почему бы не в произвольном порядке?

    foreach i in (0..1023) ...;

Данный пример показывает, что некоторые операции обладают  врожденным  паралеллизмом,
ведь  все 1024 сложений можно было бы выполнять параллельно!  Однако тупой компилятор
будет складывать их именно в том порядке, в котором вы ему велели.  Только самые сов-
ременные  компиляторы на многопроцессорных системах умеют автоматически распараллели-
вать такие циклы. Сам язык Си не содержит средств указания параллельности (разве  что
снова - библиотеки и системные вызовы для этого).