Включить охранников, как определено Вот, используются для предотвращения загрузки одного и того же кода дважды при компиляции.
Почему мой компилятор (GCC) не может обнаружить, что он загружает один и тот же код дважды и имеет разумное поведение по умолчанию?
Просто потому, что вы, возможно, хотели, чтобы компилятор загружал этот файл дважды.
Помните, что #include
просто загружает файл и помещает его содержимое вместо директивы. Этот файл может быть файлом заголовка, но может быть полезным и часто используемым фрагментом исходного кода.
Большинство современных компиляторов реагируют на #pragma once
делать именно то, что вы хотите, чтобы они. Помните, однако, что это расширение компилятора, не включенное в спецификацию языка, и, как правило, стоит придерживаться включения защиты — вы будете уверены, что оно работает на каждом компиляторе и при любых обстоятельствах.
Почему мой компилятор (GCC) не может обнаружить, что он загружает один и тот же код дважды
Это может (или, педантично, препроцессор, который имеет дело с включением заголовка, может). Вместо использования include guard, вы можете использовать нестандартное, но широко поддерживаемое расширение
#pragma once
чтобы указать, что этот заголовок должен быть включен только один раз.
и иметь разумное поведение по умолчанию?
Язык не определяет это поведение по умолчанию, в основном потому, что язык восходит к тем временам, когда отслеживание включенных заголовков может быть чрезмерно дорогим, и отчасти потому, что иногда вы хотите включить заголовок более одного раза. Например, стандарт <assert.h>
заголовок может быть включен с или без NDEBUG
определены, чтобы изменить поведение assert
макро.
Потому что есть причудливые крайние случаи, когда повторное включение файла полезно.
Придуманный уродливый пример: предположим, у вас был #include
файл mymin.h
как это:
// mymin.h : ugly "pseudo-template" hack
MINTYPE min(MINTYPE a, MINTYPE b)
{
return (a < b) ? a : b;
}
Затем вы можете сделать что-то вроде этого:
#define MINTYPE int
#include "mymin.h"
#define MINTYPE double
#include "mymin.h"
Теперь у вас есть две перегрузки min
для разных типов, и хороший кандидат на http://thedailywtf.com/. Кому нужны шаблоны? 😉
Обратите внимание, что многие современные препроцессоры поддерживают #pragma once
, что является гораздо более хорошим способом достижения того же эффекта, что и охрана. Однако это, к сожалению, нестандартно.
Почему мой компилятор (GCC) не может обнаружить, что он загружает один и тот же код дважды и имеет разумное поведение по умолчанию?
Потому что не компилятор выполняет обработку включения. Это делается препроцессором, который по сути является механизмом преобразования текста. А для механизма преобразования текста может иметь смысл, если одно и то же включение появляется несколько раз при обработке фрагмента текста.
Давайте на мгновение погрузимся в это: компилятор не обрабатывает #include
s. Это то, что делает невозможным принятие разумных решений по переопределению символов компилятором.
Другие языки реализуют модули как часть языка, и в этих языках вещи не обрабатываются как подстановка текста, и компилятор фактически знает о семантике импорта.
Включите предохранители, защищающие от переопределения символов и включающие одни и те же файлы несколько раз.
Компилятору нужен этот механизм, потому что по понятным причинам он не включает механизм для анализа и принятия решения о том, какую версию кода рассмотреть. Подумайте, что произойдет, если одна и та же сигнатура функции в двух разных заголовочных файлах отличается только типом возврата.
Предполагая, что содержимое точно такое же, только если оно включено из нескольких заголовков, компилятору потребуются дополнительные вычислительные мощности и память для отслеживания уже включенного кода.
Так что это было бы подвержено ошибкам и неэффективно
Почему мой компилятор (GCC) не может обнаружить, что он загружает один и тот же код дважды и имеет разумное поведение по умолчанию?
Потому что тогда это не будет компилятор Си. Язык указан так, что #include
создает текстовое включение, и выполнение чего-то отличного от спецификации нарушило бы допустимый код.
Очевидный последующий вопрос: «Можем ли мы изменить стандарт С?» все еще должен найти какой-то способ избежать такой же поломки существующего действующего кода.
Одна вещь компилятор мог законно сделать, это испустить предупреждение когда непустой (после обработки #ifdef
и тому подобное) файл многократно включается без какого-либо указания на его преднамеренность. Если вы достаточно мотивированы, возможно, вы могли бы подготовить подходящий патч для ваших любимых компиляторов?
Кстати, вы обнаружите, что проблема становится очень сложной, как только вы придумаете хорошее и надежное определение «того же кода».
Даже если компилятор решит это сделать, ему необходимо отслеживать огромное количество файлов, и много раз (как это было указано в itwasntpete) компилятор не может отличить фактический код от файла заголовка.