Файл заголовка включен только один раз во всей программе?

Я знаю, что это общий вопрос, но я все еще не могу полностью обдумать его.

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

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

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

Ответы

Ответ 1

Это процесс:

source           header   source header header
   \           /        \   |      /   /
    \         /          \  |     /   /
  PREPROCESSOR            PREPROCESSOR
       |                      |
       V                      V
 preprocessed code      preprocessed code
       |                      |
    COMPILER               COMPILER
       |                      |
       V                      V
  object code              object code
             \            /
              \          /
               \        /
                 LINKER
                   | 
                   V
               executable

Препроцессирование

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

Если A включает B и C, а B включает C, вывод препроцессора для A будет включать обработанный текст C дважды.

Это проблема, так как это приведет к дублированию деклараций. Исправление состоит в том, чтобы использовать препроцессорные переменные, отслеживать, включен ли исходный код (aka head guard).

#ifndef EXAMPLE_H
#define EXAMPLE_H

// header contents

#endif

В первый раз EXAMPLE_H равен undefined, а препроцессор будет оценивать содержимое в блоке ifndef/endif. Во второй раз он пропустит этот блок. Таким образом, обработанный вывод изменяется, и определения включаются только один раз.

Это настолько распространено, что существует нестандартная директива, реализованная некоторыми компиляторами, которая короче и не требует выбора уникальной переменной препроцессора:

#pragma once

// header contents

Вы можете понять, насколько переносимым вы хотите свой код на C/С++ и какой защитник заголовка использовать.

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

Компиляция

Компилятор генерирует машинный код из вашего предварительно обработанного C/С++.

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

Связь

Компонент объединяет объектные файлы. Он соответствует определениям (аналогичным реализациям) со ссылками на таблицу символов.

Возможно, два объектных файла предоставляют определение, а компоновщик - один. Это происходит, если вы поместили исполняемый код в свои заголовки. Это обычно не происходит в C, но это происходит очень часто в С++ из-за шаблонов.

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

Ответ 2

"Заголовочный файл" фактически вставлен перед процессором перед началом компиляции. Подумайте об этом как о "замене" его директивы #include.

Охранник...

#ifndef MY_HEADER_H
#define MY_HEADER_H

....

#endif

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

Итак, если в заголовке есть определения генерации кода, они, конечно же, будут включены в объектный файл блока компиляции (он же "модуль" ). Если один и тот же заголовок #include выделен в нескольких модулях, они будут отображаться несколько раз.

Для static определений это вообще не проблема, поскольку они не будут видны за пределами модуля (aka scope). Для глобальных глобальных определений это отличается и приведет к ошибке "множественных определений".

Примечание: это в основном для C. Для С++ существуют значительные различия, так как классы и т.д. Добавляют дополнительную сложность тому, что/когда допускается несколько глобальных объектов.

Ответ 3

Заголовочный файл с соответствующим include guard > будет включен только один раз для единицы перевода. Строго говоря, он может быть включен несколько раз, но части между препроцессором #ifndef и #endif будут пропущены при последующих включениях. Если все сделано правильно, это должно быть все (или большинство) файла.

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

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

Ответ 4

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