Как создать заголовочный файл c
Перейти к содержимому

Как создать заголовочный файл c

  • автор:

Как написать свою библиотеку на Си

Если ты встал на путь С/С++ разработчика, то скорее всего (помимо использования стандартной библиотеки — libc) рано или поздно вам потребуется занятся разработкой собственных библиотек. Зачем. Причин может быть несколько. Например вы написали свою структуру данных или свой алгоритм, и хотите использовать его повторно или распространять. Так же возможно вы написали несколько утилит и все они используют один и тот же кусок кода (например, как часто это бывает, логгер), и будет логично вынести этот кусок кода в отдельный модуль. Поскольку сопровождать такой код будет проще.

Реализация

И так, давайте начнем с примера. Создадим заголовочный файл somecode.h, который будет содержать объявление некоторой функции. Пусть будет простая функция которая разбивает предложение на слова и печатает каждое слово в новой строке. Простой синтетический пример.

И создадим файл somecode.c, в которой напишем реализацию нашей функции.

Далее создадим файл main.c, где будем вызывать нашу функцию.

Давайте для начала скомпилируем это все самым обычным способом для проверки работоспособности.

  • из исходных файлов получаем объектные файлы
  • из объектных файлов получаем исполняемый файл

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

А теперь рассмотрим пример получения библиотеки и линковки его к исполняемому файлу. Для компиляции используем вызов gcc со следующими опциями.

Из файла с расширением .c мы получаем файл с расширением .so. И так же обратите внимание, что библиотека имеет префикс lib. Еще мы видим, что появились два дополнительных аргумента -shared и -fpic. С помощью опции -shared мы говорим компилятору, что хотим получить а выходе библиотеку. А опция -fpic говорит компилятору, что объектные файлы должны содержать позиционно-независимый код (position independent code), который рекомендуется использовать для динамических библиотек.

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

И опс.. мы получили ошибку… Линкер говорит нам, что он не знает где лежит наша библиотека. С помощью опции -L указываем текущую директорию, где лежит наша библиотека и компиляция проходит успешно.

Пытаемся запустить нашу и программу и ловим еще одну ошибку в котором говорится, что в процессе загрузки динамических библиотек отсуствует наша библиотека.

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

Так же есть возможность задавать дополнительные директории с библиотеками с помощью переменной окружения LD_LIBRARY_PATH. С помощью утилиты ldd посмотрим от каких библиотек зависит наша программа.

Добавим в LD_LIBRARY_PATH текущую директорию.

Видим, что наша библиотека подгрузилась.

И теперь программа запускается и работает.

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

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

Разница в нашем случае может и маленькая, но в масштабах десятков и сотен файлов разница будет значительной.

Так что же мы сделали?

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

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

Как создать заголовочный файл в Visual Studio?

Как создать exe файл в C++ Visual Studio 6.0
Начал изучение C++ Visual Studio 6.0 (dev C++ не понравилась) Создал проект, написал код.

Создать заголовочный файл к коду
о созданию хедер файла к следующей программе : Добавлено через 16 секунд #include "mmmap.h".

Как создать круглую кнопку в Visual Studio
Всем привет. Подскажите как сделать круглую кнопку и круглый светодиод и при этом кнопка должна еще.

Многофайловые программы

Запуск gcc позволяет обработать файл с исходным кодом препроцессором и далее скомпилировать его. Однако при этом сам инструмент gcc не компилирует файл исходного кода в конечный исполняемый файл. Он компилирует его в объектный файл, после чего вызывает так называемый линковщик, или компоновщик. Но зачем надо сначала получать объектный файл, а потом из него уже исполняемый? Для программ, состоящих из одного файла, такой необходимости нет. Хотя при желании здесь также можно отказаться от компоновки, если выполнить команду gcc с ключом -c:

В результате получится файл с расширением *.o. Чтобы получить из объектного файла исполняемый, надо использовать ключ -o:

Для программ, состоящих из нескольких файлов исходного кода, получение объектных файлов является необходимым. Именно из них потом компонуется единственный исполняемый файл.

Компиляция программы, состоящей из нескольких файлов исходного кода

Рассмотрим пример. Пусть в одном файле определена пара функций, а в другом, содержащем функцию main() , осуществляется их вызов.

В теле функции main() заполняется массив, состоящий из строк, а также массив указателей на эти строки. Далее в функции l2r() и r2l() передаются ссылки на первый элемент массива указателей и значение символической константы N. Эти функции осуществляют вывод элементов массива строк с отступами.

Чтобы получить исполняемый файл этой программы, надо сначала получить объектные файлы из исходных:

Тоже самое можно сделать за один вызов gcc:

Или даже вот так, если в каталоге находятся только файлы текущего проекта:

В любом случае в каталоге появятся два объектных файла: superprint.o и main.o. Далее их можно скомпилировать в один исполняемый файл так:

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

Если теперь запустить файл myprint, то программа будет ожидать ввода пяти слов, после чего выведет их на экран два раза по-разному (с помощью функций l2r() и r2l() ):

Задумаемся, каким образом в представленной выше программе функция main() «узнает» о существовании функций l2r() и r2l() . Ведь в исходном коде файла main.c нигде не указано, что мы подключаем файл superprint.c, содержащий эти функции. Действительно, если попытаться получить из main.c отдельный исполняемый файл, т.е. скомпилировать программу без superprint.c:

, то ничего не получиться. Компилятор сообщит об ошибке вызова неопределенных идентификаторов. Получить из файла superprint.c отдельный исполняемый файл вообще невозможно, т.к. там отсутствует функция main() . А вот получить из этих файлов отдельные объектные файлы можно. Представим, что одни объектные файлы как бы «выставляют наружу» имена определенных в них функций и глобальных переменных, а другие — вызовы этих имен из тел других функций. Дальше объектные файлы «ожидают», что имена будут связаны с их вызовами. Связывание происходит при компиляции исполняемого файла из объектных.

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

Продолжим разбирать приведенную выше программу. Что будет, если в функции main() осуществить неправильный вызов функций l2r() и r2l() ? Например, указать неверное количество параметров. В таком случае создание объектных файлов пройдет без ошибок, и скорее всего удастся получить исполняемый файл; но вот работать программа будет неправильно. Такое возможно потому, что ничего не контролирует соответствие вызовов прототипам (объявлениям) функций.

Куда правильней сообщать о неверном вызове функций уже при получении объектного файла. Поэтому хотя можно обойтись и без этого, но очень желательно сообщать функции main() прототипы функций, которые из нее вызываются. Это можно сделать, прописав объявления функций в файле main.c:

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

А теперь представим, что программа у нас несколько больше и содержит десяток файлов исходного кода. Файл aa.c требует функций из файла bb.c, dd.c, ee.c. В свою очередь dd.c вызывает функции из ee.c и ff.c, а эти два последних файла активно пользуются неким файлом stars.c и одной из функций в bb.c. Программист замучится сверять, что чего вызывает откуда и куда, где и какие объявления надо прописывать. Поэтому все прототипы (объявления) функций проекта, а также совместно используемые символические константы и макросы выносят в отдельный файл, который подключают к каждому файлу исходного кода. Такие файлы называются заголовочными; с ними мы уже не раз встречались. В отличие от заголовочных файлов стандартной библиотеки, заголовочные файлы, которые относятся только к вашему проекту, при подключении к файлу исходного кода заключаются в кавычки, а не скобки. Об этом упоминалось в предыдущем уроке.

Итак, более грамотно будет не добавлять объявления функций в файл main.c, а создать заголовочный файл, например, myprint.h и поместить туда прототипы функций l2r() и r2l() . А в файле main.c следует прописать директиву препроцессора:

В принципе смысла подключать myprint.h к файлу superprint.c в данном случае нет, т.к. последний не использует никаких сторонних функций, кроме стандартной библиотеки. Но если планируется расширять программу и есть вероятность, что в файле superprint.c будут вызываться сторонние для него функции, то будет надежней сразу подключить заголовочный файл.

