3.10.

В современных UNIX-ах с поддержкой различных языков таблица ctype загружается из некоторых системных файлов - для каждого языка своя. Для какого языка - выбирается по содержимому переменной окружения LANG. Если переменная не задана - используется значение "C", английский язык. Загрузка таблиц должна происходить явно, вызовом


            ...

            #include <locale.h>

            ...

            main(){

                    setlocale(LC_ALL, "");

                    ...

                    все остальное

                    ...

            }

3.11.

Вернемся к нашей любимой проблеме со знаковым битом у типа char.

    #include <stdio.h>

    #include <locale.h>

    #include <ctype.h>

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

            char c;

            char *string = "абвгдежзиклмноп";

            setlocale(LC_ALL, "");

            for(;c = *string;string++){

    #ifdef DEBUG

                    printf("%c %d %d\n", *string, *string, c);

    #endif

                    if(isprint(c)) printf("%c - печатный символ\n", c);

            }

            return 0;

    }

Эта программа неожиданно печатает

    % a.out

    в - печатный символ

    з - печатный символ

И все. В чем дело???

Рассмотрим к примеру символ 'г'. Его код '\307'. В операторе


    c = *string;

Символ c получает значение -57 (десятичное), которое ОТРИЦАТЕЛЬНО. В системном файле /usr/include/ctype.h макрос isprint определен так:


    #define isprint(c)      ((_ctype + 1)[c] & (_P|_U|_L|_N|_B))

И значение c используется в нашем случае как отрицательный индекс в массиве, ибо индекс приводится к типу int (signed). Откуда теперь извлекается значение флагов нам неизвестно; можно только с уверенностью сказать, что НЕ из массива _ctype.

Проблему решает либо использование


    isprint(c & 0xFF)

либо

    isprint((unsigned char) c)

либо объявление в нашем примере

    unsigned char c;

В первом случае мы явно приводим signed к unsigned битовой операцией, обнуляя лишние биты. Во втором и третьем - unsigned char расширяется в unsigned int, который останется положительным. Вероятно, второй путь предпочтительнее.

3.12.

Итак, снова напомним, что русские буквы char, а не unsigned char дают отрицательные индексы в массиве.

    char c = 'г';

    int x[256];

            ...x[c]...            /* индекс < 0 */

            ...x['г']...

Поэтому байтовые индексы должны быть либо unsigned char, либо & 0xFF. Как в следующем примере:

    /* Программа преобразования символов в файле: транслитерация

                      tr abcd prst  заменяет строки

                      xxxxdbcaxxxx -> xxxxtrspxxxx

       По мотивам книги М.Дансмура и Г.Дейвиса.

    */

    #include <stdio.h>

    #define ASCII 256 /* число букв в алфавите ASCII */

    /* BUFSIZ определено в stdio.h */

    char mt[ ASCII ];       /* таблица перекодировки */

    /* начальная разметка таблицы */

    void mtinit(){

            register int i;

            for( i=0; i < ASCII; i++ )

                    mt[i] = (char) i;

    }

    int main(int argc, char *argv[])

    {

            register char *tin, *tout; /* unsigned char */

            char buffer[ BUFSIZ ];

            if( argc != 3 ){

                    fprintf( stderr, "Вызов: %s что наЧто\n", argv[0] );

                    return(1);

            }

            tin  = argv[1]; tout = argv[2];

            if( strlen(tin) != strlen(tout)){

                    fprintf( stderr, "строки разной длины\n" );

                    return(2);

            }

            mtinit();

            do{

                    mt[ (*tin++) & 0xFF ]  = *tout++;

                    /*   *tin - имеет тип char.

                     *   & 0xFF подавляет расширение знака

                     */

            } while( *tin );

            tout = mt;

            while( fgets( buffer, BUFSIZ, stdin ) != NULL ){

                    for( tin = buffer; *tin; tin++ )

                            *tin = tout[ *tin & 0xFF ];

                    fputs( buffer, stdout );

            }

            return(0);

    }

3.13.


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

            char c = 'г';

            if('a' <= c && c < 256)

                    printf("Это одна буква.\n");

            return 0;

    }

Увы, эта программа не печатает НИЧЕГО. Просто потому, что signed char в сравнении (в операторе if) приводится к типу int. А как целое число - русская буква отрицательна.

Снова решением является либо использование везде (c & 0xFF), либо объявление unsigned char c. В частности, этот пример показывает, что НЕЛЬЗЯ просто так сравнивать две переменные типа char. Нужно принимать предохранительные меры по подавлению расширения знака:


    if((ch1 & 0xFF) < (ch2 & 0xFF))...;

Для unsigned char такой проблемы не будет.

3.14.

Почему неверно:

    #include <stdio.h>

    main(){

            char c;

            while((c = getchar()) != EOF)

                    putchar(c);

    }

Потому что c описано как char, в то время как EOF - значение типа int равное (-1).

Русская буква "Большой твердый знак" в кодировке КОИ-8 имеет код '\377' (0xFF). Если мы подадим на вход этой программе эту букву, то в сравнении signed char со значением знакового целого EOF, c будет приведено тоже к знаковому целому - расширением знака. 0xFF превратится в (-1), что означает, что поступил символ EOF. Сюрприз!!!

Посему данная программа будет делать вид, что в любом файле с большим русским твердым знаком после этого знака (и включая его) дальше ничего нет. Что есть досадное заблуждение.

Решением служит ПРАВИЛЬНОЕ объявление int c.

3.15.

Изучите поведение программы

    #define TYPE char

    void f(TYPE c){

            if(c == 'й') printf("Это буква й\n");

            printf("c=%c c=\\%03o c=%03d c=0x%0X\n", c, c, c, c);

    }

    int main(){

            f('г'); f('й');

            f('z'); f('Z');

            return 0;

    }

когда TYPE определено как char, unsigned char, int.

Объясните поведение. Выдачи в этих трех случаях таковы (int == 32 бита):


    c=г c=\37777777707 c=-57 c=0xFFFFFFC7

    Это буква й

    c=й c=\37777777712 c=-54 c=0xFFFFFFCA

    c=z c=\172 c=122 c=0x7A

    c=Z c=\132 c=090 c=0x5A

    c=г c=\307 c=199 c=0xC7

    c=й c=\312 c=202 c=0xCA

    c=z c=\172 c=122 c=0x7A

    c=Z c=\132 c=090 c=0x5A

и снова как 1 случай.

Рассмотрите альтернативу


            if(c == (unsigned char) 'й') printf("Это буква й\n");

где предполагается, что знак у русских букв и у c НЕ расширяется. В данном случае фраза 'Это буква й' не печатается ни с типом char, ни с типом int, поскольку в сравнении c приводится к типу signed int расширением знакового бита (который равен 1).

Слева получается отрицательное число!

В таких случаях вновь следует писать


            if((unsigned char)c == (unsigned char)'й') printf("Это буква й\n");

3.16.

Обычно возникают проблемы при написании функций с переменным числом аргументов. В языке Си эта проблема решается использованием макросов va_args, не зависящих от соглашений о вызовах функций на данной машине, и использующих эти макросы специальных функций. Есть два стиля оформления таких программ: с использованием <varargs.h> и <stdarg.h>. Первый был продемонстрирован в первой главе на примере функции poly(). Для иллюстрации второго приведем пример функции трассировки, записывающей собщение в файл:


    #include <stdio.h>

    #include <stdarg.h>

    void trace(char *fmt, ...) {

        va_list args;

        static FILE *fp = NULL;

        if(fp == NULL){

           if((fp = fopen("TRACE", "w")) == NULL) return;

        }

        va_start(args, fmt);

        /* второй аргумент: арг-т после которого

         * в заголовке функции идет ... */

        vfprintf(fp, fmt, args); /* библиотечная ф-ция */

        fflush(fp);     /* вытолкнуть сообщение в файл */

        va_end(args);

    }

    main(){ trace( "%s\n", "Go home.");

            trace( "%d %d\n", 12, 34);

    }

Символ `...' (троеточие) в заголовке функции обозначает переменный (возможно пустой) список аргументов. Он должен быть самым последним, следуя за всеми обязательными аргументами функции.

Макрос va_arg(args,type), извлекающий из переменного списка аргументов `...' очередное значение типа type, одинаков в обоех моделях. Функция vfprintf может быть написана через функцию vsprintf (в действительности обе функции - стандартные):


    int vfprintf(FILE *fp, const char *fmt, va_list args){

        /*static*/ char buffer[1024]; int res;

        res = vsprintf(buffer, fmt, args);

        fputs(buffer, fp); return res;

    }

Функция vsprintf(str,fmt,args); аналогична функции sprintf(str,fmt,...) - записывает преобразованную по формату строку в байтовый массив str, но используется в контексте, подобном приведенному. В конец сформированной строки sprintf записывает '\0'.

3.17.

Напишите функцию printf, понимающую форматы %c (буква), %d (целое), %o (восьмеричное), %x (шестнадцатеричное), %b (двоичное), %r (римское), %s (строка), %ld (длинное целое). Ответ смотри в приложении.

3.18.

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


    #ifdef XX

            ... вариант1

    #else

            ... вариант2

    #endif

Эта директива препроцессора ведет себя следующим образом: если макрос с именем XX был определен


    #define XX

то в программу подставляется вариант1, если же нет - вариант2. Оператор #else не обязателен - при его отсутствии вариант2 пуст. Существует также оператор #ifndef, который подставляет вариант1 если макрос XX не определен. Есть еще и оператор #elif else if:


    #ifdef макро1

      ...

    #elif  макро2

      ...

    #else

      ...

    #endif

Определить макрос можно не только при помощи #define, но и при помощи ключа компилятора, так


    cc -DXX file.c ...

соответствует включению в начало файла file.c директивы

    #define XX

А для программы

    main(){

    #ifdef XX

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

    #else

            printf( "XX undefined\n");

    #endif

    }

ключ

    cc -D"XX=2" file.c ...

эквивалентен заданию директивы

    #define XX 2

Что будет, если совсем не задать ключ -D в данном примере?

Этот прием используется в частности в тех случаях, когда какие-то стандартные типы или функции в данной системе носят другие названия:


    cc -Dvoid=int ...

    cc -Dstrchr=index ...

В некоторых системах компилятор автоматически определяет специальные макросы: так компиляторы в UNIX неявно подставляют один из ключей (или несколько сразу):


            -DM_UNIX

            -DM_XENIX

            -Dunix

            -DM_SYSV

            -D__SVR4

            -DUSG

            ... бывают и другие

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

3.19.

Оператор #ifdef применяется в include-файлах, чтобы исключить повторное включение одного и того же файла. Пусть файлы aa.h и bb.h содержат

           aa.h                        bb.h

    #include "cc.h"                 #include "cc.h"

    typedef unsigned long ulong;    typedef int cnt_t;

А файлы cc.h и 00.c содержат

           cc.h                        00.c

           ...                      #include "aa.h"

    struct II { int x, y; };        #include "bb.h"

           ...                      main(){ ... }

В этом случае текст файла cc.h будет вставлен в 00.c дважды: из aa.h и из bb.h. При компиляции 00.c компилятор сообщит "Переопределение структуры II". Чтобы includeфайл не подставлялся еще раз, если он уже однажды был включен, придуман следующий прием - следует оформлять файлы включений так:


    /* файл   cc.h */

    #ifndef  _CC_H

    # define _CC_H  /* определяется при первом включении */

            ...

            struct II { int x, y; };

            ...

    #endif /* _CC_H */

Второе и последующие включения такого файла будут подставлять пустое место, что и требуется. Для файла <sys/types.h> было бы использовано макроопределение _SYS_TYPES_H.

3.20.

Любой макрос можно отменить, написав директиву

        #undef имяМакро

Пример:

    #include <stdio.h>

    #undef M_UNIX

    #undef M_SYSV

    main() {

            putchar('!');

    #undef  putchar

    #define putchar(c) printf( "Буква '%c'\n", c);

            putchar('?');

    #if defined(M_UNIX) || defined(M_SYSV)

    /* или просто #if M_UNIX */

            printf("Это UNIX\n");

    #else

            printf("Это не UNIX\n");

    #endif /* UNIX */

    }

Обычно #undef используется именно для переопределения макроса, как putchar в этом примере (дело в том, что putchar - это макрос из <stdio.h>).

Директива #if, использованная нами, является расширением оператора #ifdef и подставляет текст если выполнено указанное условие:


    #if  defined(MACRO)  /* равно #ifdef(MACRO)  */

    #if !defined(MACRO)  /* равно #ifndef(MACRO) */

    #if VALUE > 15       /* если целая константа

                            #define VALUE 25

                            больше 15 (==, !=, <=, ...) */

    #if COND1 || COND2   /* если верно любое из условий */

    #if COND1 && COND2   /* если верны оба условия      */

Директива #if допускает использование в качестве аргумента довольно сложных выражений, вроде

    #if !defined(M1) && (defined(M2) || defined(M3))

3.21.

Условная компиляция может использоваться для трассировки программ:

    #ifdef DEBUG

    # define DEBUGF(body)   \

    {                       \

            body;           \

    }

    #else

    # define DEBUGF(body)

    #endif

    int f(int x){   return x*x; }

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

            int x = 21;

            DEBUGF(x = f(x); printf("%s equals to %d\n", "x", x));

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

    }

При компиляции

    cc -DDEBUG file.c

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

3.22.

В языке C++ (развитие языка Си) слова class, delete, friend, new, operator, overload, template, public, private, protected, this, virtual являются зарезервированными (ключевыми). Это может вызвать небольшую проблему при переносе текста программы на Си в систему программирования C++, например:


    #include <termio.h>

      ...

    int fd_tty = 2;   /* stderr */

    struct termio old, new;

    ioctl (fd_tty, TCGETA, &old);

    new = old;

    new.c_lflag |= ECHO | ICANON;

    ioctl (fd_tty, TCSETAW, &new);

      ...

Строки, содержащие имя переменной (или функции) new, окажутся неправильными в C++. Проще всего эта проблема решается переименованием переменной (или функции). Чтобы не производить правки во всем тексте, достаточно переопределить имя при помощи директивы define:


    #define new    new_modes

      ... старый текст ...

    #undef new

При переносе программы на Си в C++ следует также учесть, что в C++ для каждой функции должен быть задан прототип, прежде чем эта функция будет использована (Си позволяет опускать прототипы для многих функций, особенно возвращающих значения типов int или void).

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

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