Новые книги

The Windows Driver Model has two separate but equally important aspects. First, the core model describes the standard structure for device drivers. Second, Microsoft provides a series of bus and class drivers for common types of devices.

The core WDM model describes how device drivers are installed and started, and how they should service user requests and interact with hardware. A WDM device driver must fit into the Plug and Play (PnP) system that lets users plug in devices that can be configured in software.

Microsoft provides a series of system drivers that have all the basic functionality needed to service many standard types of device. The first type of system driver supports different types of bus, such as the Universal Serial Bus (USB), IEEE 1394 (FireWire) and Audio port devices. Other class drivers implement standard Windows facilities such as Human Input Devices (HID) and kernel streaming. Finally, the Still Image Architecture (STI) provides a framework for handling still images, scanners, etc.

These system class drivers can make it significantly easier to write some types of device driver. For example, the USB system drivers handle all the low-level communications across this bus. A well defined interface is made available to other drivers. This makes it fairly straightforward to issue requests to the USB bus.
В мире проводится огромное количество исследований предпочтений потребителей, но далеко не все они приводят к желаемым результатам: провалы случаются на каждом шагу. Похоже, что компании неверно интерпретируют ситуацию. Как же заглянуть в будущее? Автор этой книги, Мартин Реймонд, генеральный директор агентства The Future Laboratory («Лаборатория будущего»), говорит, что о грядущем много могут рассказать потребительские тренды, зарождающиеся сегодня. Он рассказывает, где найти потенциал прибыльности, как использовать социальные сети и на чем основывать свой маркетинг. Рекомендуется к прочтению руководителям компаний, специалистам по маркетингу и брендингу, слушателям программ MBA и магистратуры.

Мобильность и машинная зависимость программ. Проблемы с русскими буквами.



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).

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

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