2015-06-16 1 views
17

Я знаю, что это распространенный вопрос, но я до сих пор не могу полностью окунуться в него.Файл заголовка включен только один раз во всей программе?

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

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

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

+8

* «заголовочный файл (с включенными защитами) будет включаться только один раз в одну единицу перевода, но несколько раз во всем коде. ?"* Да. Не один раз в программу, он (не более) один раз на единицу перевода. – CoryKramer

+6

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

+0

Спасибо. Мне нравится этот сайт :) супер быстрые ответы и отличные ответы. – Engineer999

ответ

16

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

Стражник ...

#ifndef MY_HEADER_H 
#define MY_HEADER_H 

.... 

#endif 

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

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

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

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

+3

вы также можете указать нестандартную '# прагма один раз', стоит знать ее существование – dlavila

+3

@dlavila: Я не с какой-то целью. На это нельзя положиться, поэтому вам все равно придется охранять охранников. Кроме того, это может игнорировать библиотеки, такие как boost, которые намеренно используют несколько включений. (Я не всегда придерживаюсь стандарта, но ссылка однажды имеет слишком мало преимуществ и может вызвать большие проблемы). – Olaf

+4

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

5

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

+0

Что произойдет, если у нас есть определения функций в файле заголовка. Разве не будет много определений одной и той же функции во всей программе, которая не разрешена? – Engineer999

+0

@ Engineer999 Определения функций обычно не включаются в заголовочные файлы, за исключением встроенных функций – Random832

+0

Не являются ли функции неявно встроенными, если они определены внутри тела класса в файле заголовка (конечно, C++). Как с этим связаны множественные определения? – Engineer999

6

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

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

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

20

Это процесс:

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 не определен, и препроцессор будет оценивать содержимое в блоке с ifndef/endif. Во второй раз он пропустит этот блок. Таким образом обработанный вывод изменяет, и определения включены только один раз.

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

#pragma once 

// header contents 

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

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

Компиляция

компилятор генерирует машинный код с препроцессором C/C++.

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

Связывание

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

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

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

+0

Отличный ответ, но не стоит говорить: «Как правило, файлы заголовков включают в себя ** декларации **, а не фактические реализации». поскольку определения, как правило, являются синонимами реализаций в c/C++? –

+1

@BryanShaw, да, спасибо за помощь. –