Смекни!
smekni.com

Правила правой руки 17 Замечания для программистов на c 17 Глава 1 (стр. 22 из 43)

язык специльного назначения со своим собственным компилятором.

Никакого стандартного метода создания такого компилятора с

затравкой не принято.

4.3.2 Множественные Заголовочные Файлы

Стиль разбиения программы с одним заголовочным файлом наиболее

пригоден в тех случаях, когда программа невелика и ее части не

предполагается использовать отдельно. Поэтому то, что невозможно

установить, какие описания зачем помещены в заголовочный файл,

несущественно. Помочь могут комментарии. Другой способ - сделать

так, чтобы каждая часть программы имела свой заголовочный файл, в

котором определяются предоставляемые этой частью средства. Тогда

каждый .c файл имеет соответствующий .h файл, и каждый .c файл

включает свой собственный (специфицирующий то, что в нем задается)

.h файл и, возможно, некоторые другие .h файлы (специфицирущие то,

что ему нужно).

Рассматривая организацию калькулятора, мы замечаем, что error()

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

только . Это обычная для функции ошибок ситуация, поэтому

error() следует отделить от main():

- стр 117 -

// error.h: обработка ошибок

extern int no_errors;

extern double error(char* s);

// error.c

#include

#include "error.h"

int no_of_errors;

double error(char* s) { /* ... */ }

При таком стиле использования заголовочных файлов .h файл и

связанный с ним .c файл можно рассматривать как модуль, в котором

.h файл задает интерфейс, а .c файл задает реализацию.

Таблица символов не зависит от остальной части калькулятора за

исключеним использования функции ошибок. Это можно сделать явным:

// table.h: описания таблицы имен

struct name {

char* string;

name* next;

double value;

};

extern name* look(char* p, int ins = 0);

inline name* insert(char* s) { return look(s,1); }

// table.c: определения таблицы имен

#include "error.h"

#include

#include "table.h"

const TBLSZ = 23;

name* table[TBLSZ];

name* look(char* p; int ins) { /* ... */ }

Заметьте, что описания функций работы со строками теперь

включаются из . Это исключает еще один возможный источник

ошибок.

- стр 118 -

// lex.h: описания для ввода и лексического анализа

enum token_value {

NAME, NUMBER, END,

PLUS='+', MINUS='-', MUL='*', DIV='/',

PRINT=';', ASSIGN='=', LP='(', RP=')'

};

extern token_value curr_tok;

extern double number_value;

extern char name_string[256];

extern token_value get_token();

Этот интерфейс лексического анализатора достаточно беспорядочен.

Недостаток в надлежащем типе лексемы обнаруживает себя в

необходимости давать пользователю get_token() фактические

лексические буферы number_value и name_string.

// lex.c: определения для ввода и лексического анализа

#include

#include

#include "error.h"

#include "lex.h"

token_value curr_tok;

double number_value;

char name_string[256];

token_value get_token() { /* ... */ }

Интефейс синтаксического анализатора совершенно прозрачен:

// syn.c: описания для синтаксического анализа и вычисления

extern double expr();

extern double term();

extern double prim();

// syn.c: определения для синтаксического анализа и вычисления

#include "error.h"

#include "lex.h"

#include "syn.h"

double prim() { /* ... */ }

double term() { /* ... */ }

double expr() { /* ... */ }

Главная программа, как всегда, тривиальна:

- стр 119 -

// main.c: главная программа

#include

#include "error.h"

#include "lex.h"

#include "syn.h"

#include "table.h"

#include

main(int argc, char* argv[]) { /* ... */ }

Сколько заголовочных файлов использовать в программе, зависит от

многих факторов. Многие из этих факторов сильнее связаны с тем, как

ваша система работает с заголовочными файлами, нежели с C++.

Например, если в вашем редакторе нет средств, позволяющих

одновременно видеть несколько файлов, использование большого числа

файлов становится менее привлекательным. Аналогично, если

открывание и чтение 10 файлов по 50 строк в каждом требует заметно

больше времени, чем чтение одного файла в 500 строк, вы можете

дважды подумать, прежде чем использовать в небольшом проекте стиль

множественных заголовочных файлов. Слово предостережения: набор из

десяти заголовочных файлов плюс стандартные заголовочные файлы

обычно легче поддаются управлению. С другой стороны, если вы

разбили описания в большой программе на логически минимальные по

размеру заголовочные файлы (помещая каждое описание структуры в

свой отдельный файл и т.д.), у вас легко может получиться

неразбериха из сотен файлов.

4.3.3 Скрытие Данных

Используя заголовочные файлы пользователь может определять явный

интерфейс, чтобы обеспечить согласованное использование типов в

программе. С другой стороны, пользователь может обойти интерфейс,

задаваемый заголовочным файлом, вводя в .c файлы описания extern.

Заметьте, что такой стиль компоновки не рекомендуется:

// file1.c: // "extern" не используется

int a = 7;

const c = 8;

void f(long) { /* ... */ }

// file2.c: // "extern" в .c файле

extern int a;

extern const c;

extern f(int);

int g() { return f(a+c); }

Поскольку описания extern в file2.c не включаются вместе с

определениями в файле file1.c, компилятор не может проверить

согласованность этой программы. Следовательно, если только

загрузчик не окажется гораздо сообразительнее среднего, две ошибки

в этой программе останутся, и их придется искать программисту.

Пользователь может защитить файл от такой недисциплинированной

компоновки, описав имена, которые не предназначены для общего

- стр 120 -

пользования, как static, чтобы их областью видимости был файл, и

они были скрыты от остальных частей программы. Например:

// table.c: определения таблицы имен

#include "error.h"

#include

#include "table.h"

const TBLSZ = 23;

static name* table[TBLSZ];

name* look(char* p; int ins) { /* ... */ }

Это гарантирует, что любой доступ к table действительно будет

осуществляться именно через look(). "Прятать" константу TBLSZ не

обязательно.

4.4 Файлы как Модули

В предыдущем разделе .c и .h файлы вместе определяли часть

программы. Файл .h является интерфейсом, который используют другие

части программы; .c файл задает реализацию. Такой объект часто

называют модулем. Доступными делаются только те имена, которые

необходимо знать пользователю, остальные скрыты. Это качество часто

называют скрытием данных, хотя данные - лишь часть того, что может

быть скрыто. Модули такого вида обеспечивают большую гибкость.

Например, реализация может состоять из одного или более .c файлов,

и в виде .h файлов может быть предоставлено несколько интерфейсов.

Информация, которую пользователю знать не обязательно, искусно

скрыта в .c файлах. Если важно, что пользователь не должен точно

знать, что содержится в .c файлах, не надо делать их доступными в

исходом виде. Достаточно эквивалентных им выходных файлов

компилятора (.o файлов).

Иногда возникает сложность, состоящая в том, что подобная

гибкость достигается без формальной структуры. Сам язык не

распознает такой модуль как объект, и у компилятора нет возможности

отличить .h файлы, определяющие имена, которые должны использовать

другие модули (экспортируемые), от .h файлов, которые описывают

имена из других модулей (импортируемые).

В других случаях может возникнуть та проблема, что модуль

определяет множество объектов, а не новый тип. Например, модуль

table определяет одну таблицу, и если вам нужно две таблицы, то нет

простого способа задать вторую таблицу с помощью понятия модуля.

Решение этой проблемы приводится в Главе 5.

Каждый статически размещенный объект по умолчанию

инициализируется нулем, программист может задать другие

(константные) значения. Это только самый примитивный вид

инициализации. К счастью, с помощью классов можно задать код,

который выполняется для инициализации перед тем, как модуль каким-

либо образом используется, и/или код, который запускается для

очистки после последнего использования модуля; см. #5.5.2.

- стр 121 -

4.5 Как Создать Библиотеку

Фразы типа "помещен в библиотеку" и "ищется в какой-то

библиотеке" используются часто (и в этой книге, и в других), но что

это означает для C++ программы? К сожалению, ответ зависит от того,

какая операционная система используется; в этом разделе

объясняется, как создать библиотеку в 8-ой версии системы UNIX.

Другие системы предоставляют аналогичные возможности.

Библиотека в своей основе является множеством .o файлов,

полученных в результате компиляции соответствующего множества .c

файлов. Обычно имеется один или более .h файлов, в которых

содержатся описания для использования этих .o файлов. В качестве

примера рассмотрим случай, когда нам надо задать (обычным способом)

набор математических функций для некоторого неопределенного

множества пользователей. Заголовочный файл мог бы выглядеть

примерно так:

extern double sqrt(double); // подмножество

extern double sin(double);

extern double cos(double);

extern double exp(double);

extern double log(double);

а определения этих функций хранились бы, соответственно, в файлах

sqrt.c, sin.c, cos.c, exp.c и log.c.

Библиотеку с именем math.h можно создать, например, так:

$ CC -c sqrt.c sin.c cos.c exp.c log.c

$ ar cr math.a sqrt.o sin.o cos.o exp.o log.o

$ ranlib math.a

Вначале исходные файлы компилируются в эквивалентные им объектные

файлы. Затем используется команда ar, чтобы создать архив с именем

math.a. И, наконец, этот архив индексируется для ускорения доступа.

Если в вашей системе нет команды runlib, значит она вам, вероятно,

не понадобится. Подробности посмотрите, пожалуйста, в вашем

руководстве в разделе под заголовком ar. Использовать библиотеку

можно, например, так:

$ CC myprog.c math.a

Теперь разберемся, в чем же преимущества использования math.a

перед просто непосредственным использованием .o файлов? Например:

$ CC myprog.c sqrt.o sin.o cos.o exp.o log.o

Для большинства программ определить правильный набор .o файлов,

несомненно, непросто. В приведенном выше примере они включались