Как стать автором
Обновить
2890.21
RUVDS.com
VDS/VPS-хостинг. Скидка 15% по коду HABR15

Кунг-фу стиля Linux: автоматическое генерирование заголовочных файлов

Время на прочтение7 мин
Количество просмотров6.1K
Автор оригинала: Al Williams
Я пробовал много «новых» языков программирования, но как-то так получается, что всегда, когда я возвращаюсь к С++ или даже к C, это наполняет меня радостью. Правда, когда я возвращаюсь к этим языкам, кое-что меня слегка раздражает. Это — то, что мне нужны заголовочные файлы с объявлениями, а так же — отдельный файл, в котором продублирована почти та же самая информация. Я постоянно вношу в код изменения и забываю обновлять заголовочные файлы. Эти файлы не генерируются автоматически при использовании C++ и C. А инструменты, сопутствующие другим языкам, сами заботятся о подобных вещах. Это привело меня к поискам чего-либо, позволяющего автоматизировать генерирование заголовочных файлов. Конечно, некоторые IDE автоматически вставляют объявления туда, куда нужно, но меня всё это, по многим причинам, никогда полностью не устраивало. Мне хотелось что-то маленькое и быстрое, такое, что я мог бы использовать в составе множества различных наборов инструментов.



Я нашёл один довольно старый инструмент, который хорошо справляется с этой задачей. У него, правда, имеются определённые ограничения. Инструмент этот кажется немного запутанным. Поэтому я решил, что о нём стоит рассказать. Речь идёт о makeheaders. Это — часть системы управления конфигурацией программ Fossil. История программы восходит к 1993 году, когда Дуэйн Ричард Хипп (тот самый программист, который создал SQLite) написал её для собственных нужд. Эта программа не особенно сложна: вся она помещается в довольно большом C-файле. Но своё дело она делает: сканирует директорию и создаёт для всего, что найдёт, заголовочные файлы. В некоторых случаях для применения makeheaders нет нужды делать больших изменений в исходном коде, но, если есть такая необходимость, кое-что в нём можно и изменить.

Все переводы серии
Кунг-фу стиля Linux: удобная работа с файлами по SSH
Кунг-фу стиля Linux: мониторинг дисковой подсистемы
Кунг-фу стиля Linux: глобальный поиск и замена строк с помощью ripgrep
Кунг-фу стиля Linux: упрощение работы с awk
Кунг-фу стиля Linux: наблюдение за файловой системой
Кунг-фу стиля Linux: наблюдение за файлами
Кунг-фу стиля Linux: удобный доступ к справке при работе с bash
Кунг-фу стиля Linux: великая сила make
Кунг-фу стиля Linux: устранение неполадок в работе incron
Кунг-фу стиля Linux: расшаривание терминала в браузере
Кунг-фу стиля Linux: синхронизация настроек
Кунг-фу стиля Linux: бесплатный VPN по SSH
Кунг-фу стиля Linux: превращение веб-приложений в полноценные программы
Кунг-фу стиля Linux: утилита marker и меню для командной строки
Кунг-фу стиля Linux: sudo и поворот двух ключей
Кунг-фу стиля Linux: программное управление окнами
Кунг-фу стиля Linux: организация работы программ после выхода из системы
Кунг-фу стиля Linux: регулярные выражения
Кунг-фу стиля Linux: запуск команд
Кунг-фу стиля Linux: разбираемся с последовательными портами
Кунг-фу стиля Linux: базы данных — это файловые системы нового уровня
Кунг-фу стиля Linux: о повторении кое-каких событий сетевой истории
Кунг-фу стиля Linux: PDF для пингвинов
Кунг-фу стиля Linux: делаем все и сразу
Кунг-фу стиля Linux: файловые системы пользовательского пространства теперь доступны и в Windows
Кунг-фу стиля Linux: делиться — это плохо
Кунг-фу стиля Linux: автоматическое генерирование заголовочных файлов

Обзор проблемы


Предположим, имеются C-файлы, работающие совместно. Пусть это — файлы A.c и B.c. В файле A.c находится простая функция:

double ctof(double c)
{
  return (9.0*c)/5+32.0;
}

Если предполагается использовать эту функцию в файле B.c, там должно быть объявление этой функции, чтобы, когда компилируется B.c, компилятор мог бы узнать о том, что функция принимает единственный аргумент типа double и возвращает значение того же типа. В коде, написанном на ANSI C (и на C++) понадобится примерно такая конструкция:

double ctof(double c);

Это — не исполняемый код. Это — всего лишь подсказка компилятору о том, как выглядит функция. Такие конструкции называют прототипами функций. Обычно создают заголовочный файл, содержащий подобный прототип. Включить этот файл можно и в A.c, и в B.c.

Проблема возникает тогда, когда меняют функцию в A.c:

double ctof(double c1, double c2)
{
  return (9.0*(c1+c2))/5+32.0;
}

Если не привести содержимое заголовочного файла в соответствие с новым описанием функции — жди неприятностей. Причём, объявление функции в заголовочном файле должно точно соответствовать её определению в файле исходного кода. Если, например, ошибиться и указать в заголовочном файле, что аргументы функции имеют тип float — то, что получится, работать не будет.

Программа makeheaders


Если у вас имеется makeheaders — вы можете просто запустить эту программу, передав ей все C- и H-файлы, которые ей нужно просканировать. Обычно для этого достаточно воспользоваться glob-шаблоном *.[ch]. Программа может обрабатывать и CPP-файлы, и даже — наборы файлов разных типов. Она, по умолчанию, помещает все объявления глобальных переменных и определения глобальных функций в набор заголовочных файлов.

Почему речь идёт о «наборе заголовочных файлов»? Дело в том, что работа программы основана на одном предположении, которое, если о нём не задуматься, может показаться странным. Так как заголовочные файлы генерируются автоматически — нет смысла повторно их использовать. В результате программа помещает в них то, что нужно, располагая это в правильном порядке. Так, файл A.c будет использовать заголовочный файл A.h, а B.cB.h. Между этими двумя файлами не будет перекрёстных зависимостей. Если что-то изменяется, makeheaders просто запускают снова и она создаёт нужные заголовочные файлы.

Что попадает в заголовочные файлы?


Вот что документация к makeheaders говорит о том, что программа копирует в заголовочные файлы:

  • Если функция определена в одном из C-файлов, прототип этой функции помещается в сгенерированный H-файл, предназначенный для любого из C-файлов, вызывающих эту функцию. Если в определении функции используется ключевое слово static — прототип в H-файле не размещается. Если там, где обычно пользуются static, используют ключевое слово LOCAL, тогда прототип генерируется, но размещается он лишь в единственном заголовочном файле, соответствующем тому файлу исходного кода, который содержит такую функцию. А в H-файлы, предназначенные для других C-файлов, прототип LOCAL-функции не попадает, так как область видимости такой функции ограничена тем файлом, в котором она определена. Если вызвать makeheaders с опцией командной строки -local — тогда программа будет воспринимать ключевое слово static как LOCAL и создаст прототипы соответствующих функций в заголовочном файле, предназначенном для файла исходного кода, содержащего определения таких функций.
  • Если в C-файле определена глобальная переменная, её объявление с ключевым словом extern помещают в заголовочные файлы, предназначенные для тех C-файлов, которые используют эту переменную. Если в H-файле, который создан вручную, встречается объявление структуры, объединения, перечисления, или прототип функции, или объявление C++-класса, всё это копируется в H-файлы, сгенерированные автоматически для всех функций, использующих то, что описано в H-файле, созданном вручную. Но объявления, встречающиеся в C-файлах, считаются внутренними, предназначенными для конкретных файлов, они не копируются в файлы, генерируемые автоматически.
  • Все конструкции #define и typedef, которые встречаются в H-файлах, созданных вручную, копируются, по необходимости, в H-файлы, созданные автоматически. Такие же конструкции, имеющиеся в C-файлах, считаются внутренними и не копируются. Если объявление структуры, объединения или перечисления расположено в H-файле — makeheaders автоматически сгенерирует конструкцию typedef, которая позволит ссылаться на объявление без использования квалификаторов struct, union или enum.

Обратите внимание на то, что makeheaders распознаёт файлы, созданные этой программой. Поэтому нет нужды исключать их из состава входных файлов, передаваемых ей при её вызове.

Пример на C++


В случае с чем-то вроде C++-классов, или, на самом деле, в случае с чем угодно, можно поместить блок кода в специальные директивы препроцессора для того чтобы программа makeheaders обработала бы этот блок. Вот — простой пример кода, который я использовал для того чтобы это проверить.



Тут стоит обратить внимание на несколько моментов:

  • Команда включения в C++-файл файла test.hpp возьмёт на себя задачу по включению в С++-файл и сгенерированного специально для него заголовочного файла.
  • Директива INTERFACE заключает в себя код, который должен попасть в заголовочный файл. Во время компиляции INTERFACE будет равно нулю, поэтому код не будет компилироваться дважды.
  • Функции-члены объявлены за пределами раздела INTERFACE с использованием ключевого слова PUBLIC (там, конечно, могут использоваться и ключевые слова PRIVATE или PROTECTED). Это приведёт к тому, что makeheaders обратит внимание и на них.

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

Обратите внимание на то, что, при использовании PUBLIC или других ключевых слов, функции изымают из объявления. Единственная причина, по которой в примере имеются некоторые функции, заключается в том, что они являются встроенными. Если поместить все функции за пределами блока INTERFACE, сгенерированный заголовочный файл правильно составит объявление класса. В данном случае он добавит эти функции к тем, которые уже имеются.

Сгенерированный заголовочный файл


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

Вот этот файл:



Обратите внимание на то, что INTERFACE здесь, в самом конце, устанавливается в 0. А это значит, что в файле с исходным кодом раздел INTERFACE не будет подвергаться повторной компиляции. В случае с C-файлами makeheaders, кроме того, генерирует конструкции typedef для чего-то вроде структур. В C++ это, конечно, не нужно. Тут можно видеть побочный эффект наличия некоторых объявлений в разделе INTERFACE, а некоторых — в разделе реализации: тут имеется избыточный тег PUBLIC. Вреда от этого нет, этот тег не появился бы в том случае, если бы я поместил весь код за пределами раздела INTERFACE.

Это ещё не всё


Гибкий инструмент, который мы рассмотрели, умеет и кое-что ещё. Узнать об этом можно из документации по нему. Он поддерживает флаг, благодаря которому выдаётся информация об обрабатываемом коде, которую можно использовать для его документирования. Можно создавать иерархии интерфейсов. Makeheaders, кроме того, может помочь в работе над проектами, где совместно используется C++ и C. Этот инструмент достаточно интеллектуален и поддерживает условную компиляцию. Правда, стоит учитывать то, что в число поддерживаемых им механизмов C++ не входят шаблоны и пространства имён. Правда, код makeheaders открыт, поэтому вы, если хотите, можете этот инструмент доработать. Прежде чем вы решитесь на применение makeheaders в крупном проекте — учтите, что у этого инструмента есть и ещё некоторые ограничения.

Планируете ли вы попробовать нечто вроде makeheaders, или вас устраивает ручная работа с заголовочными файлами?

Теги:
Хабы:
Всего голосов 22: ↑20 и ↓2+31
Комментарии10

Публикации

Информация

Сайт
ruvds.com
Дата регистрации
Дата основания
Численность
11–30 человек
Местоположение
Россия
Представитель
ruvds