h = fabs(b - a) / n2; h2 = h * 2.0;

/* Вычисляем первое приближение */
/* Сумма по нечетным точкам: */
for( Sodd = 0.0, x = a+h, i = 0;
i < n;
i++, x += h2 )
Sodd += (*f)(x);

/* Сумма по четным точкам: */
for( Seven = 0.0, x = a+h2, i = 0;
i < n-1;
i++, x += h2 )
Seven += f(x);

/* Предварительное значение интеграла: */
S = h / 3.0 * (fab + 4.0 * Sodd + 2.0 * Seven );
do{
niter++;
Sprev = S;

/* Вычисляем интеграл с половинным шагом */
h2 = h; h /= 2.0;
if( h == 0.0 ) break; /* потеря значимости */
n = n2; n2 *= 2;

Seven = Seven + Sodd;
/* Вычисляем сумму по новым точкам: */
for( Sodd = 0.0, x = a+h, i = 0;
i < n;
i++, x += h2 )
Sodd += (*f)(x);

/* Значение интеграла */
S = h / 3.0 * (fab + 4.0 * Sodd + 2.0 * Seven );

} while( niter < 31 && fabs(S - Sprev) / 15.0 >= eps );
/* Используем условие Рунге для окончания итераций */

return ( 16.0 * S - Sprev ) / 15.0 ;
/* Возвращаем уточненное по Ричардсону значение */
}



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

1.40. Где ошибка?

struct time_now{
int hour, min, sec;
} X = { 13, 08, 00 }; /* 13 часов 08 минут 00 сек.*/

Ответ: 08 - восьмеричное число (так как начинается с нуля)! А в восьмеричных числах
цифры 8 и 9 не бывают.

1.41. Дан текст:

int i = -2;
i <<= 2;
printf("%d\n", i); /* печать сдвинутого i : -8 */
i >>= 2;
printf("%d\n", i); /* печатается -2 */

Закомментируем две строки (исключая их из программы):

int i = -2;
i <<= 2;
/*
printf("%d\n", i); /* печать сдвинутого i : -8 */
i >>= 2;
*/
printf("%d\n", i); /* печатается -2 */

Почему теперь возникает ошибка? Указание: где кончается комментарий?
Ответ: Си не допускает вложенных комментариев. Вместо этого часто используются
конструкции вроде:

#ifdef COMMENT
... закомментированный текст ...
#endif /*COMMENT*/

и вроде

/**/ printf("here");/* отладочная выдача включена */
/* printf("here");/* отладочная выдача выключена */

или

/* выключено(); /**/
включено(); /**/

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

/*printf("here");
*/


1.42. Почему программа печатает неверное значение для i2 ?










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

int main(int argc, char *argv[]){
int i1, i2;

i1 = 1; /* Инициализируем i1 /
i2 = 2; /* Инициализируем i2 */
printf("Numbers %d %d\n", i1, i2);
return(0);
}

Ответ: в первом операторе присваивания не закрыт комментарий - весь второй оператор
присваивания полностью проигнорировался! Правильный вариант:

int main(int argc, char *argv[]){
int i1, i2;

i1 = 1; /* Инициализируем i1 */
i2 = 2; /* Инициализируем i2 */
printf("Numbers %d %d\n", i1, i2);
return(0);
}


1.43. А вот "шальной" комментарий.

void main(){
int n = 10;
int *ptr = &n;
int x, y = 40;

x = y/*ptr /* должно быть 4 */ + 1;
printf( "%d\n", x ); /* пять */
exit(0);
}


/* или такой пример из жизни - взят из переписки в Relcom */
...
cost = nRecords/*pFactor /* divided by Factor, and */
+ fixMargin; /* plus the precalculated */
...

Результат непредсказуем. Дело в том, что y/*ptr превратилось в начало комментария!
Поэтому бинарные операции принято окружать пробелами.

x = y / *ptr /* должно быть 4 */ + 1;


1.44. Найдите ошибки в директивах препроцессора Си |- (вертикальная черта обозначает
левый край файла).








____________________
|- Препроцессор Си - это программа /lib/cpp





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

|
| #include <stdio.h>
|#include < sys/types.h >
|# define inc (x) ((x) + 1)
|#define N 12;
|#define X -2
|
|... printf( "n=%d\n", N );
|... p = 4-X;

Ответ: в первой директиве стоит пробел перед #. Диез должен находиться в первой
позиции строки. Во второй директиве в <> находятся лишние пробелы, не относящиеся к
имени файла - препроцессор не найдет такого файла! В данном случае "красота" пошла
во вред делу. В третьей - между именем макро inc и его аргументом в круглых скобках
(x) стоит пробел, который изменяет весь смысл макроопределения: вместо макроса с
параметром inc(x) мы получаем, что слово inc будет заменяться на (x)((x)+1). Заметим
однако, что пробелы после # перед именем директивы вполне допустимы. В четвертом
случае показана характерная опечатка - символ ; после определения. В результате напи-
санный printf() заменится на

printf( "n=%d\n", 12; );

где лишняя ; даст синтаксическую ошибку.
В пятом случае ошибки нет, но нас ожидает неприятность в строке p=4-X; которая
расширится в строку p=4--2; являющуюся синтаксически неверной. Чтобы избежать подоб-
ной ситуации, следовало бы написать

p = 4 - X; /* через пробелы */

но еще проще (и лучше) взять макроопределение в скобки:

#define X (-2)


1.45. Напишите функцию max(x, y), возвращающую большее из двух значений. Напишите
аналогичное макроопределение. Напишите макроопределения min(x, y) и abs(x) (abs -
модуль числа). Ответ:

#define abs(x) ((x) < 0 ? -(x) : (x))
#define min(x,y) (((x) < (y)) ? (x) : (y))

Зачем x взят в круглые скобки (x)? Предположим, что мы написали

#define abs(x) (x < 0 ? -x : x )
вызываем
abs(-z) abs(a|b)
получаем
(-z < 0 ? --z : -z ) (a|b < 0 ? -a|b : a|b )

У нас появилась "дикая" операция --z; а выражение a|b<0 соответствует a|(b<0), с сов-
сем другим порядком операций! Поэтому заключение всех аргументов макроса в его теле
в круглые скобки позволяет избежать многих неожиданных проблем. Придерживайтесь этого
правила!
Вот пример, показывающий зачем полезно брать в скобки все определение:

#define div(x, y) (x)/(y)

При вызове






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

z = sizeof div(1, 2);
превратится в
z = sizeof(1) / (2);

что равно sizeof(int)/2, а не sizeof(int). Вариант

#define div(x, y) ((x) / (y))

будет работать правильно.

1.46. Макросы, в отличие от функций, могут порождать непредвиденные побочные
эффекты:

int sqr(int x){ return x * x; }
#define SQR(x) ((x) * (x))
main(){ int y=2, z;
z = sqr(y++); printf("y=%d z=%d\n", y, z);
y = 2;
z = SQR(y++); printf("y=%d z=%d\n", y, z);
}

Вызов функции sqr печатает "y=3 z=4", как мы и ожидали. Макрос же SQR расширяется в

z = ((y++) * (y++));

и результатом будет "y=4 z=6", где z совсем не похоже на квадрат числа 2.

1.47. ANSI препроцессор|- языка Си имеет оператор ## - "склейка лексем":

#define VAR(a, b) a ## b
#define CV(x) command_ ## x
main(){
int VAR(x, 31) = 1;
/* превратится в int x31 = 1; */
int CV(a) = 2; /* даст int command_a = 2; */
...
}

Старые версии препроцессора не обрабатывают такой оператор, поэтому раньше использо-
вался такой трюк:

#define VAR(a, b) a/**/b

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

1.48. Напишите программу, распечатывающую максимальное и минимальное из ряда чисел,
вводимых с клавиатуры. Не храните вводимые числа в массиве, вычисляйте max и min
сразу при вводе очередного числа!






____________________
|- ANSI - American National Standards Institute, разработавший стандарт на язык Си
и его окружение.





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

#include <stdio.h>
main(){
int max, min, x, n;
for( n=0; scanf("%d", &x) != EOF; n++)
if( n == 0 ) min = max = x;
else{
if( x > max ) max = x;
if( x < min ) min = x;
}
printf( "Ввели %d чисел: min=%d max=%d\n",
n, min, max);
}

Напишите аналогичную программу для поиска максимума и минимума среди элементов мас-
сива, изначально min=max=array[0];

1.49. Напишите программу, которая сортирует массив заданных чисел по возрастанию
(убыванию) методом пузырьковой сортировки. Когда вы станете более опытны в Си, напи-
шите сортировку методом Шелла.

/*
* Сортировка по методу Шелла.
* Сортировке подвергается массив указателей на данные типа obj.
* v------.-------.------.-------.------0
* ! ! ! !
* * * * *
* элементы типа obj
* Программа взята из книги Кернигана и Ритчи.
*/

#include <stdio.h>
#include <string.h>
#include <locale.h>
#define obj char

static shsort (v,n,compare)
int n; /* длина массива */
obj *v[]; /* массив указателей */
int (*compare)(); /* функция сравнения соседних элементов */
{
int g, /* расстояние, на котором происходит сравнение */
i,j; /* индексы сравниваемых элементов */
obj *temp;

for( g = n/2 ; g > 0 ; g /= 2 )
for( i = g ; i < n ; i++ )
for( j = i-g ; j >= 0 ; j -= g )
{
if((*compare)(v[j],v[j+g]) <= 0)
break; /* уже в правильном порядке */

/* обменять указатели */
temp = v[j]; v[j] = v[j+g]; v[j+g] = temp;
/* В качестве упражнения можете написать
* при помощи curses-а программу,
* визуализирующую процесс сортировки:
* например, изображающую эту перестановку
* элементов массива */
}
}




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

/* сортировка строк */
ssort(v) obj **v;
{
extern less(); /* функция сравнения строк */
int len;

/* подсчет числа строк */
len=0;
while(v[len]) len++;
shsort(v,len,less);
}

/* Функция сравнения строк.
* Вернуть целое меньше нуля, если a < b
* ноль, если a == b
* больше нуля, если a > b
*/
less(a,b) obj *a,*b;
{
return strcoll(a,b);
/* strcoll - аналог strcmp,
* но с учетом алфавитного порядка букв.
*/
}

char *strings[] = {
"Яша", "Федя", "Коля",
"Гриша", "Сережа", "Миша",
"Андрей Иванович", "Васька",
NULL
};
int main(){
char **next;

setlocale(LC_ALL, "");

ssort( strings );
/* распечатка */
for( next = strings ; *next ; next++ )
printf( "%s\n", *next );
return 0;
}


1.50. Реализуйте алгоритм быстрой сортировки.



















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

/* Алгоритм быстрой сортировки. Работа алгоритма "анимируется"
* (animate-оживлять) при помощи библиотеки curses.
* cc -o qsort qsort.c -lcurses -ltermcap
*/
#include "curses.h"

#define N 10 /* длина массива */

/* массив, подлежащий сортировке */
int target [N] = {
7, 6, 10, 4, 2,
9, 3, 8, 5, 1
};

int maxim; /* максимальный элемент массива */

/* quick sort */
qsort (a, from, to)
int a[]; /* сортируемый массив */
int from; /* левый начальный индекс */
int to; /* правый конечный индекс */
{
register i, j, x, tmp;

if( from >= to ) return;
/* число элементов <= 1 */

i = from; j = to;
x = a[ (i+j) / 2 ]; /* значение из середины */

do{
/* сужение вправо */
while( a[i] < x ) i++ ;

/* сужение влево */
while( x < a[j] ) j--;

if( i <= j ){ /* обменять */
tmp = a[i]; a[i] = a[j] ; a[j] = tmp;
i++; j--;

demochanges(); /* визуализация */
}
} while( i <= j );

/* Теперь обе части сошлись в одной точке.
* Длина левой части = j - from + 1
* правой = to - i + 1
* Все числа в левой части меньше всех чисел в правой.
* Теперь надо просто отсортировать каждую часть в отдельности.
* Сначала сортируем более короткую (для экономии памяти
* в стеке ). Рекурсия:
*/
if( (j - from) < (to - i) ){
qsort( a, from, j );
qsort( a, i, to );
} else {
qsort( a, i, to );
qsort( a, from, j );
}
}



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

int main (){
register i;

initscr(); /* запуск curses-а */

/* поиск максимального числа в массиве */
for( maxim = target[0], i = 1 ; i < N ; i++ )
if( target[i] > maxim )
maxim = target[i];

demochanges();
qsort( target, 0, N-1 );
demochanges();

mvcur( -1, -1, LINES-1, 0);
/* курсор в левый нижний угол */

endwin(); /* завершить работу с curses-ом */
return 0;
}

#define GAPY 2
#define GAPX 20

/* нарисовать картинку */
demochanges(){
register i, j;
int h = LINES - 3 * GAPY - N;
int height;

erase(); /* зачистить окно */
attron( A_REVERSE );

/* рисуем матрицу упорядоченности */
for( i=0 ; i < N ; i++ )
for( j = 0; j < N ; j++ ){
move( GAPY + i , GAPX + j * 2 );
addch( target[i] >= target[j] ? '*' : '.' );
addch( ' ' );
/* Рисовать '*' если элементы
* идут в неправильном порядке.
* Возможен вариант проверки target[i] > target[j]
*/
}
attroff( A_REVERSE );

/* массив */
for( i = 0 ; i < N ; i++ ){
move( GAPY + i , 5 );
printw( "%4d", target[i] );

height = (long) h * target[i] / maxim ;
for( j = 2 * GAPY + N + (h - height) ;
j < LINES - GAPY; j++ ){
move( j, GAPX + i * 2 );
addch( '|' );
}
}
refresh(); /* проявить картинку */
sleep(1);
}



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

1.51. Реализуйте приведенный фрагмент программы без использования оператора goto и
без меток.

if ( i > 10 ) goto M1;
goto M2;
M1: j = j + i; flag = 2; goto M3;
M2: j = j - i; flag = 1;
M3: ;

Заметьте, что помечать можно только оператор (может быть пустой); поэтому не может
встретиться фрагмент

{ ..... Label: } а только { ..... Label: ; }


1.52. В каком случае оправдано использование оператора goto?
Ответ: при выходе из вложенных циклов, т.к. оператор break позволяет выйти
только из самого внутреннего цикла (на один уровень).

1.53. К какому if-у относится else?

if(...) ... if(...) ... else ...

Ответ: ко второму (к ближайшему предшествующему, для которого нет другого else).
Вообще же лучше явно расставлять скобки (для ясности):

if(...){ ... if(...) ... else ... }
if(...){ ... if(...) ... } else ...


1.54. Макроопределение, чье тело представляет собой последовательность операторов в
{...} скобках (блок), может вызвать проблемы при использовании его в условном опера-
торе if с else-частью:

#define MACRO { x=1; y=2; }

if(z) MACRO;
else .......;

Мы получим после макрорасширения

if(z) { x=1; y=2; } /* конец if-а */ ;
else .......; /* else ни к чему не относится */

то есть синтаксически ошибочный фрагмент, так как должно быть либо

if(...) один_оператор;
else .....
либо
if(...){ последовательность; ...; операторов; }
else .....

где точка-с-запятой после } не нужна. С этим явлением борются, оформляя блок {...} в
виде do{...}while(0)

#define MACRO do{ x=1; y=2; }while(0)

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





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

if(z) do{ x=1; y=2; }while(0);
else .......;


1.55. В чем ошибка (для знающих язык "Паскаль")?

int x = 12;
if( x < 20 and x > 10 ) printf( "O'K\n");
else if( x > 100 or x < 0 ) printf( "Bad x\n");
else printf( "x=%d\n", x);

Напишите

#define and &&
#define or ||


1.56. Почему программа зацикливается? Мы хотим подсчитать число пробелов и табуля-
ций в начале строки:

int i = 0;
char *s = " 3 spaces";
while(*s == ' ' || *s++ == '\t')
printf( "Пробел %d\n", ++i);

Ответ: логические операции || и && выполняются слева направо; как только какое-то
условие в || оказывается истинным (а в && ложным) - дальнейшие условия просто не
вычисляются. В нашем случае условие *s==' ' сразу же верно, и операция s++ из второго
условия не выполняется! Мы должны были написать хотя бы так:

while(*s == ' ' || *s == '\t'){
printf( "Пробел %d\n", ++i); s++;
}

С другой стороны, это свойство || и && черезвычайно полезно, например:

if( x != 0.0 && y/x < 1.0 ) ... ;

Если бы мы не вставили проверку на 0, мы могли бы получить деление на 0. В данном же
случае при x==0 деление просто не будет вычисляться. Вот еще пример:

