Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
kernigan_paik.doc
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
2.91 Mб
Скачать

8.2. Заголовочные файлы и библиотеки

Заголовочные файлы и библиотеки предоставляют возможности, расширяющие базовый язык. Например, ввод и вывод осуществляются с помощью библиотек stdio в С, iostream в C + + и Java, io в Java. Стро­го говоря, эти элементы не являются частью языка, но они определены вместе с языком и представляют собой составную часть любой среды, поддерживающей этот язык. Однако, поскольку библиотеки покрывают широкий спектр возможностей и нередко имеют дело со специфически­ми вопросами устройства операционных систем, в их использовании может крыться причина плохой переносимости программы.

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

В ANSI С не определена функция копирования строк strdup, хотя она имеется в большинстве сред программирования — даже в тех, что дек­ларируют строгую приверженность стандарту. Может показаться за­манчивым использовать данную функцию, но делать этого не следует: компилятор не предупредит вас, что функция не стандартная, а в даль­нейшем программу будет не перенести в среду, этой функции не имею­щую. Проблемы подобного рода — основной источник головной боли при использовании библиотек, и единственное решение — придержи­ваться стандарта и тестировать свою программу на возможно большем количестве конфигураций.

Заголовочные файлы и описания пакетов описывают интерфейс со стандартными функциями. Один из недостатков многих заголовочных файлов состоит в том, что в них приводятся описания сразу для несколь­ких языков. Нередко можно встретить один файл вроде stdio.h с описа­ниями одновременно для старого (до стандарта ANSI) С, ANSI С и даже C++ компиляторов. Такой файл получается очень громоздким — в нем много директив условной компиляции вроде #if и #ifdef. Язык препро­цессора не слишком гибок, поэтому такие файлы получаются довольно сложными для восприятия; иногда в них даже содержатся ошибки.

Ниже приведен фрагмент заголовочного файла одной из систем, при­чем он еще гораздо лучше многих, по крайней мере нормально отформа­тирован:

? #ifdef _OLD_C

? extern int fread();

? extern int fwrite();

? #else

? #if defined(__STDC__) || defined(__cplusplus)

? extern size_t fread(void*, size_t, size_t, FILE*).;

? extern size_t fwrite(const void*, size_t, size_t, FILE*);

? # else /* not __STDC__ || __cplusplus */

? extern size_t fread();

? extern size_t fwrite();

? # endif /* else not __STDC__ | | „cplusplus */

? #endif

Даже из этого сравнительно простого примера видно, что заголовочные файлы и программы, структурированные подобным образом, получают­ся довольно запутанными и сопровождать их достаточно сложно. Воз­можно, проще использовать отдельный заголовочный файл для каждого компилятора или среды. При этом придется сопровождать множество отдельных файлов, но каждый из них будет предназначен только для ис­пользования в конкретной конфигурации, что уменьшит вероятность появления ошибок вроде включения функции st rdup в среду, строго под­держивающую стандарт ANSI С.

Заголовочные файлы также иногда "засоряют" пространство имен, определяя функции с именами, уже использующимися в программе. На­пример, наша функция оповещения об ошибках weprintf изначально на­зывалась wprintf, однако мы выяснили, что в некоторых средах функция с таким именем определена в stdio. h (можно сказать, что сделано это в преддверии нового стандарта С). Для того чтобы скомпилировать про­грамму в этих средах и защитить себя в будущем, нам пришлось изме­нить название своей функции. Если бы проблема состояла в некоррект­ном компиляторе, а не в ожидаемом изменении спецификации, как в нашем случае, то ее можно было бы решить, переопределяя имя при подключении заголовочного файла:

? /* в stdio.h иногда входит wprintf, переопределим его: */

? #define wprintf stdio_wprintf

? #include <stdio.h>

? #undef wprintf

? /* далее можно использовать нашу wprintf() ... */

Этот фрагмент изменяет все появления wprintf в заголовочном файле на stdio_wprintf, так что теперь они не повлияют на нашу версию. Те­перь мы можем использовать нашу wprintf, не изменив ее имени, правда, при этом неизбежно появится некая путаница, а также риск, что подключенная библиотека будет вызывать нашу wprintf, подразумевая обращение к своей версии. Для одной функции проблемы, может, и невелики, но уже для нескольких лучше придумать более радикальное решение. Всегда комментируйте назначение конструк­ции; без крайней необходимости не ухудшайте ее добавлением услов­ной компиляции. Если в некоторых средах определена wprintf, то стоит считать, что она определена во всех; тогда единственный разум­ный выход — переименовать ее, избавившись при этом от выражения #ifdef. Нередко проще не превозмогать трудность, а подстраиваться под нее; да это и безопаснее, вот почему мы решили переименовать свою функцию в weprintf.

Даже если вы следуете всем правилам и неясностей со средой не воз­никает, все равно вполне возможно появление ошибок. Так, можно оши­биться, предположив, что какая-нибудь ваша излюбленная возможность одинакова во всех системах, К примеру, ANSI С определяет шесть сиг­налов, которые можно поймать с помощью signal, в стандарте POSIX их определено 19, а большинство систем Unix поддерживает 32 и более. Если вы хотите использовать сигнал, отличный от описанного в ANSI С, вам придется выбирать между функциональностью и переносимостью, так что сами решайте, что для вас важнее.

Существует большое количество других стандартов, не являющихся частью определения языка: среди них можно назвать интерфейсы опера­ционных систем и сетей, графические интерфейсы и тому подобные вещи. Некоторые стандарты распространяются на несколько систем — например POSIX; другие определены исключительно для одной систе­мы, например различные API Microsoft Windows. Здесь можно еще раз повторить наши главные советы: ваша программа станет более перено­симой, если вы выберете самые распространенные и устоявшиеся стан­дарты и будете пользоваться самыми важными и общепринятыми их свойствами.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]