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 <stdio.h>

    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

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

    #include <stdio.h>

    /* Одно из двух */

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

            printf("%d\n", ch);

        return 0;

    }

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

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


            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 два раза.


    #include <stdio.h>

    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. Операция `,' выдает значение последнего выражения из перечисленных (подробнее см. ниже).

Пусть 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;

Твердо усвойте, что 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 );

Может быть напечатано либо 16, либо 8 в зависимости от поведения компилятора, т.е. данный оператор немобилен. Следует написать

            y = 4;  x = y * y;

1.84. Законен ли оператор


            f(x++, x++);   или  f(x, x++);

Ответ: Нет, порядок вычисления аргументов функций не определен. По той же причине мы не можем писать

            f( c = getchar(), c );

а должны писать

            c = getchar();  f(c, c);

(если мы именно это имели в виду). Вот еще пример:

            ...

            case '+':

                    push(pop()+pop()); break;

            case '-':

                    push(pop()-pop()); break;

            ...

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

            ...

            case '+':

                    push(pop()+pop()); break;

            case '-':

                    { int x = pop(); int y = pop();

                      push(y - x); break;

                    }

            ...

И еще пример:

      int x = 0;

      printf( "%d %d\n", x = 2, x );   /* 2 0 либо 2 2 */

Нельзя также

      struct pnt{ int x; int y; }arr[20]; int i=0;

      ...

      scanf( "%d%d", & arr[i].x, & arr[i++].y );

поскольку i++ может сделаться раньше, чем чтение в x. Еще пример:

            main(){

               int i = 3;

               printf( "%d %d %d\n", i += 7, i++, i++ );

            }

который показывает, что на IBM PC * - и PDP-11 ** = аргументы функций вычисляются справа налево (пример печатает 12 4 3). Впрочем, другие компиляторы могут вычислять их слева направо (как и подсказывает нам здравый смысл).

1.85.

Программа печатает либо x=1 либо x=0 в зависимости от КОМПИЛЯТОРА - вычисляется ли раньше правая или левая часть оператора вычитания:


    #include <stdio.h>

    void main(){

            int c = 1;

            int x = c - c++;

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

            exit(0);

    }

Что вы имели в виду ?

    left = c; right = c++; x = left - right;

или

    right = c++; left = c; x = left - right;

А если компилятор еще и распараллелит вычисление left и right - то одна программа в разные моменты времени сможет давать разные результаты.

Вот еще достойная задачка:


    x = c-- - --c;  /* c-----c */

* IBM ("Ай-би-эм") - International Buisiness Machines Corporation. Персональные компьютеры IBM PC построены на базе микропроцессоров фирмы Intel.

** PDP-11 - (Programmed Data Processor) - компьютер фирмы DEC (Digital Equipment Corporation), у нас известный как СМ-1420. Эта же фирма выпускает машину VAX.

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

Используются технологии uCoz