Поиск…


Вступление

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

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

Вступление

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

  • Idemopotence

    Если заголовочный файл несколько раз включается в блок трансляции (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. Первые строки без комментариев в файле заголовка должны иметь следующий вид:

#ifndef UNIQUE_ID_FOR_HEADER
#define UNIQUE_ID_FOR_HEADER

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

#endif /* UNIQUE_ID_FOR_HEADER */

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

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

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

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

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

#pragma once

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

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

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

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


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

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

Однажды на другое тысячелетие, AT & T Indian Hill C Стиль и стандарты кодирования заявили:

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

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

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

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

#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> .

Включить то, что вы используете (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 и заголовком должно быть пробел, хотя компиляторы не будут там места. Пробелы дешевы.

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

#include <openssl/ssl.h>
#include <sys/stat.h>
#include <linux/kernel.h>

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

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

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

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

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

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

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

Перекрестные ссылки



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow