1.110.

Почему программа зацикливается и печатает совсем не то, что нажато на клавиатуре, а только 0 и 1?
            while ( c = getchar() != 'e')
                    printf("%d %c\n, c, c);
Ответ: данный фрагмент должен был выглядеть так:
            while ((c = getchar()) != 'e')
                    printf("%d %c\n, c, c);
Сравнение в Си имеет высший приоритет, нежели присваивание! Мораль: надо быть внимательнее к приоритетам операций. Еще один пример на похожую тему:
           вместо
    if(  x & 01  == 0 ) ...     if( c&0377  > 0300)...;
           надо:
    if( (x & 01) == 0 ) ...     if((c&0377) > 0300)...;
И еще пример с аналогичной ошибкой:
    FILE *fp;
    if( fp = fopen( "файл", "w" ) == NULL ){
        fprintf( stderr, "не могу писать в файл\n");
        exit(1);
    }
    fprintf(fp,"Good bye, %s world\n","cruel"); fclose(fp);

В этом примере файл открывается, но fp равно 0 (логическое значение!) и функция fprintf() не срабатывает (программа падает по защите памяти*).

Исправьте аналогичную ошибку (на приоритет операций) в следующей функции:

    /* копирование строки from в to */
    char *strcpy( to, from ) register char *from, *to;
    {
         char *p = to;
         while( *to++ = *from++ != '\0' );
         return p;
    }

1.111.

Сравнения с нулем (0, NULL, '\0') в Си принято опускать (хотя это не всегда способствует ясности).

        if( i == 0 ) ...;    -->    if( !i ) ... ;
        if( i != 0 ) ...;    -->    if(  i ) ... ;
например, вместо
    char s[20], *p ;
    for(p=s; *p != '\0'; p++ ) ... ;
         будет
    for(p=s; *p; p++ ) ... ;
и вместо
    char s[81], *gets();
    while( gets(s) != NULL ) ... ;
         будет
    while( gets(s)) ... ;
Перепишите strcpy в этом более лаконичном стиле.

1.112.

Истинно ли выражение
            if( 2 < 5 < 4 )

Ответ: да! Дело в том, что Си не имеет логического типа, а вместо "истина" и "ложь" использует целые значения "не 0" и "0" (логические операции выдают 1 и 0). Данное выражение в условии if эквивалентно следующему:

            ((2 < 5) < 4)
Значением (2 < 5) будет 1. Значением (1 < 4) будет тоже 1 (истина). Таким образом мы получаем совсем не то, что ожидалось. Поэтому вместо
            if( a < x < b )
надо писать
            if( a < x && x < b )

1.113.

Данная программа должна печатать коды вводимых символов. Найдите опечатку; почему цикл сразу завершается?

      int c;
      for(;;) {
          printf("Введите очередной символ:");
          c = getchar();
          if(c = 'e') {
             printf("нажато e, конец\n"); break;
          }
          printf( "Код %03o\n", c & 0377 );
      }
Ответ: в if имеется опечатка: использовано `=' вместо `=='.

Присваивание в Си (а также операции +=, -=, *=, и.т.п.) выдает новое значение левой части, поэтому синтаксической ошибки здесь нет! Написанный оператор равносилен

          c = 'e'; if( c ) ... ;

и, поскольку 'e'!= 0, то условие оказывается истинным! Это еще и следствие того, что в Си нет специального логического типа (истина/ложь). Будьте внимательны: компилятор не считает ошибкой использование оператора = вместо == внутри условий if и условий циклов (хотя некоторые компиляторы выдают предупреждение).

Еще аналогичная ошибка:

    for( i=0; !(i = 15) ; i++ ) ... ;
(цикл не выполняется); или
    static char s[20] = "   abc"; int i=0;
    while(s[i] = ' ') i++;
    printf("%s\n", &s[i]); /* должно напечататься abc */
(строка заполняется пробелами и цикл не кончается).

То, что оператор присваивания имеет значение, весьма удобно:

    int x, y, z;           это на самом деле
    x = y = z = 1;         x = (y = (z = 1));
или**
    y=f( x += 2 );       // вместо x+=2; y=f(x);
    if((y /= 2) > 0)...; // вместо y/=2; if(y>0)...;
Вот пример упрощенной игры в "очко" (упрощенной - т.к. не учитывается ограниченность числа карт каждого типа в колоде (по 4 штуки)):
    #include <stdio.h>
    main(){
      int sum = 0, card; char answer[36];
      srand( getpid());  /* рандомизация */
      do{  printf( "У вас %d очков. Еще? ", sum);
           if( *gets(answer) == 'n' ) break;
           /* иначе маловато будет */
           printf( "  %d очков\n",
                   card = 6 + rand() % (11 - 6 + 1));
      } while((sum += card) < 21);      /* SIC ! */
      printf ( sum == 21 ? "очко\n"   :
               sum >  21 ? "перебор\n":
                           "%d очков\n", sum);
    }

Вот еще пример, использующийся для подсчета правильного размера таблицы. Обратите внимание, что присваивания используются в сравнениях, в аргументах вызова функции (printf), т.е. везде, где допустимо выражение:

    #include <stdio.h>
    int width = 20; /* начальное значение ширины поля */
    int len; char str[512];
    main(){
      while(gets(str)){
        if((len = strlen(str)) > width){
    fprintf(stderr,"width увеличить до %d\n", width=len);
        }
        printf("|%*.*s|\n", -width, width, str);
      }
    }
Вызывай эту программу как
a.out < входнойФайл > /dev/null

1.114.

Почему программа "зависает" (на самом деле - зацикливается) ?
            int x = 0;
            while( x < 100 );
                    printf( "%d\n", x++ );
            printf( "ВСЕ\n" );
Указание: где кончается цикл while?

Мораль: не надо ставить ; где попало. Еще мораль: даже отступы в оформлении программы не являются гарантией отсутствия ошибок в группировке операторов.

1.115.

Вообще, приоритеты операций в Си часто не соответствуют ожиданиям нашего здравого смысла. Например, значением выражения:

            x = 1 << 2 + 1 ;

будет 8, а не 5, поскольку сложение выполнится первым. Мораль: в затруднительных и неочевидных случаях лучше явно указывать приоритеты при помощи круглых скобок:

            x = (1 << 2) + 1 ;
Еще пример: увеличивать x на 40, если установлен флаг, иначе на 1:
            int bigFlag = 1, x = 2;
            x = x + bigFlag ? 40 : 1;
            printf( "%d\n", x );
ответом будет 40, а не 42, поскольку это
            x = (x + bigFlag) ? 40 : 1;
а не
            x = x + (bigFlag ? 40 : 1);
которое мы имели в виду. Поэтому вокруг условного выражения ?: обычно пишут круглые скобки.

Заметим, что () указывают только приоритет, но не порядок вычислений. Так, компилятор имеет полное право вычислить

    long a = 50, x; int b = 4;
    x = (a * 100) / b;
      /* деление целочисленное с остатком ! */
    и как   x = (a * 100)/b = 5000/4 = 1250
    и как   x = (a/b) * 100 = 12*100 = 1200

невзирая на наши скобки, поскольку и * и / имеют одинаковый приоритет (хотя это "право" еще не означает, что он обязательно так поступит). Такие операторы приходится разбивать на два, т.е. вводить промежуточную переменную:

    { long a100 = a * 100; x = a100 / b; }

1.116.

Составьте программу вычисления тригонометрической функции. Название функции и значение аргумента передаются в качестве параметров функции main (см. про argv и argc в главе "Взаимодействие с UNIX"):

            $ a.out sin 0.5
            sin(0.5)=0.479426
(здесь и далее значок $ обозначает приглашение, выданное интерпретатором команд).

Для преобразования строки в значение типа double воспользуйтесь стандартной функцией atof().

      char *str1, *str2, *str3; ...
      extern double atof();    double x = atof(str1);
      extern long   atol();    long   y = atol(str2);
      extern int    atoi();    int    i = atoi(str3);
либо
    sscanf(str1, "%f",  &x);
    sscanf(str2, "%ld", &y); sscanf(str3,"%d", &i);

К слову заметим, что обратное преобразование - числа в текст - удобнее всего делается при помощи функции sprintf(), которая аналогична printf(), но сформированная ею строка-сообщение не выдается на экран, а заносится в массив:

            char represent[ 40 ];
            int i = ... ;
            sprintf( represent, "%d", i );

1.117.

Составьте программу вычисления полинома n-ой степени:
               n          n-1
     Y = A  * X + A    * X    + ... + A0
          n        n-1
схема (Горнера):
     Y = A0 + X * ( A1 + X * ( A2 + ... + X * An )))...)