int a[5], i;
for(i=0; i < 5 && a[i] != 0; ++i) ...;

Если i выйдет за границу массива, то сравнение a[i] с нулем уже не будет вычисляться,
т.е. попытки прочесть элемент не входящий в массив не произойдет.
Это свойство && позволяет писать довольно неочевидные конструкции, вроде

if((cond) && f());
что оказывается эквивалентным
if( cond ) f();

Вообще же

if(C1 && C2 && C3) DO;
эквивалентно
if(C1) if(C2) if(C3) DO;

и для "или"





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

if(C1 || C2 || C3) DO;
эквивалентно
if(C1) goto ok;
else if(C2) goto ok;
else if(C3){ ok: DO; }

Вот еще пример, пользующийся этим свойством ||

#include <stdio.h>
main(argc, argv) int argc; char *argv[];
{ FILE *fp;
if(argc < 2 || (fp=fopen(argv[1], "r")) == NULL){
fprintf(stderr, "Плохое имя файла\n");
exit(1); /* завершить программу */
}
...
}

Если argc==1, то argv[1] не определено, однако в этом случае попытки открыть файл с
именем argv[1] просто не будет предпринято!
Ниже приведен еще один содержательный пример, представляющий собой одну из воз-
можных схем написания "двуязычных" программ, т.е. выдающих сообщения на одном из двух
языков по вашему желанию. Проверяется переменная окружения MSG (или LANG):

ЯЗЫК:
1) "MSG=engl" английский
2) MSG нет в окружении английский
3) "MSG=rus" русский

Про окружение и функцию getenv() смотри в главе "Взаимодействие с UNIX", про strchr()
- в главе "Массивы и строки".

#include <stdio.h>
int _ediag = 0; /* язык диагностик: 1-русский */
extern char *getenv(), *strchr();
#define ediag(e,r) (_ediag?(r):(e))
main(){ char *s;
_ediag = ((s=getenv("MSG")) != NULL &&
strchr("rRрР", *s) != NULL);
printf(ediag("%d:english\n", "%d:русский\n"), _ediag);
}

Если переменная MSG не определена, то s==NULL и функция strchr(s,...) не вызывается
(ее первый фргумент не должен быть NULL-ом). Здесь ее можно было бы упрощенно заме-
нить на *s=='r'; тогда если s равно NULL, то обращение *s было бы незаконно (обраще-
ние по указателю NULL дает непредсказуемые результаты и, скорее всего, вызовет крах
программы).

1.57. Иногда логическое условие можно сделать более понятным, используя правила де-
Моргана:

a && b = ! ( !a || !b )
a || b = ! ( !a && !b )

а также учитывая, что

! !a = a
! (a == b) = (a != b)

Например:




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

if( c != 'a' && c != 'b' && c != 'c' )...;
превращается в
if( !(c == 'a' || c == 'b' || c == 'c')) ...;


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

#include <stdio.h>
extern int errno; /* код системной ошибки */
FILE *fp;

int openFile(){
errno = 0;
fp = fopen("/etc/inittab", "r");
printf("fp=%x\n", fp);
return(fp == NULL ? 0 : 1);
}
int closeFile(){
printf("closeFile\n");
if(fp) fclose(fp);
return 0;
}

int die(int code){
printf("exit(%d)\n", code);
exit(code);
return 0;
}


void main(){
char buf[2048];

if( !openFile()) die(errno); closeFile();
openFile() || die(errno); closeFile();
/* если файл открылся, то die() не вычисляется */
openFile() ? 0 : die(errno); closeFile();

if(openFile()) closeFile();
openFile() && closeFile();
/* вычислить closeFile() только если openFile() удалось */
openFile() && (printf("%s", fgets(buf, sizeof buf, fp)), closeFile());
}

В последней строке использован оператор "запятая": (a,b,c) возвращает значение выра-
жения c.

1.59. Напишите функцию, вычисляющую сумму массива заданных чисел.

1.60. Напишите функцию, вычисляющую среднее значение массива заданных чисел.

1.61. Что будет напечатано в результате работы следующего цикла?

for ( i = 36; i > 0; i /= 2 )
printf ( "%d%s", i,
i==1 ? ".\n":", ");




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

Ответ: 36, 18, 9, 4, 2, 1.

