Главная · Поиск книг · Поступления книг · Top 40 · Форумы · Ссылки · Читатели

Настройка текста
Перенос строк


    Прохождения игр    
Demon's Souls |#13| Storm King
Demon's Souls |#12| Old Monk & Old Hero
Demon's Souls |#11| Мaneater part 2
Demon's Souls |#10| Мaneater (part 1)

Другие игры...


liveinternet.ru: показано число просмотров за 24 часа, посетителей за 24 часа и за сегодня
Rambler's Top100
Образование - Богатырев А. Весь текст 1009.15 Kb

Хрестоматия по программированию на Си в Unix

Предыдущая страница Следующая страница
1 2 3 4 5  6 7 8 9 10 11 12 13 14 ... 87
    }

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

Ответ: при приведении char к int расширился знаковый бит (7-ой),  заняв  всю  старшую
часть  слова.  Знаковый бит int-а стал равен 1, что является признаком отрицательного
числа. То же будет происходить со всеми значениями c из диапазона 128..255  (содержа-
щими бит 0200).  При приведении unsigned char к int знаковый бит не расширяется.
     Можно было поступить еще и так:

    printf( "%d\n", c & 0377 );

Здесь c приводится к типу int (потому что при использовании в аргументах функции  тип
char  ВСЕГДА  приводится  к  типу  int), затем &0377 занулит старший байт полученного
целого числа (состоящий из битов 1), снова превратив число в положительное.

1.70.  Почему

    printf("%d\n", '\377' == 0377 );
    printf("%d\n", '\xFF' == 0xFF );

печатает 0 (ложь)?  Ответ: по той же причине, по которой

    printf("%d %d\n", '\377', 0377);

печатает -1 255, а именно: char '\377' приводится в выражениях к  целому  расширением
знакового бита (а 0377 - уже целое).

1.71.  Рассмотрим программу

    #include 
    int main(int ac, char **av){
            int c;

            while((c = getchar()) != EOF)
                    switch(c){
                    case 'ы': printf("Буква ы\n"); break;
                    case 'й': printf("Буква й\n"); break;
                    default:  printf("Буква с кодом %d\n", c); break;
                    }
            return 0;
    }

Она работает так:

    % a.out
    йфыв
    Буква с кодом 202
    Буква с кодом 198
    Буква с кодом 217
    Буква с кодом 215
    Буква с кодом 10
    ^D
    %

Выполняется всегда default, почему не выполняются case 'ы' и case 'й'?
     Ответ: русские буквы имеют восьмой бит (левый) равный 1. В case такой байт  при-
водится  к  типу  int  расширением  знакового бита.  В итоге получается отрицательное
число.  Пример:

    void main(void){
            int c = 'й';
            printf("%d\n", c);
    }
    печатает -54

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

Решением служит подавление расширения знакового бита:

    #include 
    /* Одно из двух */
    #define U(c)    ((c) & 0xFF)
    #define UC(c)   ((unsigned char) (c))
    int main(int ac, char **av){
            int c;

            while((c = getchar()) != EOF)
                    switch(c){
                    case U('ы'):    printf("Буква ы\n"); break;
                    case UC('й'):   printf("Буква й\n"); break;
                    default:        printf("Буква с кодом %d\n", c); break;
                    }
            return 0;
    }

Она работает правильно:

    % a.out
    йфыв
    Буква й
    Буква с кодом 198
    Буква ы
    Буква с кодом 215
    Буква с кодом 10
    ^D
    %

Возможно также использование кодов букв:

    case 0312:

но это гораздо менее наглядно. Подавление знакового бита необходимо также и в  опера-
торах if:

    int c;
            ...
    if(c == 'й') ...

            следует заменить на

    if(c == UC('й')) ...

Слева здесь - signed int, правую часть компилятор тоже приводит к signed int.  Прихо-
дится явно говорить, что справа - unsigned.

1.72.  Рассмотрим программу, которая должна напечатать числа от 0 до 255.   Для  этих
чисел в качестве счетчика достаточен один байт:

    int main(int ac, char *av[]){
        unsigned char ch;

        for(ch=0; ch < 256; ch++)

        return 0;
    }

Однако эта программа зацикливается, поскольку в момент, когда ch==255,  это  значение
меньше  256.  Следующим шагом выполняется ch++, и ch становится равно 0, ибо для char

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

вычисления ведутся по модулю 256 (2 в 8 степени).  То есть в данном случае 255+1=0
     Решений существует два: первое - превратить unsigned char в int.  Второе - вста-
вить явную проверку на последнее значение диапазона.

    int main(int ac, char *av[]){
        unsigned char ch;

        for(ch=0; ; ch++){
            printf("%d\n", ch);
            if(ch == 255) break;
        }
        return 0;
    }

1.73.  Подумайте, почему для

    unsigned a, b, c;
    a < b + c    не эквивалентно   a - b < c

(первое - более корректно). Намек в виде примера (он выполнялся на 32-битной машине):

    a = 1; b = 3; c = 2;
    printf( "%u\n", a - b );     /* 4294967294, хотя в
                       нормальной арифметике 1 - 3 = -2 */
    printf( "%d\n", a < b + c ); /* 1 */
    printf( "%d\n", a - b < c ); /* 0 */

Могут ли unsigned числа быть отрицательными?

1.74.  Дан текст:

    short x = 40000;
    printf("%d\n", x);

Печатается -25536. Объясните эффект. Указание: каково наибольшее представимое  корот-
кое целое (16 битное)? Что на самом деле оказалось в x?  (лишние слева биты - обруба-
ются).

1.75.  Почему в примере

            double x = 5 / 2;
            printf( "%g\n", x );

значение x равно 2 а не 2.5 ?
     Ответ: производится целочисленное деление, затем в присваивании  целое  число  2
приводится  к  типу double. Чтобы получился ответ 2.5, надо писать одним из следующих
способов:

            double x = 5.0 / 2;
            x = 5 / 2.0;
            x = (double) 5 / 2;
            x = 5 / (double) 2;
            x = 5.0 / 2.0;

то есть в выражении должен быть хоть один операнд типа double.
     Объясните, почему следующие три оператора выдают такие значения:

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

            double g = 9.0;
            int t = 3;
            double dist = g *  t * t / 2;    /* 40.5 */
                   dist = g * (t * t / 2);   /* 36.0 */
                   dist = g * (t * t / 2.0); /* 40.5 */

В каких случаях деление целочисленное, в каких - вещественное?  Почему?

1.76.  Странслируйте пример на машине с длиной слова int равной 16 бит:

            long n  = 1024 * 1024;
            long nn =  512 *  512;
            printf( "%ld %ld\n", n, nn );

Почему печатается 0 0 а не 1048576 262144?
     Ответ: результат умножения (2**20 и 2**18) - это целое число; однако оно слишком
велико  для  сохранения  в  16  битах, поэтому старшие биты обрубаются. Получается 0.
Затем в присваивании это уже обрубленное значение приводится к типу long (32 бита)  -
это все равно будет 0.
     Чтобы получить корректный результат, надо чтобы выражение справа от = уже  имело
тип long и сразу сохранялось в 32 битах. Для этого оно должно иметь хоть один операнд
типа long:

            long n = (long) 1024 * 1024;
            long nn =        512 *  512L;

1.77.  Найдите ошибку в операторе:

    x - = 4;   /* вычесть из x число 4 */

Ответ: между `-' и `=' не должно быть пробела. Операция вида

    x @= expr;

означает

    x = x @ expr;

(где @ - одна из операций + - * / % ^ >>>> <<<< & |), причем x здесь вычисляется  единст-
венный раз (т.е. такая форма не только короче и понятнее, но и экономичнее).
     Однако имеется тонкое отличие a=a+n от a+=n; оно заключается в том, сколько  раз
вычисляется a. В случае a+=n единожды; в случае a=a+n два раза.

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

    #include 

    static int x = 0;
    int *iaddr(char *msg){
            printf("iaddr(%s) for x=%d evaluated\n", msg, x);
            return &x;
    }
    int main(){
            static int a[4];
            int *p, i;

            printf( "1: "); x = 0; (*iaddr("a"))++;
            printf( "2: "); x = 0; *iaddr("b") += 1;
            printf( "3: "); x = 0; *iaddr("c") = *iaddr("d") + 1;

            for(i=0, p = a; i < sizeof(a)/sizeof(*a); i++) a[i] = 0;
            *p++ += 1;
            for(i=0; i < sizeof(a)/sizeof(*a); i++)
                    printf("a[%d]=%d ", i, a[i]);
            printf("offset=%d\n", p - a);

            for(i=0, p = a; i < sizeof(a)/sizeof(*a); i++) a[i] = 0;
            *p++ = *p++ + 1;
            for(i=0; i < sizeof(a)/sizeof(*a); i++)
                    printf("a[%d]=%d ", i, a[i]);
            printf("offset=%d\n", p - a);

            return 0;
    }

Выдача:

    1: iaddr(a) for x=0 evaluated
    2: iaddr(b) for x=0 evaluated
    3: iaddr(d) for x=0 evaluated
    iaddr(c) for x=0 evaluated
    a[0]=1 a[1]=0 a[2]=0 a[3]=0 offset=1
    a[0]=1 a[1]=0 a[2]=0 a[3]=0 offset=2

Заметьте также, что

    a[i++] += z;

            это
    a[i] = a[i] + z; i++;

            а вовсе не
    a[i++] = a[i++] + z;

1.78.  Операция y = ++x; эквивалентна

    y = (x = x+1, x);

а операция y = x++; эквивалентна

    y = (tmp = x, x = x+1, tmp);
    или
    y = (x += 1) - 1;

где tmp - временная псевдопеременная того же типа,  что  и  x.  Операция  `,'  выдает

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

значение последнего выражения из перечисленных (подробнее см. ниже).
     Пусть x=1. Какие значения будут присвоены x и y после выполнения оператора

            y = ++x + ++x + ++x;

1.79.  Пусть i=4. Какие значения будут присвоены x и i после выполнения оператора

            x = --i + --i + --i;

1.80.  Пусть x=1. Какие значения будут присвоены x и y после выполнения оператора

            y = x++ + x++ + x++;

1.81.  Пусть i=4. Какие значения будут присвоены i и y после выполнения оператора

            y = i-- + i-- + i--;

1.82.  Корректны ли операторы

    char *p = "Jabberwocky"; char s[] = "0123456789?";
    int i = 0;

    s[i] = p[i++]; или *p   = *++p;
                   или s[i] = i++;
              или даже *p++ = f( *p );

Ответ: нет, стандарт не предусматривает, какая  из  частей  присваивания  вычисляется
первой: левая или правая.  Поэтому все может работать так, как мы и подразумевали, но
может и иначе!  Какое i используется в s[i]: 0 или уже 1 (++ уже сделан или нет),  то
есть

    int i = 0; s[i] = i++;     это
    s[0]  = 0;      или же     s[1] = 0;  ?

Какое p будет использовано в левой части *p: уже продвинутое или  старое?  Еще  более
эта идея драматизирована в

    s[i++] = p[i++];

Заметим еще, что в

    int i=0, j=0;
    s[i++] = p[j++];

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

    if( a[i++] < b[i] )...;

Порядок вычисления операндов не определен, поэтому неясно, что будет сделано  прежде:
взято  значение  b[i]  или  значение a[i++] (тогда будет взято b[i+1] ).  Надо писать
так, чтобы не полагаться на особенности вашего компилятора:

    if( a[i] < b[i+1] )...;  или  *p = *(p+1);
    i++;                          ++p;

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

     Твердо усвойте, что i++ и ++i не только выдают значения i и i+1  соответственно,
но  и  изменяют  значение  i.  Поэтому эти операторы НЕ НАДО использовать там, где по
смыслу требуется i+1, а не i=i+1.  Так для сравнения соседних элементов массива

       if( a[i] < a[i+1] ) ... ;  /* верно */
       if( a[i] < a[++i] ) ... ;  /* неверно */

1.83.  Порядок вычисления операндов в бинарных выражениях не  определен  (что  раньше
вычисляется - левый операнд или же правый ?).  Так пример

    int f(x,s) int x; char *s;
    {  printf( "%s:%d ", s, x ); return x; }

    main(){
       int x = 1;
       int y = f(x++, "f1") + f(x+=2, "f2");
       printf("%d\n", y);
    }

может печатать либо

            f1:1 f2:4 5
               либо
            f2:3 f1:3 6

в зависимости от особенностей поведения вашего компилятора (какая из двух f()  выпол-
нится первой: левая или правая?).  Еще пример:

            int y = 2;
            int x = ((y = 4) * y );
            printf( "%d\n", x );
Предыдущая страница Следующая страница
1 2 3 4 5  6 7 8 9 10 11 12 13 14 ... 87
Ваша оценка:
Комментарий:
  Подпись:
(Чтобы комментарии всегда подписывались Вашим именем, можете зарегистрироваться в Клубе читателей)
  Сайт:
 

Реклама