Оформите алгоритм как функцию с переменным числом параметров:
    poly( x, n, an, an-1, ... a0 );
О том, как это сделать - читайте раздел руководства по UNIX man varargs. Ответ:
    #include <varargs.h>
    double poly(x, n, va_alist)
           double x; int n; va_dcl
    {
      va_list args;
      double sum = 0.0;
      va_start(args); /* инициализировать список арг-тов */
      while( n-- >= 0 ){
         sum *= x;
         sum += va_arg(args, double);
         /* извлечь след. аргумент типа double */
      }
      va_end(args);   /* уничтожить список аргументов */
      return sum;
    }
    main(){
                            /* y = 12*x*x + 3*x + 7 */
      printf( "%g\n", poly(2.0, 2, 12.0,    3.0,  7.0));
    }
Прототип этой функции:
    double poly(double x, int n, ... );

В этом примере использованы макросы va_нечто. Часть аргументов, которая является списком переменной длины, обозначается в списке параметров как va_alist, при этом она объявляется как va_dcl в списке типов параметров. Заметьте, что точка-с-запятой после va_dcl не нужна! Описание va_list args; объявляет специальную "связную" переменную; смысл ее машинно зависим. va_start(args) инициализирует эту переменную списком фактических аргументов, соответствующих va_alist-у. va_end(args) деинициализирует эту переменную (это надо делать обязательно, поскольку инициализация могла быть связана с конструированием списка аргументов при помощи выделения динамической памяти; теперь мы должны уничтожить этот список и освободить память). Очередной аргумент типа TYPE извлекается из списка при помощи

    TYPE x = va_arg(args, TYPE);
Список аргументов просматривается слева направо в одном направлении, возврат к предыдущему аргументу невозможен.

Нельзя указывать в качестве типов char, short, float:

    char ch = va_arg(args, char);
поскольку в языке Си аргументы функции таких типов автоматически расширяются в int, int, double соответственно. Корректно будет так:
    int ch = va_arg(args, int);

1.118.

Еще об одной ловушке в языке Си на PDP-11 (и в компиляторах бывают ошибки!):
            unsigned x = 2;
            printf( "%ld %ld",
                    - (long) x,
                    (long)  -x
            );

Этот фрагмент напечатает числа -2 и 65534. Во втором случае при приведении к типу long был расширен знаковый бит. Встроенная операция sizeof выдает значение типа unsigned. Подумайте, каков будет эффект в следующем фрагменте программы?

            static struct point{ int  x,  y    ;}
                          p =  {     33, 13   };
            FILE *fp = fopen( "00", "w" );
            /* вперед на длину одной структуры */
            fseek( fp, (long)  sizeof( struct point ), 0 );
            /* назад на длину одной структуры */
     /*!*/  fseek( fp, (long) -sizeof( struct point ), 1 );
            /* записываем в начало файла одну структуру */
            fwrite( &p, sizeof p, 1, fp );
            /* закрываем файл */
            fclose( fp );

Где должен находиться минус во втором вызове fseek для получения ожидаемого результата? (Данный пример может вести себя по-разному на разных машинах, вопросы касаются PDP-11).

1.119.

Обратимся к указателям на функции:
    void g(x){ printf("%d: here\n", x); }
    main(){
      void (*f)() = g;  /* Указатель смотрит на функцию g() */
      (*f)(1); /* Старая форма вызова функции по указателю */
        f (2); /* Новая  форма вызова */
      /* В обоих случаях вызывается g(x); */
    }
Что печатает программа?
    typedef void (*(*FUN))(); /* Попытка изобразить
            рекурсивный тип typedef FUN (*FUN)(); */
    FUN  g(FUN f){ return f; }
    void main(){
         FUN y = g(g(g(g(g))));
         if(y == g) printf("OK\n");
    }
Что печатает программа?
            char *f(){
                    return "Hello, user!";
            }
            g(func)
                char * (*func)();
            {
                    puts((*func)());
            }
            main(){
                    g(f);
            }
Почему было бы неверно написать
            main(){
                    g(f());
            }
Еще аналогичная ошибка (посмотрите про функцию signal в главе "Взаимодействие с UNIX"):
            #include <signal.h>
            f(){ printf( "Good bye.\n" ); exit(0); }
            main(){
                 signal ( SIGINT, f() );
                 ...
            }

Запомните, что f() - это ЗНАЧЕНИЕ функции f (т.е. она вызывается и нечто возвращает return-ом; это-то значение мы и используем), а f - это АДРЕС функции f (раньше это так и писалось &f), то есть метка начала ее машинных кодов ("точка входа").

* "Падать" - программистский жаргон. Означает "аварийно завершаться". "Защита памяти" - обращение по некорректному адресу. В UNIX такая ошибка ловится аппаратно, и программа будет убита одним из сигналов: SIGBUS, SIGSEGV, SIGILL. Система сообщит нечто вроде "ошибка шины". Знайте, что это не ошибка аппаратуры и не сбой, а ВАША ошибка!

** Конструкция //текст, которая будет изредка попадаться в дальнейшем - это комментарий в стиле языка C++. Такой комментарий простирается от символа // до конца строки.

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

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