Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Kernigan_B__Payk_R_Praktika_programmirovania.pdf
Скачиваний:
76
Добавлен:
18.03.2016
Размер:
2.53 Mб
Скачать

Однако, если вы пишете на С или C++, всю работу придется выполнять самостоятельно. Главное, что можно сказать про побайтовую обработку: она решает имеющуюся проблему для всех машин с 8-битовыми байтами, причем решает без участия flifdef. Мы еще вернемся к этой теме в следующей главе.

Итак, лучшим решением нередко оказывается преобразование информации в текстовый формат, который (не считая проблемы CRLF) является абсолютно переносимым: не существует никаких неопределенностей с его представлением. Однако и текстовый формат не является панацеей. Время и размер для некоторых программ могут быть критичны, кроме того, часть данных, особенно числа с плавающей точкой, могут потерять точность при округлениях в процессе передачи через printf и scanf. Если вам надо передавать числа с плавающей точкой, особо заботясь о точности, убедитесь, что у вас есть хорошая библиотека форматированного ввода-вывода; такие библиотеки существуют, просто ее может не оказаться конкретно в вашей системе. Особенно сложно представлять числа с плавающей точкой для переноса их в двоичном формате, но при должном внимании текст может эту проблему решить.

Существует один тонкий момент, связанный с использованием стан-| дартных функций для обработки двоичных файлов, — эти файлы необходимо открывать в двоичном режиме:

FILE *fin;

fin = fopen(binary_file, "rb"); с = getc(fin);

Если ' b' опущено, то в большинстве систем Unix это ни на что не повлиЯ яет, но в системах Windows первый встретившийся во вводе байт 1 Control-Z (восьмеричный 032, шестнадцатеричный 1А) прервет чтение! (такое происходило у нас с программой st rings из главы 5). В то же вре- j мя при использовании двоичного режима для чтения текстовых файлов; вам придется вставлять символы \ r во ввод и убирать их из вывода.

Переносимость и внесение усовершенствований

Одним из наиболее огорчительных источников проблем переносимо-1 сти является изменение системного программного обеспечения за время жизненного цикла. Изменения могут затронуть любой интерфейс системы, приводя к неоправданной несовместимости версий.

При изменении спецификации изменяйте и имя. Наш любимый (если)

можно так выразиться) пример — изменение свойств команды Unix: echo, которая в изначальном виде предназначалась для простого вывода аргументов:

% echo hello, world

hello, world

%

Однако со временем эта команда стала ключевой частью многих тов оболочки, и перед ней встала необходимость генерировать формате рованный вывод. Теперь echo стала некоторым образом интерпретирс вать аргументы, то есть стала неким аналогом printf:

% echo 'hello\nworld

hello

world

%

Новые возможности, конечно, полезны, но из-за них у всех скриптов, использующих echo в изначальном варианте, возникли проблемы с совместимостью. Поведение

% echo $PATH

стало зависеть от того, какая из версий echo используется. Если переменная случайно содержит обратную косую черту (что вполне может произойти в DOS или Windows), то echo попытается интерпретировать ее. Это похоже на разницу в выводе через printf(str) и printf ("%s", str) в случае, если переменная str содержит знак процента.

Мы привели только часть истории про echo, но уже то, что сказано, иллюстрирует основную проблему: изменения в системе приводят к появлению версий программ с преднамеренно различным поведением, что создает непреднамеренные проблемы с переносимостью. И исправить эти ошибки зачастую оказывается непросто. Проблем было бы гораздо меньше, если бы новая версия echo получила и новое имя.

Приведем еще один пример. В Unix существует команда sum, которая вводит размер файла и его контрольную сумму. Предназначена эта команда для проверки правильности передачи данных:

% sum file 52313 2 file

%

% копируем file на другую машину

%

% telnet othermachine

$

$ sum file 52313 2 file

$

После передачи контрольная сумма не изменилась, так что с хорошей вероятностью можно считать, что передача прошла успешно.

Система разрасталась, появлялись новые версии, и в какой-то момент кто-то решил, что алгоритм вычисления контрольной суммы не идеален, и sum была изменена с использованием лучшего алгоритма. Кто-то еще пришел к тому же выводу и тоже изменил sum, реализовав другой, столь же хороший алгоритм, и т. д. В результате сейчас имеется несколько версий sum, каждая из которых выдает свой вариант ответа. Мы поставили ссперимент, скопировав некий файл на другие машины, чтобы выяснить, жие же результаты покажет sum в каждом конкретном случае:

% sum file 52313 2 file

%

%копируем file на машину 2

%копируем file на машину 3

%telnet machine2

$ Ф

$ sum file eaaOd468 713 file $ telnet machines >

> sum file 62992 1 file >

Непонятно, произошел сбой в передаче или просто мы столкнулися разными версиями sum. Может быть и то, и другое.

Таким образом, sum являет собой яркий пример препятствия на пути переносимости: программа, призванная помогать в копировании файлов с одной машины на другую, имеет несколько несовместимых версий, что делает ее абсолютно непригодной для использования.

Для выполнения изначально поставленной задачи первая версия sum] юлнелодходила: алгоритм, заложенный в ней, был не самым эффективным, но приемлемым. Ее "улучшение", может, и сделало собственно команду лучше, но зато использовать ее по назначению стало нельзя. И делят, надо сказать, не в том, что получилось несколько разных по существу! зманд, а в том, что все эти команды имеют одно и то же имя. Как видите! юблема несовместимости версий может оказаться весьма серьезной.

Поддерживайте совместимость с существующими программами и данными. Когда выпускается новая версия программы, например текстового редактора, то она, как правило, умеет читать файлы, созданный предыдущей версией. При этом мы ожидаем, что из-за добавления новых возможностей формат должен измениться. Но зачастую новые версии оказываются не в состоянии обеспечить способ записи в предыдущем формате. Пользователи новых версий, даже если они не обращаются к добавленным возможностям, не могут применять свои файлы совместно с пользователями более старой версии, таким образом, обновлять программы приходится сразу всем. Независимо от того, чем определяются такие решения — технической необходимостью или маркетинговой политикой, — о таких случаях можно только сожалеть.

Совместимостью сверху вниз называется возможность программы соответствовать спецификациям своих более ранних версий. Если вы собираетесь изменить свою программу, убедитесь, что при этом вы не создадите противоречий со старыми версиями и связанными с ними данными. Тщательно документируйте изменения и продумайте способ восстановить первоначальные возможности. И главное, задумайтесь над тем, перевесят ли достоинства предлагаемых вами усовершенствований потери от непереносимости, которые при этом возникнут.

Интернационализация

Если вы живете в Соединенных Штатах, то вы, может быть, забыли, что английский

— не единственный язык на свете, ASCII — не единственный набор символов, $ — не единственный символ валюты, что даты могут записываться с указанием сначала

дня, а потом уже месяца, что время может записываться в формате с 24-мя часами и т. п. Так вот, еще один аспект переносимости в общем виде связан с созданием программ, переносимых между разными языками и культурными границами. Это на самом деле весьма обширная тема для разговора, и мы будем вынуждены ограничиться освещением лишь нескольких основных концепций.

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

Не рассчитывайте на ASCII. В большинстве стран мира наборы символов богаче, чем ASCII. Стандартная функция проверки символов из ctype. h, в общем, успешно справляется с этими различиями:

if (isalpha(c)) ...

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

В большинстве европейских стран кодировка ASCII, определяющая только значения до 0x7F (7 битов), расширяется дополнительными символами, которые представляют собой буквы национальных языков.

Кодировка Latin-1, широко распространенная в Западной Европе, является расширением ASCII, определяющим значения байтов от 80 до FF я небуквенных символов и акцентированных букв — так, значение Е7 представляет букву д. Английское слово boy представляется в ASCII ли Latin-1) тремя байтами с шестнадцатеричными значениями 62 6F 79, эранцузское слово да гсоп представляется в Latin-1 байтами 67 61 72 Е7 6Е. В других языках определяются, соответственно, другие символы, но они не могут уложиться в 128 значений, не используемых в ASCII, к что существует множество конфликтующих стандартов для символов, привязанных к байтам от 80 до FF.

Некоторым языкам вообще не хватает 8 битов: в большинстве азиат-их языков существуют тысячи символов. В кодировках, используемых в Китае, Японии и Корее, на символ отводится 16 битов. В результате возникает глобальная проблема переносимости: как прочитать кумент на некотором языке на компьютере, настроенном на другой язык. Даже если все символы передадутся без ошибок, для прочтения на американском компьютере документа на китайском языке должны как шимум стоять специальные шрифты и соответствующее программное обеспечение. Если же мы захотим на одной машине использовать английский, китайский и русский языки, проблем у нас возникнет море.

Набор символов Unicode — попытка улучшить описанную ситуацию, предоставив единую кодировку для всех языков мира. Unicode совместима с 16-битовым подмножеством стандарта ISO 10646; в ней используется 16 битов на символ. Значения от OOFF и ниже относятся к Latin-1,то есть слово gargon будет представлено 16-битовыми значениями 0067 61 0072 ООЕ7 006F 006Е. Кириллица занимает значения от 0401 до 04FF,а идеографическим языкам отведен большой блок, начинающийся ЮОО. Все известные и некоторые почти неизвестные языки мира представлены в Unicode, так что именно этой кодировкой и стоит пользовать

для передачи документов между странами или для хранения текста, .писанного на разных языках. Unicode стала весьма популярна в Интер-;те, и некоторые языки программирования даже поддерживают ее как стандартный формат: например, Java использует Unicode как родной набоp символов для строк. Операционные системы Plan 9 и Inferno испольч ют Unicode более широко — даже для имен файлов и пользователей, Microsoft Windows поддерживает набор символов Unicode, но не считает о стандартом; большинство приложений Windows до сих пор лучше; .ботает с ASCII, хотя соотношение стремительно меняется в пользу: nicode.

Надо сказать, что и у Unicode есть недостатки: символы в ней уже не гещаются в один байт, поэтому текст в Unicode страдает от проблемы порядка байтов. Для преодоления этой напасти документы в Unicod перед передачей между программами или по сети обычно преобразуются в кодировку потока байтов, называемую UTF-8. В ней каждый 16-битовый символ кодируется для передачи как последовательность из 1, 2 или 3 байтов. Набор символов ASCII использует значения от 00 до 7F, все они умещаются в один байт при использовании UTF-8. Таким образом, получается, что UTF-8 односторонне совместима с ASCII. Значения между 80 и 7FF представляются двумя байтами, а значения от 800 и выше — тремя.LВ UTF-8 слово gargon представляется байтами 67 61 72 СЗ А7 6F 6Е; значение Unicode E7 — символ g — представляется в UTF-8 двумя байтами — СЗ А7.

Совместимость UTF-8 с ASCII весьма полезн-а, поскольку благодаря ей программы, рассматривающие текст как непрерывный поток байтов, могут работать с текстом Unicode на любом языке. Мы опробовали программу markov из третьей главы с текстом в UTF-8 на русском, греческом, японском и китайском языках, и она работала без каких-либо проблем. Для европейских языков, слова в которых разделяются ASCII-символами пробелов, табуляции или перевода строки, программа выдавала вполне сносный текст. При использовании других языков для того, чтобы получить что-то приемлемое на выходе, пришлось бы изменять правила разбиения текста на слова.

С и C++ поддерживают "широкие символы" (wide characters), которые представляются 16-битовыми или еще большими целыми. Суще-CTByiw и соответствующие функции, которые могут быть использованы для обработки символов в Unicode или в другом расширенном наборе символов. Строковые константы из широких символов записываются как L". . ,". Однако и здесь возникает большая проблема с переносимостью: программа с константами из широких символов может быть воспроизведена только на дисплее, использующем тот же набор символов. Поскольку символы должны быть конвертированы в поток байтов вроде UTF-8 для передачи между машинами, язык С предоставляет функции для преобразования широких символов в байты и обратно. Однако какое преобразование использовать? Интерпретация набора символов и описания кодировки потока байтов таятся в недрах библиотек, и вытащить их оттуда достаточно сложно; ситуация складывается не в нашу пользу. Может статься, в отдаленном светлом будущем все наконец придут к согласию об использовании единого набора символов, но пока что от проблемы порядка байтов никуда нам не деться.

Не ориентируйтесь только на английский язык. Создатели пользова-гельского интерфейса должны помнить, что в различных языках на выражение одного и того же понятия может потребоваться совершенно эазное количество символов, так что на экране и в массивах должно быть достаточно места.