Обратим внимание еще на один момент. Стоит ли в описанном в этом уроке примере выносить константу N в заголовочный файл? Здесь нельзя дать однозначный ответ. Если ее туда вынести, то она станет доступна в обоих файлах, и поэтому можно изменить прототипы функций так, чтобы они принимали только один параметр (указатель), а значение N будет известно функциям их заголовочного файла. Однако стоит ли так делать? В функции r2l() второй параметр изменяется в процессе ее выполнения, что делать с константой будет невозможно. Придется переписывать тело функции. Кроме того, вдруг в последствии нам захочется использовать файл superprint.c в другом проекте, где будут свои порядки, и константы N в заголовочном файле не найдется.

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

Особенности использования глобальных переменных

Помните, если можно отказаться от использования глобальных переменных, то лучше это сделать. Желательно стремиться к тому, чтобы любой файл проекта, скажем так, «не лез к соседу за данными, а сосед не разбрасывал эти данные в виде глобальных переменных». Обмен данными между функциями должен осуществлять за счет передачи данных в качестве параметров и возврата значений с помощью оператора return . (Массивов это не касается.)

Однако в языке программирования C есть проблема. С помощью return можно вернуть только одно значение. Но могут быть случаи, когда функция должна изменить несколько переменных (здесь не имеются ввиду элементы массива). В таком случае без глобальных переменных обойтись сложно.

C Language
Создание и включение файлов заголовков

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

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

Вступление

При создании и использовании файлов заголовков в проекте C существует ряд рекомендаций:

Если заголовочный файл несколько раз включается в блок трансляции (TU), он не должен нарушать сборки.

Если вам нужны средства, объявленные в файле заголовка, вам не нужно включать какие-либо другие заголовки явно.

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

Включить то, что вы используете (IWYU)

Больше заботы о C ++, чем C, но тем не менее важны и для C. Если код в TU (вызовите его code.c ) напрямую использует функции, объявленные заголовком (назовите его "headerA.h" ), тогда code.c должен #include "headerA.h" напрямую, даже если TU включает другой заголовок (назовите его "headerB.h" ), который в настоящий момент происходит с включением "headerA.h" .

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

идемпотентность

Если конкретный заголовочный файл включен более одного раза в блок трансляции (TU), не должно быть проблем с компиляцией. Это называется «идемпотенция»; ваши заголовки должны быть идемпотентными. Подумайте, насколько сложной была бы жизнь, если бы вы должны были убедиться, что #include <stdio.h> был включен только один раз.

Существует два способа достижения идемпотенциала: защита заголовков и директива #pragma once .

Защитные кожухи

Защитные решетки просты и надежны и соответствуют стандарту C. Первые строки без комментариев в файле заголовка должны иметь следующий вид:

Последняя строка без комментария должна быть #endif , необязательно с комментарием после нее:

Весь операционный код, включая другие директивы #include , должен находиться между этими строками.

Каждое имя должно быть уникальным. Часто используется схема имен, такая как HEADER_H_INCLUDED . В некотором более старом коде используется символ, обозначенный как защита заголовка (например, #ifndef BUFSIZ в <stdio.h> ), но он не так надежен, как уникальное имя.

Один из вариантов заключается в использовании сгенерированного хеша MD5 (или другого) для имени защитника заголовка. Вам следует избегать эмуляции схем, используемых заголовками системы, которые часто используют имена, зарезервированные для имен реализации, начиная с символа подчеркивания, за которым следует либо другое подчеркивание, либо буква верхнего регистра.

Директива #pragma once

В качестве альтернативы, некоторые компиляторы поддерживают директиву #pragma once которая имеет тот же эффект, что и три строки, показанные для защиты заголовков.

Компиляторы, которые поддерживают #pragma once включают MS Visual Studio и GCC и Clang. Однако, если переносимость вызывает беспокойство, лучше использовать защиту заголовков или использовать оба варианта. Современные компиляторы (поддерживающие C89 или более поздние) должны игнорировать, без комментариев, прагмы, которые они не распознают («Любая такая прагма, которая не распознается реализацией, игнорируется»), но старые версии GCC не были настолько снисходительными.

Автономность

Современные заголовки должны быть автономными, а это значит, что программа, которая должна использовать средства, определенные header.h может включать этот заголовок ( #include "header.h" ) и не беспокоиться о том, нужно ли сначала включать другие заголовки.

Рекомендация: Файлы заголовков должны быть автономными.

Исторические правила

Исторически сложилось так, что это был спорный спорный вопрос.

Файлы заголовков не должны быть вложенными. Поэтому пролог для файла заголовка должен описывать, какие другие заголовки должны быть #include d для того, чтобы заголовок был функциональным. В крайних случаях, когда большое количество файлов заголовков должно быть включено в несколько разных исходных файлов, допустимо поместить все обычные #include s в один файл include.

Это противоположность самоограничения.

Современные правила

Однако с тех пор мнение имеет тенденцию в противоположном направлении. Если исходный файл должен использовать средства, объявленные заголовком header.h , программист должен иметь возможность писать:

и (при условии наличия правильных путей поиска, установленных в командной строке), любые необходимые предварительные заголовки будут включены header.h без необходимости добавления дополнительных заголовков в исходный файл.

Это обеспечивает лучшую модульность исходного кода. Он также защищает источник от «догадки, почему этот заголовок был добавлен», головоломка, которая возникает после того, как код был изменен и взломан на десятилетие или два.

Стандарты кодирования космического полета NASA Goddard (GSFC) для C — один из самых современных стандартов, но сейчас его трудно отследить. В нем говорится, что заголовки должны быть автономными. Он также обеспечивает простой способ гарантировать, что заголовки являются автономными: файл реализации для заголовка должен включать заголовок в качестве первого заголовка. Если он не является самодостаточным, этот код не будет компилироваться.

Обоснование, данное GSFC, включает:

§2.1.1 Заголовок включает в себя обоснование

Этот стандарт требует, чтобы заголовок блока содержал инструкции #include для всех других заголовков, требуемых заголовком блока. Размещение #include для заголовка блока сначала в блоке устройства позволяет компилятору проверить, содержит ли заголовок все необходимые инструкции #include .

Альтернативный дизайн, не разрешенный этим стандартом, не допускает операторов #include в заголовках; все #includes производятся в файлах body. Заголовочные файлы блока должны содержать инструкции #ifdef, которые проверяют, что требуемые заголовки включены в правильный порядок.

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

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

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

Таким образом, самоограничение означает, что:

  • Если заголовку header.h нужен новый вложенный заголовок extra.h , вам не нужно проверять каждый исходный файл, который использует header.h чтобы узнать, нужно ли вам добавлять extra.h .
  • Если заголовку header.h больше не нужно включать определенный заголовок notneeded.h , вам не нужно проверять каждый исходный файл, который использует header.h чтобы увидеть, можете ли вы безопасно удалить notneeded.h (но см. notneeded.h Включить то, что вы используете» .
  • Вам не нужно устанавливать правильную последовательность для включения необходимых заголовков (что требует топологической сортировки для правильной работы).

Проверка самоограничения

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

Минимальность

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

Например, заголовок проекта не должен включать <stdio.h> если только один из функциональных интерфейсов не использует тип FILE * (или один из других типов, определенных исключительно в <stdio.h> ). Если интерфейс использует size_t , наименьший заголовок, который достаточно, <stddef.h> . Очевидно, что если включен другой заголовок, который определяет size_t , нет необходимости включать <stddef.h> .

Если заголовки минимальны, то это также приводит к минимуму времени компиляции.

Можно создать заголовки, единственной целью которых является включение множества других заголовков. Они редко оказываются хорошей идеей в долгосрочной перспективе, потому что для нескольких исходных файлов понадобятся все средства, описанные всеми заголовками. Например, можно было бы разработать <standard-ch> , который включает все стандартные заголовки C — с осторожностью, поскольку некоторые заголовки не всегда присутствуют. Однако очень немногие программы фактически используют средства <locale.h> или <tgmath.h> .

  • См. Также Как связать несколько файлов реализации в C?

Включить то, что вы используете (IWYU)

Проект Google Include What You Use , или IWYU, гарантирует, что исходные файлы включают все заголовки, используемые в коде.

Предположим, что исходный файл source.c содержит заголовок arbitrary.h который, в свою очередь, случайно включает freeloader.h , но исходный файл также явно и независимо использует средства из freeloader.h . Все хорошо начать. Затем в один прекрасный день arbitrary.h изменяется, поэтому его клиентам больше не нужны средства freeloader.h . Внезапно source.c прекращает компиляцию — поскольку он не соответствует критериям IWYU. Поскольку код в source.c явно использовал возможности freeloader.h , он должен был включить то, что он использует — в источнике также должен был быть явно #include "freeloader.h" . ( Идемпотентность гарантировала бы, что проблем не было.)

Философия IWYU максимизирует вероятность того, что код продолжает компилироваться даже при разумных изменениях, внесенных в интерфейсы. Очевидно, что если ваш код вызывает функцию, которая впоследствии удаляется из опубликованного интерфейса, никакая подготовка не может препятствовать изменениям. Вот почему, когда это возможно, избегаются изменения API-интерфейсов и почему существуют циклы отказов по нескольким выпускам и т. Д.

Это особая проблема в C ++, потому что стандартным заголовкам разрешено включать друг друга. Исходный файл file.cpp может включать один заголовок header1.h который на одной платформе включает другой заголовок header2.h . file.cpp может использовать средства header2.h . Первоначально это не было проблемой — код будет компилироваться, поскольку header1.h включает header2.h . На другой платформе или обновлении текущей платформы header1.h можно было бы пересмотреть, чтобы она больше не включала header2.h , а затем file.cpp прекратил компиляцию в результате.

IWYU обнаружит проблему и порекомендует, что header2.h будет включен непосредственно в file.cpp . Это обеспечило бы ее компиляцию. Аналогичные соображения также относятся к коду C.

Обозначение и сборник

В стандарте C говорится, что между обозначениями #include <header.h> и #include "header.h" очень мало различий.

[ #include <header.h> ] ищет последовательность определённых реализацией мест для заголовка, идентифицированного однозначно указанной последовательностью между разделителями < и > и вызывает замену этой директивы на все содержимое заголовка. Как указано места, или идентифицированный заголовок определяется реализацией.

[ #include "header.h" ] вызывает замену этой директивы всем содержимым исходного файла, идентифицированного указанной последовательностью между разделителями "…" . Именованный исходный файл выполняется поисковым способом. Если этот поиск не поддерживается или если поиск не выполняется, директива перерабатывается, как если бы она читала [ #include <header.h> ] .

Таким образом, двойная кавычка может выглядеть в большем количестве мест, чем форма с угловыми скобками. Стандарт указывает на пример, что стандартные заголовки должны быть включены в угловые скобки, даже если компиляция работает, если вместо этого использовать двойные кавычки. Аналогично, такие стандарты, как POSIX, используют формат с угловым скобкой — и вам тоже нужно. Зарезервировать заголовки с двойными кавычками для заголовков, определенных проектом. Для внешних заголовков (включая заголовки других проектов, на которые опирается ваш проект), наиболее подходящим является обозначение углового кронштейна.

Обратите внимание, что между #include и заголовком должно быть пробел, хотя компиляторы не будут там места. Пробелы дешевы.

В ряде проектов используются такие обозначения, как:

Вы должны подумать, следует ли использовать этот контроль пространства имен в вашем проекте (это, скорее всего, хорошая идея). Вы должны избегать имен, используемых существующими проектами (в частности, как sys и linux будут плохими выборами).

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

Не используйте обозначения #include "../include/header.h" .

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

Следствие: вы не будете объявлять глобальные переменные в исходном файле — исходный файл будет содержать только определения.

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *