оказывает на идентификатор никакого влияния,
если ему уже была назначена какая-то компоновка.
Объявления с конфликтующими компоновками могут вызывать неопределенное поведение; более подробную информацию об этом можно получить
в правиле DCL36-C стандарта CERT C (не объявляйте идентификатор
с конфликтующими классами компоновки).
В табл. 10.1 показаны примеры объявлений с явной и неявной компоновкой.
Таблица 10.1. Примеры явной и неявной компоновки
Косвенная и ручная компоновка
foo.c
void func(int i) { // Неявная внешняя компоновка
// у i нет компоновки
}
static void bar(void); // Внутренняя компоновка, bar не из bar.c
extern void bar(void) {
// bar по-прежнему имеет внутреннюю компоновку, так как начальное
// объявление было статическим; спецификатор extern в данном случае
// ни на что не влияет
}
bar.c
extern void func(int i); // Явная внешняя компоновка
static void bar(void) { // Internal Внутренняя компоновка, bar не из foo.c
func(12); // Вызывает func из foo.c
}
int i; // Внешняя компоновка; не конфликтует с i из foo.c или bar.c
void baz(int k) { // Неявная внешняя компоновка
bar(); // Вызываем bar из bar.c, не foo.c
}
260
Глава 10. Структура программы (в соавторстве с Аароном Баллманом)
Идентификаторы в вашем публичном интерфейсе должны иметь внешнюю
компоновку, чтобы их можно было вызывать из-за пределов их единицы
трансляции. В то же время идентификаторы, относящиеся к подробностям
реализации, нужно объявлять либо с внутренней компоновкой, либо без
таковой. Обычно, чтобы этого достичь, функции публичного интерфейса
объявляют в заголовочном файле со спецификатором класса хранения
extern или без него (внешняя компоновка назначается объявлениям авто
матически, но в использовании extern нет никакого вреда) и похожим
образом размещают их определения в исходном файле.
Однако внутри исходного файла все объявления, которые выступают
подробностями реализации, должны содержать спецификатор static;
это позволяет сделать их приватными и доступными только в данном
файле. Вы можете подключить публичный интерфейс, объявленный в заголовочном файле, используя директиву препроцессора #include, чтобы
обратиться к нему из другого файла. Сущности, которые объявлены на
уровне файла и не должны быть видны за его пределами, обычно лучше
делать статическими (static). Этот подход ограничивает загромождение
глобального пространства имен и снижает вероятность непредвиденного
взаимодействия между единицами трансляции.
Структурирование простой программы
Чтобы научиться структурировать настоящие сложные программы, начнем
с несложного примера, определяющего, является ли число простым. Простым называют натуральное число больше 1, которое нельзя получить путем
умножения двух меньших натуральных чисел. Мы напишем два отдельных
компонента: статическую библиотеку, которая будет заниматься проверкой,
и приложение командной строки с пользовательским интерфейсом к ней.
Программа primetest принимает на вход список целочисленных значений, разделенных пробелами, и выводит информацию о том, является ли
каждое из них простым числом. Если какая-либо часть ввода окажется
некорректной, то программа выведет информативное сообщение с описанием того, как пользоваться ее интерфейсом.
Прежде чем приступать к структурированию программы, рассмотрим пользовательский интерфейс. Вначале мы выводим справочную информацию
для программы командной строки, как показано в листинге 10.2.
Структурирование простой программы 261
Листинг 10.2. Вывод справочной информации
// Выводим справочный текст в командной строке
static void print_help(void) {
printf("%s", "primetest num1 [num2 num3 ... numN]\n\n");
printf("%s", "Tests positive integers for primality. Supports testing
");
printf("%s [2-%llu].\n", "numbers in the range", ULLONG_MAX);
}
Функция print_help состоит из трех отдельных вызовов printf, которые
записывают в стандартный вывод текст с объяснением о том, как использовать эту команду.
Аргументы командной строки передаются программе в текстовом виде,
поэтому мы определяем служебную функцию, чтобы преобразовать их
в целочисленные значения, как показано в листинге 10.3.
Листинг 10.3. Преобразование отдельного аргумента
командной строки
// Преобразует строковый аргумент arg в значение unsigned long long,
// на которое ссылается val
// Возвращает true, если преобразование аргументов было успешным,
// и false, если нет
static bool convert_arg(const char *arg, unsigned long long *val) {
char *end;
// strtoll возвращает внутренний индикатор ошибки; очистите errno
// перед вызовом
errno = 0;
*val = strtoull(arg, &end, 10);
//
//
if
if
if
}
Отслеживаем ошибки, когда вызов возвращает контрольное значение
и устанавливает errno
((*val == ULLONG_MAX) && errno) return false;
(*val == 0 && errno) return false;
(end == arg) return false;
// Если мы попали сюда, нам удалось преобразовать аргумент.
// Но мы хотим допускать только
// значения больше 1, поэтомы мы отбрасываем все значения
Последние комментарии
15 часов 19 минут назад
17 часов 36 минут назад
1 день 8 часов назад
1 день 8 часов назад
1 день 13 часов назад
1 день 17 часов назад