Что такое защитный блок в заголовочном файле c

Обновлено: 02.07.2024

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

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

Рекомендация¶

Добавьте в файл одну из следующих форм защиты заголовка (где HEADER_NAME – это уникальный идентификатор, полученный из имени файла):

Пример¶

Автор следующего заголовка пытался использовать защиту заголовка, но допустил опечатку:

В подобных сценариях MY_HAEDER_H следует заменить на MY_HEADER_H (обратите внимание на переставленные A и E ):

Пример¶

Следующий заголовок может показаться защищенным, но не строго соответствует правилам:

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

Пример¶

Следующий заголовок менялся со временем, и разные авторы добавляли объявления функций в разные места:

Ссылки¶

Правила AV 27 и 35, Стандарты кодирования C++ для истребителей совместных ударов. Корпорация Локхид Мартин, 2005 г.

В уроке 2.6 -- Предварительные объявления и определения мы отметили, что идентификатор переменной или функции может иметь только одно определение (правило одного определения). Таким образом, программа, определяющая идентификатор переменной более одного раза, вызовет ошибку компиляции:

Аналогичным образом программы, определяющие функцию более одного раза, также вызовут ошибку компиляции:

Рассмотрите следующий академический пример:

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

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

Даже заголовки стандартной библиотеки используют защиту заголовков. Если бы вы взглянули на заголовочный файл iostream из Visual Studio, вы бы увидели:

Для опытных читателей

В больших программах можно иметь два отдельных файла заголовков (включенных из разных каталогов), которые в конечном итоге имеют одно и то же имя файла (например, каталогA\config.h и каталогB\config.h). Если для включения защиты используется только имя файла (например, CONFIG_H), эти два файла могут в конечном итоге использовать одно и то же имя защиты. Если это произойдет, любой файл, который включает (прямо или косвенно) оба файла config.h, не получит содержимое включаемого файла, который будет включен вторым. Это, вероятно, вызовет ошибку компиляции.

Из-за возможности конфликтов имен сторожей многие разработчики рекомендуют использовать более сложные/уникальные имена в защитниках заголовков. Некоторые хорошие предложения относятся к соглашению об именах

_ _H , _ _H или _ _H

Обновление нашего предыдущего примера с помощью защиты заголовков

Вернемся к примеру square.h, используя square.h с защитой заголовка. Для удобства мы также добавим защиту заголовков в geometry.h.

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

Как видно из примера, второе включение содержимого square.h (из geometry.h) игнорируется, поскольку SQUARE_H был уже определен с первого включения. Поэтому функция getSquareSides включается только один раз.

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

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

Обратите внимание, что square.h включен как из main.cpp, так и из square.cpp. Это означает, что содержимое square.h будет включено один раз в square.cpp и один раз в main.cpp.

Давайте рассмотрим, почему это происходит более подробно. Когда square.h включается из square.cpp, SQUARE_H определяется до конца square.cpp. Это определение предотвращает повторное включение square.h в square.cpp (что является целью защиты заголовков). Однако после завершения square.cpp SQUARE_H больше не считается определенным. Это означает, что когда препроцессор работает в main.cpp, SQUARE_H изначально не определен в main.cpp.

Конечным результатом является то, что и square.cpp, и main.cpp получают копию определения getSquareSides. Эта программа будет скомпилирована, но компоновщик будет жаловаться на то, что ваша программа имеет несколько определений для идентификатора getSquareSides!

Лучший способ обойти эту проблему — просто поместить определение функции в один из файлов .cpp, чтобы заголовок содержал только предварительное объявление:

Теперь, когда программа скомпилирована, функция getSquareSides будет иметь только одно определение (через square.cpp), поэтому компоновщик доволен. Файл main.cpp может вызывать эту функцию (даже несмотря на то, что он находится в файле square.cpp), поскольку он включает файл square.h, который предварительное объявление функции (компоновщик свяжет вызов getSquareSides из main.cpp с определением getSquareSides в square .cpp).

Можем ли мы просто избежать определений в файлах заголовков?

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

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

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

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

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

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

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

Практически каждый заголовочный файл должен следовать идиоме include guard:

мой-header-file.h

header-1.h

header-2.h

main.c

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

header-1.h

header-2.h

main.c

Затем это расширится до:

Когда компилятор достигает второго включения header-1.h, HEADER_1_H уже был определен предыдущим включением. Следовательно, это сводится к следующему:

Таким образом, ошибки компиляции нет.

Примечание. Существует несколько различных соглашений по именованию средств защиты заголовков. Некоторым людям нравится называть его HEADER_2_H_ , некоторые включают имя проекта, например MY_PROJECT_HEADER_2_H . Важно убедиться, что соглашение, которому вы следуете, делает так, что каждый файл в вашем проекте имеет уникальную защиту заголовка.

Если бы детали структуры не были включены в заголовок, объявленный тип был бы неполным или непрозрачным. Такие типы могут быть полезны, скрывая детали реализации от пользователей функций.Для многих целей тип FILE в стандартной библиотеке C можно рассматривать как непрозрачный тип (хотя обычно он не является непрозрачным, так что макрореализации стандартных функций ввода-вывода могут использовать внутренности структуры). В этом случае header-1.h может содержать:

Обратите внимание, что структура должна иметь имя тега (здесь MyStruct — это пространство имен тегов, отдельное от пространства имен обычных идентификаторов имени typedef MyStruct ), и что < … >опущено. Это говорит о том, что "существует структура типа struct MyStruct и для нее есть псевдоним MyStruct".

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

Если вы используете C11, вы можете повторить структуру typedef MyStruct MyStruct; объявление, не вызывая ошибки компиляции, но более ранние версии C выдавали ошибку. Следовательно, по-прежнему лучше использовать идиому include guard, хотя в этом примере она была бы необязательной, если бы код компилировался только компиляторами, поддерживающими C11.

мой-header-file.h

Практически каждый заголовочный файл должен следовать идиоме include guard:

мой-header-file.h

header-1.h

header-2.h

main.c

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

header-1.h

header-2.h

main.c

Затем это расширится до:

Когда компилятор достигает второго включения header-1.h, HEADER_1_H уже был определен предыдущим включением. Следовательно, это сводится к следующему:

Таким образом, ошибки компиляции нет.

Примечание. Существует несколько различных соглашений по именованию средств защиты заголовков. Некоторым людям нравится называть его HEADER_2_H_ , некоторые включают имя проекта, например MY_PROJECT_HEADER_2_H . Важно убедиться, что соглашение, которому вы следуете, делает так, что каждый файл в вашем проекте имеет уникальную защиту заголовка.

Если бы детали структуры не были включены в заголовок, объявленный тип был бы неполным или непрозрачным. Такие типы могут быть полезны, скрывая детали реализации от пользователей функций. Для многих целей тип FILE в стандартной библиотеке C можно рассматривать как непрозрачный тип (хотя обычно он не является непрозрачным, так что макрореализации стандартных функций ввода-вывода могут использовать внутренности структуры). В этом случае header-1.h может содержать:

Обратите внимание, что структура должна иметь имя тега (здесь MyStruct — это пространство имен тегов, отдельное от пространства имен обычных идентификаторов имени typedef MyStruct ), и что < … >опущено. Это говорит о том, что "существует структура типа struct MyStruct и для нее есть псевдоним MyStruct".

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

Если вы используете C11, вы можете повторить структуру typedef MyStruct MyStruct; объявление, не вызывая ошибки компиляции, но более ранние версии C выдавали ошибку. Следовательно, по-прежнему лучше использовать идиому include guard, хотя в этом примере она была бы необязательной, если бы код компилировался только компиляторами, поддерживающими C11.

мой-header-file.h

В уроке 2.6 -- Предварительные объявления и определения мы отметили, что идентификатор переменной или функции может иметь только одно определение (правило одного определения). Таким образом, программа, определяющая идентификатор переменной более одного раза, вызовет ошибку компиляции:

Аналогичным образом программы, определяющие функцию более одного раза, также вызовут ошибку компиляции:

Рассмотрите следующий академический пример:

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

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

Даже заголовки стандартной библиотеки используют защиту заголовков. Если бы вы взглянули на заголовочный файл iostream из Visual Studio, вы бы увидели:

Для опытных читателей

В больших программах можно иметь два отдельных файла заголовков (включенных из разных каталогов), которые в конечном итоге имеют одно и то же имя файла (например, каталогA\config.h и каталогB\config.h). Если для включения защиты используется только имя файла (например, CONFIG_H), эти два файла могут в конечном итоге использовать одно и то же имя защиты. Если это произойдет, любой файл, который включает (прямо или косвенно) оба файла config.h файлы не получат содержимое включаемого файла, который будет включен вторым. Это, вероятно, вызовет ошибку компиляции.

Из-за возможности конфликтов имен сторожей многие разработчики рекомендуют использовать более сложные/уникальные имена в защитниках заголовков. Некоторые хорошие предложения относятся к соглашению об именах

_ _H , _ _H или _ _H

Обновление нашего предыдущего примера с помощью защиты заголовков

Вернемся к примеру square.h, используя square.h с защитой заголовка. Для удобства мы также добавим защиту заголовков в geometry.h.

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

Как видно из примера, второе включение содержимого square.h (из geometry.h) игнорируется, поскольку SQUARE_H был уже определен с первого включения. Поэтому функция getSquareSides включается только один раз.

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

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

Обратите внимание, что square.h включен как из main.cpp, так и из square.cpp. Это означает, что содержимое square.h будет включено один раз в square.cpp и один раз в main.cpp.

Давайте рассмотрим, почему это происходит более подробно. Когда square.h включается из square.cpp, SQUARE_H определяется до конца square.cpp. Это определение предотвращает повторное включение square.h в square.cpp (что является целью защиты заголовков). Однако после завершения square.cpp SQUARE_H больше не считается определенным. Это означает, что когда препроцессор работает в main.cpp, SQUARE_H изначально не определен в main.cpp.

Конечным результатом является то, что и square.cpp, и main.cpp получают копию определения getSquareSides. Эта программа будет скомпилирована, но компоновщик будет жаловаться на то, что ваша программа имеет несколько определений для идентификатора getSquareSides!

Лучший способ обойти эту проблему — просто поместить определение функции в один из файлов .cpp, чтобы заголовок содержал только предварительное объявление:

Теперь, когда программа скомпилирована, функция getSquareSides будет иметь только одно определение (через square.cpp), поэтому компоновщик доволен. Файл main.cpp может вызывать эту функцию (даже несмотря на то, что он находится в файле square.cpp), поскольку он включает файл square.h, который предварительное объявление функции (компоновщик свяжет вызов getSquareSides из main.cpp с определением getSquareSides в square .cpp).

Можем ли мы просто избежать определений в файлах заголовков?

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

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

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

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

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

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

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

Читайте также: