В современных UNIX-ах с поддержкой различных языков таблица ctype загружается из некоторых системных файлов - для каждого языка своя. Для какого языка - выбирается по содержимому переменной окружения LANG. Если переменная не задана - используется значение "C", английский язык. Загрузка таблиц должна происходить явно, вызовом
... #include <locale.h> ... main(){ setlocale(LC_ALL, ""); ... все остальное ... }
#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, который останется положительным. Вероятно, второй путь предпочтительнее.
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); }
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 такой проблемы не будет.
#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.
#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");
Обычно возникают проблемы при написании функций с переменным числом аргументов. В языке Си эта проблема решается использованием макросов 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'.
Напишите функцию printf, понимающую форматы %c (буква), %d (целое), %o (восьмеричное), %x (шестнадцатеричное), %b (двоичное), %r (римское), %s (строка), %ld (длинное целое). Ответ смотри в приложении.
Для того, чтобы один и тот же исходный текст программы транслировался на разных машинах (в разных системах), приходится выделять в программе системно-зависимые части. Такие части должны по-разному выглядеть на разных машинах, поэтому их оформляют в виде так называемых "условно компилируемых" частей:
#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.
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.
#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))
#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 этой выдачи не будет.
В языке 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
Назад | Содержание | Вперед