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.
© Copyright А. Богатырев, 1992-95
Си в UNIX
Назад | Содержание | Вперед