1.62. Найдите ошибки в следующей программе:

main {
int i, j, k(10);

for ( i = 0, i <= 10, i++ ){
k[i] = 2 * i + 3;
for ( j = 0, j <= i, j++ )
printf ("%i\n", k[j]);
}
}

Обратите внимание на формат %i, существует ли такой формат? Есть ли это тот формат,
по которому следует печатать значения типа int?

1.63. Напишите программу, которая распечатывает элементы массива. Напишите прог-
рамму, которая распечатывает элементы массива по 5 чисел в строке.

1.64. Составьте программу считывания строк символов из стандартного ввода и печати
номера введенной строки, адреса строки в памяти ЭВМ, значения строки, длины строки.

1.65. Стилистическое замечание: в операторе return возвращаемое выражение не обяза-
тельно должно быть в ()-скобках. Дело в том, что return - не функция, а оператор.

return выражение;
return (выражение);

Однако если вы вызываете функцию (например, exit) - то аргументы должны быть в круг-
лых скобках: exit(1); но не exit 1;

1.66. Избегайте ситуации, когда функция в разных ветвях вычисления то возвращает
некоторое значение, то не возвращает ничего:

int func (int x) {
if( x > 10 ) return x*2;
if( x == 10 ) return (10);
/* а здесь - неявный return; без значения */
}

при x < 10 функция вернет непредсказуемое значение! Многие компиляторы распознают
такие ситуации и выдают предупреждение.

1.67. Напишите программу, запрашивающую ваше имя и "приветствующую" вас. Напишите
функцию чтения строки. Используйте getchar() и printf().
Ответ:

#include <stdio.h> /* standard input/output */
main(){
char buffer[81]; int i;
printf( "Введите ваше имя:" );
while((i = getstr( buffer, sizeof buffer )) != EOF){
printf( "Здравствуй, %s\n", buffer );
printf( "Введите ваше имя:" );
}
}

getstr( s, maxlen )
char *s; /* куда поместить строку */
int maxlen; /* длина буфера:



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

макс. длина строки = maxlen-1 */
{ int c; /* не char! (почему ?) */
register int i = 0;

maxlen--; /* резервируем байт под конечный '\0' */
while(i < maxlen && (c = getchar()) != '\n'
&& c != EOF )
s[i++] = c;
/* обратите внимание, что сам символ '\n'
* в строку не попадет */
s[i] = '\0'; /* признак конца строки */
return (i == 0 && c == EOF) ? EOF : i;
/* вернем длину строки */
}

Вот еще один вариант функции чтения строки: в нашем примере ее следует вызывать как
fgetstr(buffer,sizeof(buffer),stdin);
Это подправленный вариант стандартной функции fgets (в ней строки @1 и @2 обменяны
местами).

char *fgetstr(char *s, int maxlen, register FILE *fp){
register c; register char *cs = s;

while(--maxlen > 0 && (c = getc(fp)) != EOF){
if(c == '\n') break; /* @1 */
*cs++ = c; /* @2 */
}
if(c == EOF && cs == s) return NULL;
/* Заметьте, что при EOF строка s не меняется! */
*cs = '\0'; return s;
}

Исследуйте поведение этих функций, когда входная строка слишком длинная (длиннее max-
len). Замечание: вместо нашей "рукописной" функции getstr() мы могли бы использовать
стандартную библиотечную функцию gets(buffer).

1.68. Объясните, почему d стало отрицательным и почему %X печатает больше F, чем в
исходном числе? Пример выполнялся на 32-х битной машине.

main(){
unsigned short u = 65535; /* 16 бит: 0xFFFF */
short d = u; /* 15 бит + знаковый бит */
printf( "%X %d\n", d, d); /* FFFFFFFF -1 */
}

Указание: рассмотрите двоичное представление чисел (смотри приложение). Какие приве-
дения типов здесь происходят?

1.69. Почему 128 превратилось в отрицательное число?

main()
{
/*signed*/ char c = 128; /* биты: 10000000 */
unsigned char uc = 128;
int d = c; /* используется 32-х битный int */
printf( "%d %d %x\n", c, d, d );
/* -128 -128 ffffff80 */
d = uc;
printf( "%d %d %x\n", uc, d, d );
/* 128 128 80 */
}



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



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

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

#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, это значение