Включение header файлов


Содержание

Заголовочные файлы и стражи включения C/C++

Любой заголовочный файл C/C++ должен иметь следующую структуру.

Например, заголовочный файл myFunctions.h, в котором размещены объявления функций f и g, будет выглядеть так:

Зачем нужны заголовочные файлы

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

Указанное требование тривиально выполняется в простых случаях наподобие (считается, что компилятор просматривает программу сверху-вниз один раз — что, вообще говоря, неверно для современных компиляторов):

Однако если программу немного изменить, то требование уже нарушится:

Хотя в остальном эта программа вполне корректна. В случаях, когда такой порядок определения функций (сначала main, потом f) по какой-то причине более предпочтителен, можно использовать специальный элемент языка, называемый объявлением или прототипом функции f:

Объявление в первой строке сообщает компилятору, что вызовы функции f в дальнейшем тексте предполагают, что эта функция принимает один аргумент типа int и возвращает void.

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

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

вставляет целиком содержимое указанного заголовочного файла в текущее место исходного файла перед компиляцией (например, в main.c, если в нём встретилась эта строка). После такой вставки в main.c окажутся все объявления функций из файла myFunctions.h и компилятор будет счастлив.

В чём смысл стражей включения

Вкратце, директивы ifndef-define-endif, которые обрамляют любой грамотно оформленный заголовочный файл, являются трюком препроцессора: они обеспечивают то, что любой заголовочный файл будет включён в любой исходный файл не более одного раза.

Более подробно. Каждому заг. файлу «вручную» ставится в соответствие некоторый «символ», обычно связанный с именем этого файла, чтобы обеспечить уникальность. В первой строке проверяется, был ли уже определён этот символ ранее, если да, то весь остальной текст игнорируется. Если нет, то этот символ определяется, а затем вставляется и весь остальной текст заголовочного файла. Последняя строка (endif) просто означает закрытие такого «условного оператора».

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

Заголовочный файл

Материал из Seo Wiki — Поисковая Оптимизация и Программирование

Заголовочный файл (иногда головной файл, англ. header file ), или подключаемый файл — в языках программирования Си и C++ файл, содержащий определения типов данных, структуры, прототипы функций, перечисления, макросы препроцессора. Имеет по умолчанию расширение .h; иногда для заголовочных файлов языка C++ используют расширение .hpp. Заголовочный файл используется путём включения его текста в данный файл директивой препроцессора #include .

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

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

Например, в языках программирования Си и C++ функции стандартной библиотеки по сложившейся традиции объявляют в заголовочных файлах. Подробнее смотрите Стандартная библиотека языка Си и Стандартная библиотека C++.

Преимущества использования

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

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

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

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

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

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

Альтернативные варианты

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

Включение header файлов

Файл заголовков — Заголовочный файл (иногда головной файл, англ. header file) (или подключаемый файл) в языках программирования Си и C++ файл, содержащий определения типов данных, структуры, прототипы функций, перечисления, макросы препроцессора. Имеет по… … Википедия

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

Файл — У этого термина существуют и другие значения, см. Файл (значения). Файл (англ. file) блок информации на внешнем запоминающем устройстве компьютера, имеющий определённое логическое представление (начиная от простой последовательности… … Википедия

Компьютерный файл — Файл (англ. file папка, скоросшиватель) концепция в вычислительной технике: сущность, позволяющая получить доступ к какому либо ресурсу вычислительной системы и обладающая рядом признаков: фиксированное имя (последовательность символов, число или … Википедия

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

Препроцессор Си — Препроцессор С/С++ программный инструмент, изменяющий код программы для последующей компиляции и сборки, используемый в языках программирования Си и его потомка C++. Этот препроцессор обеспечивает использование стандартного набора… … Википедия

stdint.h — Стандартная библиотека языка программирования С assert.h complex.h ctype.h errno.h fenv.h float.h inttypes.h iso646.h limits.h locale.h math.h setjmp.h signal.h stdarg.h stdbool.h stddef.h stdint … Википедия

errno.h — Стандартная библиотека языка программирования С assert.h complex.h ctype.h errno.h fenv.h float.h inttypes.h iso646.h limits.h locale.h math.h setjmp.h signal.h stdarg.h stdbool.h stddef.h … Википедия

Илон Маск рекомендует:  Center (центр) центрирование (нет в html 2 0)

Iso646.h — Стандартная библиотека языка программирования С assert.h complex.h ctype.h errno.h fenv.h float.h inttypes.h iso646.h limits.h locale.h math.h setjmp.h signal.h stdarg.h stdbool.h stddef.h stdint.h stdio.h … Википедия

Подключение заголовочного файла

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

Есть файл AZX, в котором прописана функция ask.

У вас есть заголовочный файл AZX.h для файла AZK, в котором описаны функция ask (задан прототип данной функции) и константа pi.

Подключаем заголовочный файл AZX_H к файлу AZX. Подключение всегда объявляется в начале файла специальной командой include.

После подключения AZX.h становится возможным использовать функцию ask из файла AZX (не из AZX.h, в нем лишь прототип данной функции).

Функция call вернет (s+j+(s+j+s)*pi)*pi, то есть использует функцию ask, не описываемую в данном файле. Так же в файле AZK возможно использовать константу pi, также объявленную в AZX.h.

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

Раздельная компиляция программ на C++

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

Термины

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

Исходный код — программа, написанная на языке программирования, в текстовом формате. А также текстовый файл, содержащий исходный код.

Компилятор — программа, выполняющая компиляцию (неожиданно! не правда ли?). На данный момент среди начинающих наиболее популярными компиляторами C/C++ являются GNU g++ (и его порты под различные ОС) и MS Visual Studio C++ различных версий. Подробнее см. в Википедии статьи: Компиляторы, Компиляторы C++.

Компиляция — преобразование исходного кода в объектный модуль.

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

Компоновщик (редактор связей, линкер, сборщик) — это программа, которая производит компоновку («линковку», «сборку»): принимает на вход один или несколько объектных модулей и собирает по ним исполнимый модуль. (подробности)

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

Препроцессор — программа для обработки текста. Может существовать как отдельная программа, так и быть интегрированной в компилятор. В любом случае, входные и выходные данные для препроцессора имеют текстовый формат. Препроцессор преобразует текст в соответствии с директивами препроцессора. Если текст не содержит директив препроцессора, то текст остаётся без изменений. Подробнее см. в Википедии: Препроцессор и Препроцессор Си.

IDE (англ. Integrated Development Environment) — интегрированная среда разработки. Программа (или комплекс программ), предназначенных для упрощения написания исходного кода, отладки, управления проектом, установки параметров компилятора, линкера, отладчика. Важно не путать IDE и компилятор. Как правило, компилятор самодостаточен. В состав IDE компилятор может не входить. С другой стороны с некоторыми IDE могут быть использованы различные компиляторы. (подробности)

Объявление — описание некой сущности: сигнатура функции, определение типа, описание внешней переменной, шаблон и т.п. Объявление уведомляет компилятор о её существовании и свойствах.

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

От исходного кода к исполняемому модулю

Создание исполняемого файла издавна производилось в три этапа: (1) обработка исходного кода препроцессором, (2) компиляция в объектный код и (3) компоновка объектных модулей, включая модули из объектных библиотек, в исполняемый файл. Это классическая схема для компилируемых языков. (Сейчас уже используются и другие схемы.)

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

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

Итак, допустим, у нас есть программа на C++ «Hello, World!»:

Сначала исходный код обрабатывается препроцессором. Препроцессор находит директиву #include , ищет файл iostream и заменяет директиву текстом из этого файла, попутно обрабатывая все директивы препроцессора во включаемом тексте.

Файл, указанный в директиве #include , в данном случае является заголовочным файлом (или «хеадером», «хидером», «заголовком»). Это обычный текстовый файл, содержащий объявления (объявления типов, прототипы функций, шаблоны, директивы препроцессора и т.п.). После текстуального включения заголовочного файла в текст программы (или модуля) становится возможным использование в тексте программы всего того, что описано в этом заголовочном файле.

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

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

Что бы получить нормальный исполняемый модуль, необходимо «разрешить» внешние ссылки. Т.е. добавить в исполняемый модуль код отсутствующих подпрограмм и настроить соответствующим образом все ссылки на этот код. Этим занимается компоновщик. Он анализирует таблицу внешних ссылок объектного модуля, ищет в объектных библиотеках недостающие модули, копирует их в исполняемый модуль и настраивает ссылки. После этого исполняемый модуль готов.

Библиотека (объектная библиотека) — это набор откомпилированных подпрограмм, собранных в единый файл определённой структуры. Подключение библиотеки происходит на этапе компоновки исполняемого файла из объектных файлов (т.е. из тех файлов, которые получаются в результате компиляции исходного текста программы).

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

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

Разделение текста программы на модули

Разделение исходного текста программы на несколько файлов становится необходимым по многим причинам:

  1. С большим текстом просто неудобно работать.
  2. Разделение программы на отдельные модули, которые решают конкретные подзадачи.
  3. Разделение программы на отдельные модули, с целью повторного использования этих модулей в других программах.
  4. Разделение интерфейса и реализации.

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

Как только мы решаем разделить исходный текст программы на несколько файлов, возникают две проблемы:

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

Первая проблема — чисто техническая. Она решается чтением руководств по компилятору и/или линкеру, утилите make или IDE. В самом худшем случае просто придётся проштудировать все эти руководства. Поэтому на решении этой проблемы мы останавливаться не будем.

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

Во-первых, нужно определить какие части программы выделить в отдельные модули. Что бы это получилось просто и естественно, программа должна быть правильно спроектирована. Как правильно спроектировать программу? — на эту тему написано много больших и правильных книг. Обязательно поищите и почитайте книги по методологии программирования — это очень полезно. А в качестве краткой рекомендации можно сказать: вся программа должна состоять из слабо связанных фрагментов. Тогда каждый такой фрагмент может быть естественным образом преобразован в отдельный модуль (единицу компиляции). Обратите внимание, что под «фрагментом» подразумевается не просто произвольный кусок кода, а функция, или группа логически связанных функций, или класс, или несколько тесно взаимодействующих классов.

Во-вторых, нужно определить интерфейсы для модулей. Здесь есть вполне чёткие правила.

Интерфейс и реализация

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

Таким образом, модуль состоит из двух файлов: заголовочного (интерфейс) и файла реализации.

Илон Маск рекомендует:  Хеш таблицы

Заголовочный файл, как правило, имеет расширение .h или .hpp, а файл реализации — .cpp для программ на C++ и .c, для программ на языке C. (Хотя в STL включаемые файлы вообще без расширений, но, по сути, они являются заголовочными файлами.)

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

Что может быть в заголовочном файле

Правило 1. Заголовочный файл может содержать только объявления. Заголовочный файл не должен содержать определения.

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

Единственным «исключением» из этого правила является определение метода в объявлении класса. Но по стандарту языка, если метод определён в объявлении класса, то для этого метода используется инлайновая подстановка. Поэтому, такое объявление не порождает исполняемого кода — код будет генерироваться компилятором только при вызове этого метода.

Аналогичная ситуация и с объявлением переменных-членов класса: код будет порождаться при создании экземпляра этого класса.

Правило 2. Заголовочный файл должен иметь механизм защиты от повторного включения.

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

Для препроцессора при первом включении заголовочного файла это выглядит так: поскольку условие «символ SYMBOL не определён» ( #ifndef SYMBOL ) истинно, определить символ SYMBOL ( #define SYMBOL ) и обработать все строки до директивы #endif . При повторном включении — так: поскольку условие » символ SYMBOL не определён» ( #ifndef SYMBOL ) ложно (символ был определён при первом включении), то пропустить всё до директивы #endif .

В качестве SYMBOL обычно применяют имя самого заголовочного файла в верхнем регистре, обрамлённое одинарными или сдвоенными подчерками. Например, для файла header.h традиционно используется #define __HEADER_H__ . Впрочем, символ может быть любым, но обязательно уникальным в рамках проекта.

В качестве альтернативного способа может применяться директива #pragma once . Однако преимущество первого способа в том, что он работает на любых компиляторах.

Заголовочный файл сам по себе не является единицей компиляции.

Что может быть в файле реализации

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

Правило 3. В файле реализации должна быть директива включения соответствующего заголовочного файла.

Понятно, что объявления, которые видны снаружи модуля, должны быть также доступны и внутри.

Правило также гарантирует соответствие между описанием и реализацией. При несовпадении, допустим, сигнатуры функции в объявлении и определении компилятор выдаст ошибку.

Правило 4. В файле реализации не должно быть объявлений, дублирующих объявления в соответствующем заголовочном файле.

При выполнении Правила 3, нарушение Правила 4 приведёт к ошибкам компиляции.

Практический пример

Допустим, у нас имеется следующая программа:

main.cpp

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

Итак, что у нас имеется?

  1. глобальная константа cint , которая используется и в классе, и в main ;
  2. глобальная переменная global_var , которая используется в функциях func1 , func2 и main ;
  3. глобальная переменная module_var , которая используется только в функциях func1 и func2 ;
  4. функции func1 и func2 ;
  5. класс CClass ;
  6. функция main .

Вроде вырисовываются три единицы компиляции: (1) функция main , (2) класс CClass и (3) функции func1 и func2 с глобальной переменной module_var , которая используется только в них.

Не совсем понятно, что делать с глобальной константой cint и глобальной переменной global_var . Первая тяготеет к классу CClass , вторая — к функциям func1 и func2 . Однако предположим, что планируется и эту константу, и эту переменную использовать ещё в каких-то, пока не написанных, модулях программы. Поэтому прибавится ещё одна единица компиляции.

Теперь пробуем разделить программу на модули.

Сначала, как наиболее связанные сущности (используются во многих местах программы), выносим глобальную константу cint и глобальную переменную global_var в отдельную единицу компиляции.

globals.h

globals.cpp

Обратите внимание, что глобальная переменная в заголовочном файле имеет спецификатор extern . При этом получается объявление переменной, а не её определение. Такое описание означает, что где-то существует переменная с таким именем и указанным типом. А определение этой переменной (с инициализацией) помещено в файл реализации. Константа описана в заголовочном файле.

С объявлением констант в заголовочном файле существует одна тонкость. Если константа тривиального типа, то её можно объявить в заголовочном файле. В противном случае она должна быть определена в файле реализации, а в заголовочном файле должно быть её объявление (аналогично, как для переменной). «Тривиальность» типа зависит от стандарта (см. описание того стандарта, который используется для написания программы).

Также обратите внимание (1) на защиту от повторного включения заголовочного файла и (2) на включение заголовочного файла в файле реализации.

Затем выносим в отдельный модуль функции func1 и func2 с глобальной переменной module_var . Получаем ещё два файла:

funcs.h

funcs.cpp

Поскольку переменная module_var используется только этими двумя функциями, её объявление в заголовочном файле отсутствует. Из этого модуля «на экспорт» идут только две функции.

В функциях используется переменная из другого модуля, поэтому необходимо добавить #include «globals.h» .

Наконец выносим в отдельный модуль класс CClass :

CClass.h

CClass.cpp

Обратите внимание на следующие моменты.

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

(2) Класс имеет статический член класса. Т.е. для всех экземпляров класса эта переменная будет общей. Её инициализация выполняется не в конструкторе, а в глобальной области модуля.

(3) В файл реализации добавлена директива #include «globals.h» для доступа к константе cint .

Классы практически всегда выделяются в отдельные единицы компиляции.

В файле main.cpp оставляем только функцию main . И добавляем необходимые директивы включения заголовочных файлов.

main.cpp

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

Типичные ошибки

Ошибка 1. Определение в заголовочном файле.

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

Ошибка 2. Отсутствие защиты от повторного включения заголовочного файла.

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

Ошибка 3. Несовпадение объявления в заголовочном файле и определения в файле реализации.

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

Ошибка 4. Отсутствие необходимой директивы #include .

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

Ошибка 5. Отсутствие необходимого модуля в проекте построения программы.

Вызывает ошибку компоновки «не определён символ . ». Обратите внимание, что имя символа в сообщении компоновщика почти всегда отличается от того, которое определено в программе: оно дополнено другими буквами, цифрами или знаками.

Ошибка 6. Зависимость от порядка включения заголовочных файлов.

Не совсем ошибка, но таких ситуаций следует избегать. Обычно сигнализирует либо об ошибках в проектировании программы, либо об ошибках при разделении исходного кода на модули.

Заключение

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

Файлы заголовков в си (код программы из нескольких файлов) — C , C++

Primary tabs

Forums:

Заголовочные файлы в языке си.

Вообще говоря, для использования сразу нескольких «источников» кода в процессе компиляции, вам необходимо просто «подключить» эти файлы к проекту (в среде Pelles C) — единственное с чем придётся разобраться — это с прототипами функций — эти

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

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

Илон Маск рекомендует:  Простой парсер статистики Liveinternet на PHP. Практика!

— размещается в файле заголовка ( имя которого заканчивается «точкой аш» = «.h«) =
file yourheadername.h=

этот файл (заголовка) — включается в тот файл, где находиться функция — точка входа в программу =
File Main.c

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

Как происходит компиляция

Зачем нужен .h файл?

Напомним, что на прошлой практике был создан проект, состоящий из трёх файлов:

  • 1.cpp — main (вызывает foo() )
  • 2.cpp — foo — определение
  • 2.h — foo — объявление

Заметим, что этот же код можно было написать, без использования 2.h .

Возникает законный вопрос: зачем был создан файл 2.h ?

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

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

Иногда может возникнуть ситуация, когда один заголовочный файл включён не один, а несколько раз. Посмотрим, к каким проблемам это может привести. Если в файле содержались только объявления (как в файле 2.h из примера), то ничего страшного не произойдёт (стол останется столом, сколько об этом не объявляй), но не всегда всё так удачно. Действительно, ведь заголовочный файл кроме объявлений файлов может содержать и определения (например констант или классов). Тогда его повторное включение приведет к ошибке компоновки. Чтобы этого избежать, все заголовочные файлы следует защищать от повторного включения.

Делается это так:

Здесь директива #ifndef указывает препроцессору, что участок кода до #endif следует компилировать только в случае, если объявления _2_H_ не было. Директива #define же указывает, что _2_H_ следует объявить.

Допустим, что есть файл 3.cpp .

Что произойдет при препроцессинге этого файла?

Вместо каждого #include «2.h» будет подставлено содержимое соответствующего файла. На момент первой подстановки _2_H_ еще не определено, по этому произойдут подстановка объявления функции и объявление _2_H_ . На момент же, когда препроцессор перейдет ко второму включению, _2_H_ уже определено, и потому подстановка выполнена не будет.

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

Подробнее о препроцессоре

Препроцессор — весьма мощное средство, и в языке C он использовался весьма широко.

Возьмем для примера функцию, вычисляющую максимум:

В языке C нет перегрузки функций.

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

  • Происходит текстовая подстановка в макрос. (Результат работы программы будет 2, а не 1)
  • В случае если не все параметры заключены в скобки, возможны неожиданности с приоритетами операций.

Сборка

Вспомним, как происходит процесс сборки:

Из этой картинки хорошо видно, зачем нужен Makefile . Процесс сборки весьма громоздок, причем большая часть процесса проделывается для каждого файла независимо от других. Значит, если проделывать каждый шаг лишь для тех файлов, для которых это необходимо (зависимости которых обновились) сборка значительно ускорится. (а для больших проектов, например OpenOffice, она может занимать несколько часов)

Многие компиляторы умеют сами обнаруживать зависимости и создавать Makefile .

Библиотеки

Какие бывают библиотеки?

  • Статические ( *.a , *.lib ) — код функций вставляется в исполняемый файл
  • Динамические ( *.so , *.dll ) — в исполняемый файл вставляется имя функции и ее адрес в библиотеке. Для работы необходим файл библиотеки

Отличие библиотек от программ — нет точки входа int main() .

Пример подключения библиотек:

g++ это синоним для gcc -lstd++ , что указывает, что нужно линковать со стандартной библиотекой с++. По умолчанию приложение собирается с динамическими библиотеками, в случае, если требуется собрать со статическими необходимо явно указать ключ -static . Кстати, как уже говорилось, gcc — это огромный конвейер, содержащий несколько различных компиляторов, и вызов g++ ничего не говорит, о языке написанной нами программы. Информацию о том, какой компилятор следует использовать gcc получает из расширения файла.

Упражнение 1. Попробовать собрать статическую/динамическую библиотеку и использовать ее в программе.

Подробнее об объектных файлах

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

Упражнение 2. Посмотреть содержимое объектного файла.

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

Почему компилятор переименовывает функции?

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

Так как в объектном файле информации о языке уже не сохраняется (в том числе о типах передаваемых/возвращаемых значений), то для того, чтобы эти функции различать (иначе бы при линковке возникла ошибка повторного определения) компилятор дописывает к названиям функций информацию о типах (этот процесс называется маскированием). Восстановить объявление функции по ее расширенному названию можно, например, с помощью программы c++filt.

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

Множественное включение файла заголовка С++

У меня проблема с многократным включением заголовочного файла в код C++.

Например, у меня есть три класса X, Y, Z. X и Y являются производными от базового класса Z. И я хочу создать экземпляр X в Y. Код будет выглядеть следующим образом.

Таким образом, в этом множественном определении всех методов базового класса возникает. Как я могу справиться с этой проблемой?

Использование «включить охранников» (ссылка Википедии)

Это идиоматический код, легко узнаваемый любым опытным программистом на C и С++. Измените MYHEADER_H на что-то конкретное для вас, например, если заголовок определяет класс с именем CustomerAccount , вы можете вызвать охрану CUSTOMERACCOUNT_H .

В вашем конкретном случае иметь отдельный заголовочный файл/исходный файл для каждого класса. В файле заголовка для класса Z будет хранитель include:

Теперь заголовки X и Y могут включать z.h безопасно — он будет действительно включен только один раз в файл .cpp , который включает в себя как x.h , так и y.h , и дублирование не произойдет.

Всегда помните, что в C и С++ то, что действительно скомпилировано, являются исходными (.c или .cpp) файлами, а не файлами заголовков. Файлы заголовков просто «копируются» в препроцессор в файлы источников, которые include их.

Включение header-файлов

Единственный header-файл, который вы реально должны включать в ваши модули, это php.h , находящийся в директории PHP. Этот файл делает доступными вашему коду все макросы и определения API, необходимые для построения новых модулей.

Подсказка: хорошей практикой является создание для вашего модуля отдельного header-файла, содержащего специфичные для данного модуля определения. Этот header-файл должен содержать опережающие определения для всех экспортируемых функций и включать/include php.h .

Прикладное программирование с нуля.

AVR, STM, Android, IoT. Встраиваемые системы.

Использование заголовков (header). Утилита avr-size.Часть 6. Шаг№ 21

Обновлено 7.01.16. Всем привет. Сегодня мы с Вами поговорим о том как использовать заголовки, разбить правильно код и в конце статьи научимся использовать утилиту avr-size. В предыдущей статье, мы с Вами начали разговор об универсальности кода, т.е. когда код можно применить под другой микроконтроллер меняя только настройки в директивах препроцессора — define. В этой статье рассмотрим применение своих заголовочных файлов. Что такое заголовок ( header)?

Основные правила, которыми мы будем руководствоваться при написании кода:

Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL