Что такое guard block в заголовочном файле c
Имена программных элементов, таких как переменные, функции, классы и т. д., должны быть объявлены до их использования. Например, вы не можете просто написать x = 42 без первого объявления "x".
Объявление указывает компилятору, является ли элемент элементом int , функцией double , а class также какой-либо другой вещью. Кроме того, каждое имя должно быть объявлено (прямо или косвенно) в каждом CPP-файле, в котором он используется. При компиляции программы каждый CPP-файл компилируется независимо в единицу компиляции. Компилятор не знает, какие имена объявляются в других единицах компиляции. Это означает, что если вы определяете класс или функцию или глобальную переменную, необходимо предоставить объявление этой вещи в каждом дополнительном CPP-файле, который использует его. Каждое объявление этой вещи должно быть точно идентичным во всех файлах. Небольшое несоответствие приведет к ошибкам или непреднамеренное поведение, когда компоновщик пытается объединить все блоки компиляции в одну программу.
В Visual Studio 2019 г. функция модулей C++20 появилась как улучшение и итоговая замена файлов заголовков. Дополнительные сведения см. в разделе "Обзор модулей" в C++.
4 ответы
У меня в голове два сценария:
- когда вы хотите включить / выключить возможности отладки (например, как assert.h работает)
- для 'x-macro' Тип функциональности, при которой у вас есть включаемый файл, выполняет две части проблемы, например, определение перечисления, а затем определение массива строковых имен, соответствующих перечислениям
Создан 22 июля '11, 17:07
Один случай, когда вы хотите включить один и тот же файл несколько раз с разными параметрами. В этом случае включаемый файл будет действовать как своего рода шаблон. Примером являются зачистные на Dosbox.
Создан 22 июля '11, 17:07
В наших проектах мы никогда не используем include guard. Мы используем include antiguard:
Потому что, если вы повторно включили тот же заголовок, вы написали неправильный код или работали с неправильной архитектурой.
Возможно, вы не совсем понимаете проблему повторного включения заголовков. Заголовок включения в два разных файла .c / .cpp не является повторным включением. Включить заголовок дважды в один файл .cpp. Если мы включим в stdafx.h нам не нужно включать его поверх заголовков наших проектов. Потому что заголовки не компилируются, только .cpp. Include guard - это своего рода защита от дурака, для тех, кто не хочет думать об архитектуре приложения. С помощью include guard мы не можем увидеть реальную иерархию слоев приложения из-за многократного перекрестного включения одних и тех же заголовков друг в друга. - Юрий Кравченко
Я понимаю, что проекты должны разрабатываться таким образом, чтобы не было циклических зависимостей. Кроме того, я считаю, что включение одного и того же заголовка несколько раз имеет логический смысл. Например, если мой "application.cpp" определяет вектор, я должен включить , даже если я также включаю свой файл "algorithmms.h", который также включает по какой-то причине. Некоторые руководства по стилю, такие как руководство Google, предлагают вам включать все заголовки используемых вами типов данных. - ChronoTrigger
И если я хочу выяснить иерархию слоев путем анализа исходного кода - я не могу. И если я хочу изменить в "myvector.h" в проекте, я должен искать все файлы исходного кода, а не изменять только одну строку в stdafx.h. И если я хочу быть уверен в эффективности использования предварительно скомпилированного заголовка, я не могу. Мне не нравятся такие руководства по стилю, извините. - Юрий Кравченко
Аналогично, с вашим стилем, если "алгоритмы.h" изменен и теперь он включает "myvector.h", приложение не будет компилироваться, потому что "application.cpp" не может найти . Я думаю, что у обеих наших методик есть свои плюсы и минусы. - ChronoTrigger
Защита включения используется для того, чтобы включаемый файл мог быть включен несколько раз в одну единицу компиляции, не приводя к дублированию объявлений.
Не используйте include guards, когда файл должен быть включен несколько раз в одну единицу компиляции, и это не приводит к дублированию объявлений.
Мы все знаем, когда использовать include guard, но когда мы не будем использовать его в нашем проекте?
Недавно я увидел проект с компиляцией микширования (CUDA + GCC), один заголовочный файл (файл CUDA) намеренно оставлен без включения защиты. Мне просто интересно.
Я думаю, что всегда безопасно использовать его, а не искать ситуацию, в которой это было бы бесполезно.
Для обычных заголовков пользовательских библиотек всегда добавляйте include guard. Есть некоторые извращенные уловки, которые вы можете использовать при повторном включении неохраняемых заголовков, но вам никогда не придется делать это как уважаемый семьянин (или жена).
Синтаксис
Включение охранников
Remarks
Параметр /guard:cf указывает компилятору на необходимость анализа потока управления для целевых объектов косвенного вызова во время компиляции и последующей вставки кода для проверки целевых объектов во время выполнения. По умолчанию /guard:cf выключен и должен быть явным образом включен. Для отключения явным образом этого параметра используйте /guard:cf-.
Visual Studio 2017 и более поздних версий: этот параметр добавляет условия для switch инструкций, создающих таблицы переходов.
Если указан параметр /guard:cf защиты потока управления (CFG), компилятор и компоновщик вставляют дополнительные проверки безопасности среды выполнения для обнаружения компрометации вашего кода. Во время компиляции и компоновки анализируются все косвенные вызовы в коде, чтобы найти любое расположение, доступное коду, когда он работает правильно. Эти сведения хранятся в дополнительных структурах в заголовках двоичных файлов. Компилятор также вставляет проверку перед каждым косвенным вызовом в коде, позволяющую убедиться, что целевой объект — одно из проверенных расположений. Если проверка завершается сбоем во время выполнения в операционной системе, которая поддерживает CFG, то операционная система закрывает программу.
Типичная атака на программное обеспечение использует ошибки при обработке экстремальных или непредвиденных входных данных. Аккуратно созданные входные данные для приложения могут перезаписать расположение, которое содержит указатель на исполняемый код. Это можно использовать для перенаправления потока управления в код, контролируемый злоумышленником. Проверки среды выполнения CFG не исправляют поврежденные данные в исполняемом файле. Вместо этого они усложняют злоумышленнику возможность их применения для выполнения произвольного кода. CFG представляет собой средство уменьшения угроз, предотвращающее вызовы в расположения, отличные от точек входа функций в коде. Аналогично тому, как предотвращение выполнения данных (DEP), проверки стека /GS , а также /DynamicBase и /highentropyva (ASLR), понижает вероятность того, что ваш код станет вектором эксплойтов.
Для построения кода, использующего метод предотвращения эксплойта CFG, параметр /guard:cf должен передаваться и в компилятор, и в компоновщик. Если двоичный файл создается с помощью одиночной команды cl , компилятор передает параметр в компоновщик. Если компиляция и компоновка выполняются отдельно, параметр должен быть указан в командах компилятора и компоновщика. Также требуется параметр компоновщика /DYNAMICBASE. Чтобы убедиться, что двоичный файл имеет данные CFG, используйте команду dumpbin /headers /loadconfig . Двоичные файлы с включенной функцией CFG имеют функцию Guard в списке характеристик EXE или DLL, а флаги защиты включают в себя CF Instrumented и FID table present .
Параметр /guard:cf несовместим с /ZI (Изменить и продолжить отладочную информацию) или /clr (компиляция CLR).
Код, скомпилированный с помощью /guard:cf , может быть связан с библиотеками и файлами объектов, которые не были скомпилированы с помощью параметра. Защиту CFG имеет только этот код, когда он также связан с помощью параметра /guard:cf и выполняется в операционной системе, которая поддерживает CFG. Так как код, скомпилированный без параметра, не будет останавливать атаки, этот параметр рекомендуется использовать во всем компилируемом коде. Проверки CFG расходуют некоторые ресурсы среды выполнения, но функция анализа компилятора пытается оптимизировать проверки на косвенных переходах, которые могут быть установлены как безопасные.
Пример файла заголовка
В следующем примере показаны различные типы объявлений и определений, разрешенных в файле заголовка:
Я пытаюсь создать класс C ++, используя IDE Code :: Blocks, и есть поле под названием «Блок защиты». Я провел поиск и не смог найти никакой полезной информации. Для чего это поле? Спасибо.
2 ответы
Блоки защиты используются для защиты от многократного включения файла заголовка одним и тем же модулем компиляции (файл c ++). Выглядят они примерно так:
Если вы включите один и тот же файл в несколько файлов, вы получите множественную ошибку определения. Использование защиты включения не обязательно в небольших проектах, но становится критичным в любых средних и крупных проектах. Я обычно использую его в любых файлах заголовков, которые я пишу.
ответ дан 20 окт '11, 21:10
Спасибо за быстрый ответ. Еще одна вещь: я вижу, что файл заголовка созданного мной класса включен в сам файл класса .cpp, что для меня не имеет смысла. Я не уверен, почему Code :: Blocks делает это = / - Chrislgarry
@Quicksort заголовок включен в .cpp, потому что типы и константы определены в заголовке, а классы объявлены там. - Дэвид Хеффернан
Спасибо, Дэвид. Боже, ты быстр с ответами. Таким образом, файл заголовка моего класса («Deck.cpp») просто предоставляет компилятору список всех объявлений, которые, возможно, могут возникнуть в main.cpp, без включения каких-либо деталей реализации, поэтому нет ошибки компиляции и, если элемент действительно создается в основном файле, компилятор затем ищет файл Deck.cpp, связанный с файлом заголовка, и вставляет детали реализации в main.cpp? - Chrislgarry
@Quicksort Хм. Это не совсем так, но довольно сложно вдаваться в кровавые подробности в комментариях. В основном файлы заголовков полезны тем, что они позволяют вам вызывать функции в других единицах перевода без необходимости записывать объявления более одного раза. Ключевым моментом здесь является понимание разницы между декларацией и определением. - Дэвид Хеффернан
OK. Я сделаю еще несколько поисков. Спасибо за помощь! - Chrislgarry
Защитные блоки используются для предотвращения многократного включения файла заголовка в одну единицу трансляции. Это часто является проблемой, когда вы включаете несколько файлов заголовков, которые, в свою очередь, включают общие стандартные файлы заголовков.
Проблема с несколькими включениями одного и того же файла заключается в том, что это приводит к тому, что один и тот же символ определяется несколько раз.
ответ дан 20 окт '11, 21:10
Это зависит. Если вы параноидально относитесь к переносимости, вы не будете ее использовать, потому что она не переносима, то есть не поддерживается стандартом. Но все популярные компиляторы реализуют это, поэтому на практике это не проблема. Если однажды вы наткнетесь на кого-то, чего нет, тогда достаточно легко переключиться на охранников старого стиля. - Дэвид Хеффернан
Не тот ответ, который вы ищете? Просмотрите другие вопросы с метками c++ ide header header-files codeblocks or задайте свой вопрос.
Все мы знаем, когда использовать include guard, но когда мы не должны использовать его в нашем проекте?
Недавно я видел проект со смешанной компиляцией (CUDA + GCC), один файл заголовка (файл CUDA) намеренно оставлен без защиты включения. Мне это просто любопытно.
задан 22 июля '11, 13:07
Я думаю, что всегда безопасно использовать его, а не искать ситуации, в которых он не будет полезен. - Mahesh
Для заголовков обычных пользовательских библиотек всегда добавляйте защиты включения. Есть несколько извращенных уловок, которые можно проделать с помощью повторного включения незащищенных заголовков, но вам никогда не придется делать это как уважаемый семьянин (или жена). - Kerrek SB
Показать более подробную информацию о файле заголовка? Мне тоже любопытно. - Stan
@KerrekSB Поделитесь, пожалуйста, своими хитростями! - Santropedro
@Santropedro: есть способы сгенерировать код, включив какой-либо файл, который вызывает макросы, когда вы действительно хотите, чтобы файл был включен каждый раз. Макросы иногда называют X-макросы, и заголовки «x-заголовки» (потому что их имя файла начинается с x_ or xx_ в некоторых условностях); смотрите также здесь. Boost.Preprocessor показывает, как довести это до крайности. - Kerrek SB
Что нужно поместить в файл заголовка
Так как файл заголовка может быть включен несколькими файлами, он не может содержать определения, которые могут создавать несколько определений одного и того же имени. Следующие действия не допускаются или считаются очень плохой практикой.
- встроенные определения типов в пространстве имен или глобальной области
- Определения функций, отличных от встроенных
- Определения переменных, отличных от const
- агрегатные определения
- безымянные пространства имен
- Директивы using
using Использование директивы не обязательно приведет к ошибке, но может вызвать проблему, так как она добавляет пространство имен в область в каждом CPP-файле, который прямо или косвенно включает этот заголовок.
4 ответа
С головы: есть 2 сценария:
- когда вы хотите включить/отключить возможности отладки (как работает assert.h )
- для 'x-macro' тип функциональности, в котором у вас есть файл include, выполняет две части проблемы, такие как определение перечисления, определяющее массив строковых имен, соответствующих перечислениям
В наших проектах мы никогда не используем include guard. Мы используем include antiguard:
Потому что, если вы повторно включили один и тот же заголовок - вы написали неправильный код или работали с неправильной архитектурой.
Может быть, вы не совсем понимаете проблему повторного включения заголовков. Включение заголовка в два разных файла .c / .cpp не является повторным включением. Включите заголовок дважды в один файл .cpp. Если мы добавим
Я понимаю, что проекты должны быть разработаны таким образом, чтобы не было круговых зависимостей. Кроме того, я думаю, что включение одного и того же заголовка несколько раз логично. Например, если мой «application.cpp» определяет вектор, я должен включить
И если я хочу выяснить иерархию слоев посредством анализа исходного кода - я не могу. И если я хочу изменить
Аналогично, с вашим стилем, если «gorithms.h »изменен и теперь включает« myvector.h », приложение не будет компилироваться, потому что« application.cpp »не может найти
Хм, это интересно. Я мог бы sed это в свои заголовки в моем текущем проекте, чтобы посмотреть, делаю ли я что-нибудь избыточное или почти круглое. Теоретически, как вы сказали, для небиблиотечных / шаблонных заголовков повторные включения не должны происходить.
Один случай, когда вы хотите добавить один и тот же файл несколько раз с разными параметрами. В этом случае включенный файл будет действовать как своего рода шаблон. Примером может служить scalers в Dosbox.
Включение создания компилятором проверок безопасности для защиты потока управления.
Установка данного параметра компилятора в среде разработки Visual Studio
Перейдите на страницу свойств Свойства конфигурации>C/C++>Создание кода.
Выберите свойство Защита потока управления .
В раскрывающемся списке нажмите Да , чтобы включить защиту потока управления, или Нет , чтобы отключить ее.
Пример
В следующем примере показан распространенный способ объявления класса и последующего использования его в другом исходном файле. Начнем с файла заголовка. my_class.h Он содержит определение класса, но обратите внимание, что определение является неполным; Функция-член do_something не определена:
В файле реализации можно использовать using оператор, чтобы избежать необходимости указывать все упоминания "my_class" или "cout" с "N::" или "std::". Не помещайте using инструкции в файлы заголовков!
После завершения компиляции каждого CPP-файла в OBJ-файлы он передает OBJ-файлы компоновщику. При слиянии файлов объектов компоновщик находит только одно определение для my_class; он находится в OBJ-файле, создаваемом для my_class.cpp, и сборка завершается успешно.
Читайте также: