Глава 1 Сборка приложений на C++

1.0. Введение в сборку

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

Если посмотреть на названия рецептов в этой главе, то можно получить впечатление, что я снова и снова решаю одни и те же проблемы. И это будет правильно. Это происходит потому, что имеется большое количество способов сборки приложений С++, и хотя я не могу описать их все, я пытаюсь описать несколько наиболее важных методов. В первой десятке рецептов я показываю, как различными методами выполнять три базовые задачи - собирать статические библиотеки, собирать динамические библиотеки и собирать исполняемые файлы. Рецепты сгруппированы по методам; сначала я рассматриваю сборку из командной строки, затем с помощью системы Boost (Boost.Build), затем с помощью интегрированной среды разработчика (Integrated Development Environment (IDE), и наконец, с помощью GNU make.

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

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

Базовая терминология

Три базовых инструмента, используемых для сборки приложений С++, — это компилятор, компоновщик и архиватор (или библиотекарь). Набор этих программ и, возможно, других инструментов называется инструментарием.

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

Исполняемый файл, или приложение, — это просто любая программа, которая может выполняться операционной системой. Динамическая библиотека, также называемая совместно используемой библиотекой, похожа на исполняемый файл, за исключением того, что она не может исполняться самостоятельно. Она состоит из тела машинного кода, которое загружается в память после запуска приложения, и может использоваться одним или несколькими приложениями. В Windows динамические библиотеки также называются динамически подключаемыми библиотеками (dynamic link libraries (DLL)).

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

Таблица 1.1 приводит расширения файлов, обычно связанные с этими четырьмя базовыми типами файлов в Microsoft Windows и Unix. Когда я упоминаю файл, имеющий в Windows и Unix различные расширения, я иногда опускаю расширение, если оно ясно из контекста.


Табл. 1.1. Расширения файлов в Windows и Unix

Тип файла Windows Mac OS X Другие Unix
Объектные файлы .obj .o .o
Статические библиотеки .lib .a .a
Динамические библиотеки .dll .dylib .so
Исполняемые файлы .exe Нет расширения Нет расширения

В этой главе, когда я говорю Unix, я также имею в виду и Linux.

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

IDE и системы сборки

Компилятор, компоновщик и архиватор — это инструменты командной строки, что означает, что они предназначены для запуска из оболочки, такой как bash в Unix или cmd.exe в Microsoft Windows. Имена входных и выходных файлов, а также вся остальная необходимая настроечная информация передаются в компилятор, компоновщик и архиватор как текст в командной строке. Однако вызов этих команд вручную довольно утомителен. Даже для небольших проектов может быть сложно запомнить параметры командной строки для каждого инструмента и порядок, в котором исходные и двоичный файлы проекта должны компилироваться и компоноваться. При изменении одного исходного файла вы должны определить, какие объектные файлы требуется перекомпилировать, какие статические библиотеки требуется обновить и какие исполняемые файлы или динамические библиотеки требуется перекомпоновать. Если вы пересоберете больше файлов, чем требуется, вы зря потратите время, а если пересоберете не все требуемые, то получите ошибки при сборке или неработоспособное приложение. В случае больших проектов на С++, которые могут включать тысячи отдельных файлов, включая исходные файлы, объектные файлы, библиотеки и исполняемые файлы, сборка из командной строки просто невозможна.

Имеется два основных подхода к сборке больших приложений на С++.

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

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

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

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

Наиболее часто в качестве инструмента сборки используется утилита make; текстовые файлы, на которых она основана, называются makefile (make-файл). Хотя имеется множество версий make, в этой главе я обсуждаю GNU make — наиболее мощную и переносимую инкарнацию make. GNU make — это очень гибкий инструмент, который может использоваться не только для сборки приложений на С++. Он также имеет целый ряд преимуществ и широко используется и хорошо понимается разработчиками. К сожалению, заставить GNU make сделать именно то, что вам требуется, может оказаться не так просто, особенно в случае сложных проектов, использующих различные инструментарии. По этой причине я также описываю Boost.Build — мощную и расширяемую систему сборки, изначально предназначенную для сборки приложений на С++.

За подробным исследованием GNU make обратитесь к книге Роберта Мекленбурга (Robert Mecklenburg) Managing Projects with GNU make, Third Edition (издательство O'Reilly).

Boost.Build была разработана членами проекта Boost C++ Libraries. Она уже несколько лет используется большим сообществом разработчиков и постоянно активно совершенствуется. Boost.Build использует инструмент сборки, который называется bjam, и текстовые файлы, которые называются Jamfile (Jam-файлы). Ее самой сильной стороной является простота, с которой она позволяет управлять сложными проектами, предназначенными для нескольких платформ и содержащими несколько сборочных конфигураций. Хотя Boost.Build изначально создавалась как расширение системы сборки Perforce's Jam, она с тех пор подверглась значительной переработке. В момент сдачи этой книги в печать разработчики Boost.Build готовили официальный релиз второй основной версии этой системы сборки, и именно она описывается в этой главе

Обзор инструментария

В этой главе я буду обсуждать семь наборов инструментов командной строки: GCC, Visual C++, Intel, Metrowerks, Borland, Comeau и Digital Mars. Таблица 1.2 показывает имена инструментов командной строки из различных инструментариев, а табл. 1.3 показывает, где они расположены в вашей системе, если они установлены. Имена инструментов для Windows используют суффикс .exe, который требуется для исполняемых файлов Windows. Для инструментария, доступного как для Windows, так и для Unix, я заключаю этот суффикс в квадратные скобки.


Табл. 1.2. Имена инструментов командной строки в различном инструментарии

Инструментарий Компилятор Компоновщик Архиватор
GCC g++[.exe] g++ ar[.exe] ranlib[.exe]
Visual C++ cl.exe link.exe lib.exe
Intel (Windows) icl.exe xilink.exe xilib.exe
Intel (Linux) lcpc icpc arranlib
Metrowerks mwcc[.exe] mwld[.exe] mwld[.exe]
Comeau como[.exe] como[.exe] Зависит от инструментария
Borland bcc32.exe bcc32.exe ilink32.exe tlib.exe
Digital Mars dmc.exe link.exe lib.exe

Табл. 1.3. Расположение ваших инструментов командной строки

Инструментарий Расположение
GCC (Unix) Обычно /usr/bin или /usr/local/bin
GCC (Cygwin) Поддиректория bin установки Cygwin
GCC (MinGW) Поддиректория bin установки MinGW
Visual C++ Поддиректория VC/bin установки Visual Studio¹
Intel (Windows) Поддиректория Bin установки компилятора Intel
Intel (Linux) Поддиректория bin установки компилятора Intel
Metrowerks Поддиректория Other Metrowerks Tools/Command Line Tools установки CodeWarrior
Comeau Поддиректория bin установки Comeau
Borland Поддиректория Bin установки C++Builder, C++BuilderX или инструментов командной строки Borland

¹ В предыдущих версиях Visual Studio директория VC называлась VC98 или Vc7.


Пусть количество инструментария вас не пугает - вам не требуется изучать их все. В большинстве случаев можно просто пропустить материал, который не относится к вашему инструментарию. Однако, если вы хотите узнать немного о другом инструментарии, прочтите разделы о Visual C++ и GCC, так как это основной инструментарий для Windows и Unix.

Теперь давайте посмотрим на каждый из этих семи наборов.

GNU Compiler Collection (GCC)

GCC — это набор компиляторов для большого количества языков, включая С и С++. Следует заметить, что он является проектом с открытыми исходными кодами, доступен почти для всех имеющихся в природе платформ и обладает высокой степенью соответствия стандарту языка С++. Это основной компилятор для многих платформ Unix, он также широко используется в Microsoft Windows. Даже если GCC не является вашим основным инструментарием, вы можете многое узнать, используя его для компиляции своего кода. Также, если вы думаете, что знаете способ улучшить язык C++, проверьте свою идею на базе кода GCC.

GCC поставляется вместе с libstdc++ — хорошей реализацией с открытыми кодами стандартной библиотеки С++. Также он может использоваться совместно со стандартной библиотекой C++ с открытыми исходниками STLPort и со стандартной библиотекой Dinkumware

Чтобы узнать, где взять GCC, обратитесь к рецепту 1.1.

Примеры GCC в этой главе были протестированы с GCC 3.4.3 и GCC 4.0.0 на GNU/Linux (Fedora Core 3), с GCC 4.0.0 на Mac OS X (Darwin 8.2.0) и с GCC 3.4.2 (MinGW) и 3.4.4 (Cygwin) на Windows 2000 Professional.

Visual C++

Инструментарий Microsoft является главным на платформе Windows. Хотя до сих пор широко используются некоторые старые версии, лучше всего соответствуют стандарту самые последние версии. Также он может создавать хорошо оптимизированный код. Инструменты Microsoft распространяются вместе со средами разработки Visual C++ и Visual Studio, которые обсуждаются в следующем разделе. В момент написания этой книги они также были доступны в составе Visual C++ Toolkit 2003, который можно бесплатно скачать с www.microsoft.com.

Visual C++ поставляется в комплекте с модифицированной версией реализации стандартной библиотеки C++ Dinkumware. Стандартная библиотека C++ Dinkumware является одной из наиболее эффективных и наиболее полно соответствующих стандарту коммерческих реализаций. Она доступна на различных платформах и поддерживает многие из наборов инструментов, описываемых в этой главе.

Примеры Visual C++ в этой главе были протестированы с Microsoft Visual Studio .NET 2003 и Microsoft Visual Studio 2005 (Beta 2) (См. табл. 1.4.)


Табл. 1.4. Версии Microsoft Visual Studio

Название продукта Версия IDE Версия компилятора
Microsoft Visual Studio 6.0 1200
Microsoft Visual Studio .NET 7.0 1300
Microsoft Visual Studio .NET 2003 7.1 1310
Microsoft Visual Studio 2005 (Beta 2) 8.0 1400
Intel

Intel производит несколько компиляторов С++, предназначенных для использования со своими процессорами. Они отличаются генерацией очень быстрого кода — возможно, самого быстрого, доступного для архитектуры Intel. Основанные на интерфейсной части C++ производства Edison Design Group (EDG), они также очень хорошо соответствуют стандарту.

Компилятор Intel C++ для Windows используется совместно со средой разработки Microsoft's Visual C++ или Visual Studio, которая требуется для его правильного функционирования. Этот компилятор разработан с учетом совместимости с Visual С++: он может использоваться как дополнение к среде разработки Visual С++, может генерировать код, который на двоичном уровне совместим с кодом, генерируемым компилятором Visual С++, он предлагает многие из таких же опций командной строки, что и компилятор Visual C++, и, если вы не указали не делать этого, даже эмулирует некоторые из ошибок Microsoft. Коммерческую версию компилятора Intel C++ для Windows можно приобрести по адресу www.intel.com. Также имеется академическая версия по более низкой цене.

В то время как компилятор Intel для Windows разработан с учетом совместимости с компилятором Visual С++, компилятор Intel для Linux разработан с учетом совместимости с GCC. Для работы ему требуется GCC, он поддерживает многие опции GCC и по умолчанию реализует некоторые из расширений GCC. Коммерческую версию компилятора Intel C++ для Linux можно приобрести по адресу www.intel.com. Некоммерческая версия доступна для бесплатного скачивания.

В Windows компилятор Intel использует стандартную библиотеку Dinkumware, поставляемую вместе с Visual С++. В Linux он использует libstdc++.

Примеры Intel в этой главе были протестированы с компилятором Intel C++ Compiler 9.0 for Linux на GNU/Linux (Fedora Core 3) и с Intel C++ Compiler 9.0 for Windows на Windows 2000 Professional.

Metrowerks

Инструменты командной строки Metrowerks, распространяемые вместе со средой разработки CodeWarrior, относятся к числу лучших как с точки зрения соответствия стандарту, так и с точки зрения эффективности генерируемого кода. Они также поставляются вместе с MSL — превосходной реализацией стандартной библиотеки C++ от Metrowerks. До недавнего времени Metrowerks разрабатывала инструменты для Windows, Mac OS и некоторых встраиваемых платформ. Однако в 2004 году Metrowerks продала свои технологии компилятора и отладчика для Intel х86 фирме Nokia и прекратила выпуск линии продуктов CodeWarrior для Windows. В 2005 году, после того как Apple Computer анонсировала планы по переходу на процессоры Intel, Metrowerks объявила, что будущая версия CodeWarrior 10 для Mac OS будет, скорее всего, последней для этой платформы. В будущем Metrowerks сосредоточит свое внимание на разработке для встраиваемых систем на основе чипов производства Freescale Semiconductor.

К тому моменту, как вы будете читать эти строки, Metrowerks станет частью Freescale Semiconductor, и имя Metrowerks больше не будет связано с линейкой продуктов CodeWarrior. Однако я буду использовать имя Metrowerks, так как пока не ясно, каково будет ее имя в дальнейшем.

Примеры Metrowerks в этой главе были протестированы с CodeWarrior 9.6 и 10.0 (Beta) on Mac OS X (Darwin 8.2.0) и с CodeWarrior 9.4 на Windows 2000 Professional.

Borland

Инструменты командной строки Borland когда-то считались очень хорошими. Однако к сентябрю 2005 года последнее обновление насчитывало уже три года и представляло собой только незначительные улучшения предыдущей версии, которая была выпущена в 2000 году. В результате теперь инструменты Borland являются несколько устаревшими. В 2003 году Borland анонсировала планы по серьезному редизайну компилятора С++, используя интерфейсную часть EGD. К сожалению, в течение прошедшего времени Borland не делала больше никаких новых анонсов. Однако инструменты командной строки Borland остаются важны, так как все еще широко используются.

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

Инструментарий Borland поставляется с двумя стандартными библиотеками С++: STLPort и устаревшей версией стандартной библиотеки Rogue Wave. Также Borland разрабатывает версию инструментов, которые будут поставляться со стандартной библиотекой Dinkumware.

Примеры Borland в этой главе были протестированы с Borland C++ Builder 6.0 (версия компилятора 5.6.4) на Windows 2000 Professional

Comeau

Компилятор Comeau широко известен своим полным соответствием стандарту С++. Кроме реализации самых последних версий языка C++ он поддерживает несколько версий С и большое количество ранних диалектов С++. Он также является одним из самых дешевых, стоя на настоящий момент $50.

Аналогично компилятору Intel Comeau использует интерфейс EDG и требует для корректной работы отдельного компилятора С. В отличие от Intel, Comeau может использовать в качестве внутреннего интерфейса различные компиляторы С.

Comeau доступен для Microsoft Windows и для многих видов Unix. Если для вашей платформы Comeau недоступен, вы можете оплатить Comeau Computing создание отдельной версии, но это значительно дороже. Заказать компилятор Comeau можно по адресу www.comeaucomputing.com.

При обсуждении Comeau в Unix я буду в качестве внутреннего компилятора подразумевать GCC. При обсуждении Comeau в Windows я попробую указать, как опции командной строки зависят от используемого компилятора. Но так как Comeau может использоваться с большим количеством других компиляторов, не всегда возможно дать исчерпывающую информацию.

Comeau поставляется с libcomo — реализацией стандартной библиотеки С++, основанной на стандартной библиотеке Silicon Graphics. Также он может использовать стандартную библиотеку Dinkumware.

Примеры Comeau в этой главе предполагают, что используется libcomo и что компилятор настроен так, что он автоматически находит libcomo. Примеры были проверены с Comeau 4.3 3 и libcomo 31, используя GCC 3.4.3 на GNU/Linux (Fedora Core 3) и используя Visual C++ .NET 2003 на Windows 2000 Professional. (См. табл 1.4.)

Digital Mars

Digital Mars — это компилятор С++, написанный Вальтером Брайтом (Walter Bright). Его можно бесплатно скачать с www.digitalmars.com, а за небольшую сумму можно заказать CD, содержащий компилятор Digital Mars, IDE и некоторые полезные инструменты. Бесплатная версия компилятора может компилировать все примеры Digital Mars из этой главы, за исключением тех, которые требуют динамическую версию рабочей библиотеки, доступную только на CD.

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

Digital Mars поставляется с двумя стандартными библиотеками: портом стандартной библиотеки STLPort и более старой "стандартной библиотекой, которая не соответствует стандарту и неполна. В целях обратной совместимости STLPort должен явно подключаться пользователем. Все примеры Digital Mars в этой главе используют стандартную библиотеку STLPort.

Примеры Digital Mars в этой главе были проверены с Digital Mars 8.45 на Windows 2000 Professional.

Обзор IDE

В этой главе я описываю четыре IDE: Microsoft Visual С++, Metrowerks CodeWarrior, Borland C++Builder и Bloodshed Software Dev-C++. Есть большое количество различных IDE, не охватываемых мной, — примерами являются Apple Xcode и Eclipse Project, — но рассмотрение этих четырех IDE должно дать вам достаточно материала для начала изучения других IDE.

Как и в случае с инструментами командной строки, вы можете пропустить материал, не относящийся к вашей IDE.

Visual C++

Microsoft Visual C++ — это главная среда разработки C++ для Microsoft Windows. Она доступна как отдельное приложение или как часть набора Visual Studio и поставляется в комплекте с большим набором инструментов для разработки под Windows. Для переносимой разработки на C++ наиболее важными ее качествами являются

• высокое соответствие компилятора стандарту С++;

• стандартная библиотека C++ Dinkumware;

• хороший визуальный отладчик;

• менеджер проектов, который отслеживает зависимости между проектами.

Широко используются несколько версий Visual Studio. Так как названия различных версий могут сбить с толку, я перечислил наиболее широко используемые версии в табл. 1.4.

Первая версия Visual С++, включающая первоклассные компилятор и стандартную библиотеку, находится в третьей строке табл. 1.4. Все предшествующие версии имеют серьезные проблемы с реализацией стандарта.

CodeWarrior

CodeWarrior — это кросс-платформенная среда разработки Metrowerks. Она имеет большинство таких же функций, что и Visual С++, включая:

• высокое соответствие компилятора стандарту С++;

• превосходную стандартную библиотеку C++;

• хороший визуальный отладчик;

• менеджер проектов, который отслеживает зависимости между проектами.

Одной из сильных сторон CodeWarrior традиционно являлось большое количество платформ, для которых он был доступен, однако, как было сказано в предыдущем разделе, его линия для Windows была закрыта, а линия для Macintosh будет закрыта в скором будущем. Однако он остается важной платформой для разработки встраиваемых систем.

При обсуждении CodeWarrior IDE я предполагаю, что вы используете CodeWarrior 10 для Mac OS X. CodeWarrior IDE для других платформ очень похожа на эту версию.

C++Builder

C++Builder — это среда разработки Borland для приложений Microsoft Windows. Одной из ее привлекательных черт является поддержка библиотеки Borland's Visual Component Library. Однако для переносимой (мобильной) разработки на C++ наиболее важными ее качествами являются:

• проверенный временем компилятор С++;

• стандартная библиотека STLPort;

• хороший визуальный отладчик;

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

Я описываю C++Builder, потому что он широко используется и у него есть большое сообщество преданных пользователей.

C++Builder не следует путать с C++BuilderX — кросс-платформенной средой разработки, выпущенной Borland в 2003 году. Хотя C++BuilderX является полезным инструментом разработки, он не имел коммерческого успеха и неизвестно, будет ли Borland выпускать его новые версии.

Dev-C++

Bloodshed Software Dev-C++ — это бесплатная среда разработки C++ для Windows, использующая порт MinGW GCC, описанный в рецепте 1.1. Он содержит вполне удобный текстовый редактор и визуальный интерфейс для отладчика GNU.

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

John, Paul, George, and Ringo

Co времен, когда в 1978 году Брайан Керниган (Brian Kernighan) и Деннис Ритчи (Dennis Ritchie) опубликовали книгу The С Programming Language (Язык программирования С), стало традицией начинать изучение нового языка программирования с написания, компиляции и запуска небольшой программки, которая печатает в консоли «Hello, World!» («Привет, мир!»). Так как эта глава описывает статические и динамические библиотеки, а также исполняемые файлы, мне потребуется несколько более сложный пример.

Примеры 1.1, 1.2 и 1.3 представляют исходный код приложения hellobeatles, которое выводит текст

John, Paul, George, and Ringo

на консоль. Это приложение можно написать в виде единого исходного файла, но я разбил его на три модуля: статическую библиотеку libjohnpaul, динамическую библиотеку libgeorgeringo и исполняемый файл hellobeatles. Более того, хотя каждая из этих библиотек могла бы быть легко реализована как один заголовочный файл и один файл .cpp, я, чтобы проиллюстрировать компиляцию и компоновку проектов, содержащих более одного исходного файла, разбил реализацию на несколько исходных файлов.

Прежде чем вы начнете прорабатывать рецепты в этой главе, создайте четыре расположенные на одном уровне директории johnpaul, georgeringo, hellobeatles и binaries. В первые три директории поместите исходные файлы из примеров 1.1, 1.2 и 1.3. Четвертая директория будет использоваться для двоичных файлов, генерируемых IDE.

Исходный код libjohnpaul представлен в примере 1.1. Открытый интерфейс libjohnpaul состоит из одной функции

johnpaul()
, объявленной в заголовочном файле johnpaul.hpp. Функция
johnpaul()
отвечает за печать:

John, Paul,

на консоль. Реализация

johnpaul()
разбита на два. исходных файла — john.cpp и paul.cpp, каждый из которых отвечает за печать одного имени.

Пример 1.1. Исходный код libjohnpaul


johnpaul/john.hpp

#ifndef JOHN_HPP_INCLUDED

#define JOHN_HPP_INCLUDED


void john(); // Печатает "John, "

#endif // JOHN _HPP_INCLUDED


johnpaul/john.cpp

#include 

#include "john.hpp"


void john() {

 std::cout << "John, ";

}


johnpaul/paul.hpp

#ifndef PAUL_HPP_INCLUDED

#define PAUL_HPP_INCLUDED


void paul(); // Печатает " Paul, "


#endif // PAUL_HPP_INCLUDED


johnpaul/paul.cpp

#include 

#include "paul.hpp"


void paul() {

 std::cout << "Paul, ";

}


johnpaul/johnpaul.hpp

#ifndef JOHNPAUL_HPP_INCLUDED

#define JOHNPAUL_HPP_INCLUDED


void johnpaul(); // Печатает "John, Paul, "


#endif // JOHNPAUL_HPP_INCLUDED


johnpaul/johnpaul.cpp

#include "john.hpp"

#include "paul.hpp"

#include "johnpaul.hpp"


void johnpaul() {

 john();

 paul();

}

Исходный код libgeorgeringo представлен в примере 1.2. Открытый интерфейс libgeorgeringo состоит из одной функции

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

George, and Ringo

на консоль. И снова реализация

georgeringo()
разделена на два исходных файла — george.cpp и ringo.cpp.

Пример 1.2. Исходный код libgeorgeringo


georgeringo/george.hpp

#ifndef GEORGE_HPP_INCLUDED

#define GEORGE_HPP_INCLUDED


void george(); // Печатает "George, "


#endif // GEORGE_HPP_INCLUDED


georgeringo/george.cpp

#include 

#include "george.hpp"


void george()

 std::cout << "George, ";

}


georgeringo/ringo.hpp

#ifndef RINGO_HPP_INCLUDED

#define RINGO_HPP_INCLUDED


void ringo(); // Печатает "and Ringo\n"


#endif // RINGO_HPP_INCLUDED


georgeringo/ringo.cpp

#include 

#include "ringo.hpp"


void ringo() {

 std::cout << "and Ringo\n";

}


georgeringo/georgeringo.hpp

#ifndef GEORGERINGO_HPP_INCLUDED

#define GEORGERINGO_HPP_INCLUDED


// определите GEORGERINGO_DLL при сборке libgeorgeringo.dll

#if defined(_WIN32) && !defined(__GNUC__)

#ifdef GEORGERINGO_DLL

# define GEORGERINGO_DECL __declspec(dllexport)

#else

# define GEORGERINGO_DECL __declspec(dllimport)

#endif

#endif // WIN32

#ifndef GEORGERINGO_DECL

# define GEORGERINGO_DECL

#endif


// Печатает "George, and Ringo\n"

#ifdef __MWERKS__

# pragma export on

#endif


GEORGERINGO_DECL void georgeringo();


#ifdef __MWERKS__

# pragma export off

#endif

#endif // GEORGERINGO_HPP_INCLUDED


georgeringo/georgeringo.cpp

#include "george.hpp"

#include "ringo.hpp"

#include "georgeringo.hpp"


void georgeringo() {

 george();

 ringo();

}

Заголовок georgeringo.hpp содержит несколько сложных директив препроцессора. Если вы их не понимаете, не страшно. Я объясню их в рецепте 1.4.

Наконец, исходный код исполняемого файла hellobeatles представлен в примере 1.3. Он состоит из единственного исходного файла hellobeatles.cpp, который просто включает заголовки johnpaul.hpp и georgeringo.hpp и вызывает функцию

johnpaul()
, а вслед за ней — функцию
georgeringo()
.

Пример 1.3. Исходный код hellobeatles


hellobeatles/ hellobeatles.cpp

#include "johnpaul/johnpaul.hpp"

#include "georgeringo/georgeringo.hpp"


int main() {

 // Печатает "John, Paul, George, and Ringo\n"

 johnpaul();

 georgeringo();

}

1.1. Получение и установка GCC

Проблема

Вы хотите получить GCC — свободно распространяемый компилятор GNU C/С++.

Решение

Решение зависит от вашей операционной системы.

Windows

Установите MinGW, Cygwin или оба.

Чтобы установить MinGW, посетите страницу MinGW по адресу www.mingw.org и проследуйте по ссылкам до страницы загрузки MinGW. Скачайте последнюю версию программы установки MinGW, которая должна иметь имя MinGW-<версия>.exe.

Далее запустите программу установки. Она попросит вас указать, куда вы хотите установить MinGW. Также она может спросить, какие пакеты вы хотите установить, - как минимум вы должны установить gcc-core, gcc-g++, binutils и среду выполнения MinGW, но можно установить и другие. По окончании установки вы сможете запустить из командной строки Windows gcc, g++, ar, ranlib, dlltool и некоторые другие инструменты GNU. Вам может потребоваться добавить директорию bin установки MinGW в переменную среды окружения

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

Чтобы установить Cygwin, посетите страницу Cygwin по адресу www.cygwin.com и для загрузки программы установки Cygwin проследуйте по ссылке Install Cygwin Now. Далее запустите программу установки. Она попросит вас выбрать несколько опций, таких как путь, куда следует устанавливать Cygwin.

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

Наиболее важным выбором является выбор пакетов. Если у вас достаточно места на диске и высокоскоростное соединение с Интернетом, я рекомендую устанавливать все пакеты. Чтобы сделать это, щелкните на слове Default (По умолчанию) рядом со словом All (Все) в верхней части иерархии пакетов. После паузы (возможно, продолжительной) слово Default должно измениться на Install (Установить).

Если места на диске недостаточно или у вас медленное соединение с Интернетом, можете выбрать меньшее количество пакетов. Чтобы выбрать только инструменты разработки, щелкните на слове Default рядом со словом Devel. После паузы (возможно, продолжительной) слово Default должно измениться на Install. Для выбора еще меньшего набора пакетов раскройте список пакетов для разработки, щелкнув на пиктограмме + рядом со словом Devel. Выберите пакеты gcc-core, gcc-g++ и make, щелкнув на слове Skip (Пропустить) напротив каждого из этих пакетов, в результате чего это слово сменится на Install.

По окончании выбора пакетов нажмите на Finish (Готово). Когда программа установки завершит работу, директория установки Cygwin должна содержать файл cygwin.bat. Запуск этого сценария приведет к отображению оболочки Cygwin — среды с командной строкой, из которой можно запускать gcc, g++, ar, ranlib, dlltool, make и любые другие установленные вами утилиты. Процесс установки добавляет поддиректорию bin установки Cygwin в переменную среды окружения

PATH
, так что запускать эти утилиты можно также и из оболочки Windows cmd.exe. Однако вы увидите, что оболочка Cygwin — порт оболочки bash — гораздо удобнее для запуска утилит GNU.

Unix

Введя в командной строке

g++ -v
, проверьте, установлен ли в вашей системе GCC. Если GCC установлен и если доступна поддержка языка С++, эта команда должна напечатать сообщение, похожее на следующее.

Using built-in specs.

Target: powerpc-apple-darwin8

Configured with /private/var/tmp/gcc/gcc-5026.obj~19/src/configure

--disable-checking --prefix=/usr ...

Если GCC не установлен или если он установлен без поддержки С++, вы должны самостоятельно установить его. Обычно это сложный процесс, который зависит от вашей платформы. Среди прочего вам потребуется установить пакеты GNU make и GNU binutils. Подробные инструкции доступны по адресу gcc.gnu.org/install.

Если вы используете Mac OS X, то простейшим способом получения GCC является скачивание с Web-сайта Apple среды разработки Xcode и следование простым инструкциям ее установки. В настоящий момент Xcode доступен по адресу developer.apple.com/tools.

Если вы используете Linux, то какая-то версия GCC уже должна быть установлена. Чтобы проверить номер версии, введите

g++ -v
. Текущая версия GCC — это 4.0.0. Если ваша версия сильно устарела, используйте систему управления пакетами, применяемую в вашем дистрибутиве Linux, и установите наиболее новую.

Обсуждение

Cygwin и MinGW представляют очень разные подходы к портированию инструментов GNU в Windows. Cygwin — это амбициозный проект, стремящийся воспроизвести Unix-подобную среду, работающую под Windows. Он предоставляет уровень совместимости с Unix, что позволяет компилировать и выполнять под Windows программы, написанные для Unix. Следовательно, для Cygwin доступно огромное количество утилит Unix. Даже если вы не разрабатываете для Unix, вы, возможно, скоро станете считать, что вам необходимы инструменты Cygwin.

MinGW, что означает «Minimalist GNU for Windows» (минимальный GNU для Windows), предоставляет минимальную среду для сборки исполняемых файлов для Windows с помощью GCC. Среди других вещей MinGW включает порт GCC, порт архиватора и компоновщика GNU и порт отладчика GNU GDB. Он также включает MSYS — среду командной строки, способную выполнять make-файлы GNU и сценарии configure. MSYS будет обсуждаться в рецепте 1.14.

Одно из важных различий между Cygwin и MinGW относится к лицензированию. За некоторыми исключениями вы можете распространять двоичные файлы, скомпилированные с помощью порта MinGW GCC, под любой удобной вам лицензией. С другой стороны, двоичные файлы, собранные с помощью порта Cygwin GCC, по умолчанию подпадают под действие лицензии GNU General Public License (GPL). Если вы хотите распространять программы, скомпилированные в Cygwin, не делая их исходники открытыми, вы должны приобрести лицензию у Red Hat. За полным описанием обратитесь к Web-сайтам Cygwin и MinGW.

Смотри также

Рецепт 1.14.

1.2. Сборка простого приложения «Hello, World» из командной строки

Проблема

Вы хотите собрать простую программу «Hello, World», подобную приведенной в примере 1.4.

Пример 1.4. Простая программа «Hello, World»


hello.cpp

#include 


int main() {

 std.:cout << "Hello, World!\n";

}

Решение

Выполните следующие шаги.

1. Установите все переменные среды окружения, необходимые для вашего инструментария.

2. Введите команду, которая говорит компилятору скомпилировать и скомпоновать вашу программу.

Сценарии для установки переменных среды окружения перечислены в табл 1.5. Эти сценарии расположены в той же директории, что и инструменты командной строки (табл. 1.3), Если ваш инструментарий в табл. 1.5 не указан, пропустите первый шаг. В противном случае, если вы используете Windows, запустите соответствующий сценарий из командной строки, а если используете Unix, то укажите его в качестве источника переменных окружения.


Табл. 1.5. Сценарии для установки переменных среды окружения, необходимые для инструментов командной строки

Инструментарий Сценарий
Visual C++ vcvars32.bat
Intel (Windows) iclvars.bat¹
Intel (Linux) iccvars.sh или iccvars.csh
Metrowerks (Mac OS X) iccvars.sh или mwvars.csh²
Metrowerks (Windows) cwenv.bat
Comeau Тот же, что и для используемого базового инструментария

¹ В предыдущих версиях компилятора Intel этот сценарий назывался iccvars.bat.

² В версиях CodeWarrior до 10.0 имелся единственный сценарий csh с именем mwvars.


Команды для компиляции и компоновки hello.cpp приведены в табл. 1.6. Для корректной работы эти команды требуют, чтобы ваша текущая директория была директорией, содержащей hello.cpp, и чтобы директория, в которой находится компилятор командной строки, была указана в переменной среды

PATH
. Если на шаге 1 вы запустили сценарий, то последнее требование будет удовлетворено автоматически. Также возможно, что директорию, содержащую инструменты командной строки, в переменную
PATH
добавил инсталлятор при установке инструментария. В противном случае вы можете либо добавить эту директорию в переменную
PATH
, как показано в табл. 1.7, либо указать в командной строке полный путь к файлу.


Табл. 1.6. Команды для компиляции и компоновки hello.cpp за один шаг

Инструментарий Командная строка
GCC g++ -o hello hello.cpp
Visual C++ cl -nologo -EHsc -GR -Zc:forScope -Zc:wchar_t -Fehello hello.cpp
Intel (Windows) id -nologo -EHsc -GR -Zc:forScope -Zc:wchar_t -Fehello hello.cpp
Intel (Linux) icpc -o hello hello.cpp
Metrowerks mwcc -wchar_t on -cwd include -o hello hello.cpp
Comeau como -o hello hello.cpp
Borland bcc32 -q -ehello hello.cpp
Digital Mars dmc -Ae -Ar -l/stlport/stlport -o hello hello.cpp

Табл. 1.7. Добавление директории в переменную среды окружения PATH для одной сессии работы с командной строкой

Оболочка Командная строка
bash, sh, ksh (Unix) export PATH=:$PATH
csh, tsch (Unix) setenv PATH :$PATH
cmd.exe (Windows) set PATH=;%PATH%

Например, при использовании Microsoft Visual Studio .NET 2003 и установке ее по стандартному пути на диск С перейдите в директорию, содержащую hello.cpp, и введите показанные ниже команды.

> "C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\bin\vcvars32.bat"

Setting environment for using Microsoft Visual Studio .NET 2003 tools.

(If you have another version of Visual Studio or Visual C++ installed

and wish to use its tools from the command line, run vcvars32.bat for

that version.)

> cl -nologo -EHsn -GR -Zc:forScope -Zc:wchar_t -Fehello hello.cpp hello

hello.cpp

hello

Теперь программу можно запустить.

> hello

Hello World!

Аналогично при использовании Intel 9.0 для Linux и установке его по стандартному пути /opt/intel/cc/9.0 откройте оболочку bash, перейдите в директорию, содержащую hello.cpp, и введите команды:

$ . /opt/intel/cc/9.0/bin/iccvars.sh

$ icpc -о hello hello.cpp

$ ./hello

Hello, World!

Обсуждение

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

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

Инструменты командной строки используют переменные среды как в Unix, так и в Windows, но в Unix обычно есть системный компилятор С++, и переменные среды для его работы обычно по умолчанию устанавливаются в правильные значения. Однако в Windows традиционно имелось несколько конкурирующих компиляторов С++. Например, два различных компилятора будут, скорее всего, искать стандартные заголовочные файлы и библиотеки в различных местах. Следовательно, для Windows инструментарий часто предоставляет сценарии, которые устанавливают несколько переменных среды, в которых записано расположение заголовочных файлов и библиотек, а также другая информация.

Один из способов использовать такой сценарий — запускать его из командной строки перед вызовом какого-либо инструмента командной строки, как я продемонстрировал для Visual C++ и для Intel 9.0 для Linux. Также можно сделать установки переменных среды постоянными, с тем чтобы не приходилось каждый раз при запуске сессии командной строки запускать этот сценарий. Как это сделать, зависит от вашей операционной системы и вашей оболочки. Однако изменение постоянных значений переменных среды является плохой идеей, так как некоторые наборы инструментов могут содержать инструменты с одинаковыми именами, что приведет к вызову в процессе сборки неправильного инструмента. Например, если у вас установлено несколько версий Visual С++, вы должны быть уверены, что перед использованием инструментов командной строки вы запустили правильную версию vcvars32.bat. Другим примером является то, что Visual C++ и Digital Mars содержат инструменты с именами link.exe и lib.exe.

Теперь давайте посмотрим на командные строки в табл. 1.7. Помните, что вам требуется обратить внимание только на ту строку, которая соответствует вашему инструментарию. В общем случае информация, передаваемая компилятору, делится на четыре категории.

• Имя (имена) входного (исходного) файла (файлов).

• Имя (имена) выходного файла (файлов).

• Пути поиска файлов.

• Общая конфигурационная информация.

В табл. 1.6 указан только один входной файл hello.cpp, и он передается компилятору с помощью указания его имени в командной строке. Не имеет значения, в каком месте строки находится имя входного файла, при условии, что оно не находится в середине другой опции командной строки. В табл. 1.7 я поместил hello.cpp в самый конец командной строки.

Также в ней присутствует один выходной файл — hello.exe или hello, в зависимости от операционной системы. Однако в этом случае способ передачи имени файла компилятору зависит от инструментария. Большая часть инструментов для указания выходного файла использует , но Visual C++ и Intel для Windows используют -Fe, a Borland использует -e. Заметьте, что указывать расширение исполняемого файла не обязательно.

Единственная информация в табл. 1.7, относящаяся к третьей категории — путям поиска файлов, — имеется в строке для Digital Mars. Так как библиотека STLPort не является встроенной стандартной библиотекой Digital Mars, компилятору с помощью опции -I требуется сообщить, где искать заголовочные файлы STLPort. Заголовочные файлы STLPort расположены в поддиректории /stlport/stlport установки Digital Mars. В табл. 1.7 я указал эту директорию с помощью опции /stlport/stlport. За дополнительной информацией об опции -I обратитесь к рецепту 1.5.

Большая часть опций командной строки в табл. 1.7 относится к четвертой категории: общей конфигурационной информации. Эти опции не относятся к какому-либо отдельному файлу, а включают или отключают определенные функции компилятора.

• Опции -nologo (Visual C++ и Intel для Windows) и -q (Borland) говорят компилятору не печатать в консоли свои название и версию. Это делает вывод компилятора более простым для чтения.

• Опции -EHsc (Visual C++ и Intel для Windows) и -Ае (Digital Mars) говорят компилятору включить обработку исключений С++.

• Опции -GR (Visual C++ и Intel для Windows) и -Ar (Digital Mars) говорят компилятору включить информацию времени исполнения (RTTI).

• Опции -Zc:wchar_t (Visual C++ и Intel для Windows) и -wchar_t (Metrowerks) говорят компилятору распознавать

wchar_t
как встроенный тип.

• Опция -Zc:forScope (Visual C++ и Intel для Windows) говорит компилятору задействовать современные правила для областей видимости циклов

for
.

• Опция -cwd include (Metrowerks) говорит компилятору начинать поиск включенного заголовка с директории исходного файла, содержащего директиву

include
. Это поведение по умолчанию для всех инструментов, кроме Metrowerks.

Далее давайте рассмотрим второе решение нашей проблемы. Вместо того чтобы компилировать и компоновать с помощью одной команды, второй шаг можно разбить на две части.

2a. Введите команду, говорящую компилятору скомпилировать программу в объектный файл без компоновки.

2b. Введите команду, говорящую компоновщику создать исполняемый файл из объектных файлов, созданных на шаге 2a.

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

Команды для компиляции и компоновки в два этапа представлены в табл. 1.8 и 1.9. В некоторых случаях я устанавливаю для объектного файла расширение o[bj], указывающее, что одна и та же командная строка годится и для Windows, и для Unix, за исключением расширения объектного файла.


Табл. 1.8. Команды для компиляции hello.cpp без компоновки

Инструментарий Командная строка
GCC g++ --c -o hello.o hello.cpp
Visual C++ cl -с -nologo -EHsc -GR -Zc:forScope -Zc:wchar_t -Fohello hello.cpp
Intel (Windows) icl -с -nologo -EHsc -GR -Zc:forScope Zc:wchar_t -Fohello hello.cpp
Intel (Linux) icpc -с о hello.о hello.cpp
Metrowerks mwcc -c -wchar_t on -cwd include -o hello.o[bj] hello.cpp
Comeau como -с -o hello.o[bj] hello.cpp
Borland bcc32 -c -q -o hello.obj hello.cpp
Digital Mars dmc -c -Ae -Ar -l/stlport/stlport -o hello.obj hello.cpp

Табл. 1.9. Команды для компоновки hello.exe или hello

Инструментарий Командная строка
GCC g++ -о hello hello.o
Visual C++ link -nologo -out:hello.exe hello.obj
Intel (Windows) xilink -nologo -out:hello.exe hello.obj
Intel (Linux) icpc -o hello hello.o
Metrowerks mwld -o hello hello.o[bj]
Comeau como --no_prelink_verbose -о hello hello.o[bj]
Borland bcc32 -q -ehello hello.cpp
Digital Mars link -noi hello.obj, hello.exe,NUL,user32.lib kernel32.lib

Например, чтобы собрать исполняемый файл hello с помощью инструментария GCC, перейдите в директорию, содержащую hello.cpp, и введите следующие команды.

$ g++ -с -о hello.о hello.cpp

$ g++ -о hello hello.о

Теперь программу можно запустить вот так.

$ ./hello Hello, World!

Таблица 1.9 почти идентична табл. 1.6. Имеется только два различия. Во-первых, используется опция , говорящая компилятору скомпилировать без компоновки. Во-вторых, указанный выходной файл является объектным файлом hello.obj или hello.o, а не исполняемым. Большая часть компиляторов для указания выходного файла использует опцию , но Visual C++ и Intel для Windows используют опцию -Fo. Кроме того, все компиляторы, за исключением Visual C++ и Intel для Windows, требуют, чтобы было указано расширение объектного файла.

Теперь все командные строки в табл. 1.9 должны быть просты и понятны, так что я сделаю только два замечания.

• Компоновщик Digital Mars имеет необычный синтаксис, содержащий шесть полей, разделенных запятыми, которые используются для указания различных типов входных файлов. Сейчас вам требуется знать только то, что первое поле предназначено для объектных файлов, а второе — для выходного файла. Опция -noi говорит компоновщику выполнить компоновку с учетом регистра, что необходимо для программ на C++.

• Компоновщик Borland ilink32.exe использует синтаксис, похожий на Digital Mars. Чтобы упростить командную строку, я использовал для выполнения этапа компоновки компилятор bcc32.exe. Внутри себя bcc32.exe вызывает ilink32.exe.

Смотри также

Рецепты 1.7 и 1.15.

1.3. Сборка статической библиотеки из командной строки

Проблема

Вы хотите использовать свои инструменты командной строки для сборки статической библиотеки из набора исходных файлов С++, таких как перечисленные в примере 1.1.

Решение

Во-первых, используйте компилятор для компиляции исходных файлов в объектные файлы. Если ваши исходные файлы включают заголовочные файлы, расположенные в других директориях, то для указания компилятору, где искать эти заголовочные файлы, вам может потребоваться использовать опцию -I. За дополнительной информацией обратитесь к рецепту 1.5. Во-вторых, для объединения объектных файлов в статическую библиотеку используйте архиватор.

Чтобы скомпилировать каждый из трех исходных файлов из примера 1.1, используйте командные строки из табл. 1.8, изменив соответственно имена входного и выходного файлов. Чтобы объединить результирующие объектные файлы в статическую библиотеку, используйте команды, приведенные в табл. 1.10.


Табл. 1.10. Команды для создания архива libjohnpaul.lib или libjohnpaul.а

Инструментарий Командная строка
GCC (Unix) Intel (Linux) Comeau (Unix) ar ru libjohnpaul.a john.c paul.о johnpaul.o ranlib libjohnpaul.a
GCC (Windows) ar ru libjohnpaul.a john.o paul.o johnpaul.о
Visual C++ lib -nologo -out:libjohnpaul.lib john.obj paul.obj johnpaul.obj
Comeau (with Visual С++)
Intel (Windows) xilib -nologo/out:libjohnpaul.lib john.obj paul.obj johnpaul.obj
Metrowerks (Windows) mwld -library -o libjohnpaul.lib john.obj paul.obj johnpaul.obj
Metrowerks (Mac OS X) mwld -library -o libjohnpaul.a john.о paul.o johnpaul.о
Borland tlib libjohnpaul lib /u /a /C +john +paul +johnpaul
Digital Mars lib -c -n libjohnpaul.lib john.obj paul.obj johnpaul.obj

Например, чтобы скомпилировать john.cpp, paul.cpp и johnpaul.cpp в объектные файлы с помощью GCC, перейдите в директорию johnpaul и введите следующие команды, создающие объектные файлы john.о, paul.о и johnpaul.о:

$ g++ -с -о john.o john.cpp

$ g++ -с -о paul.o paul.cpp

$ g++ -с -о johnpaul.о johnpaul.cp
p

Теперь скомпонуйте эти объектные файлы в статическую библиотеку следующим образом.

$ ar ru libjohnpaul.a john.o paul.o johnpaul.о

$ ranlib libjohnpaul.а

Обсуждение

При использовании GCC в Unix для создания статической библиотеки вы используете две отдельные команды: во-первых, вы вызываете архиватор ar, а затем вызываете инструмент с именем ranlib. Опция ru говорит ar добавить указанные объектные файлы в указанный архив, если в нем отсутствуют члены с такими же именами, а обновить существующий член архива только в том случае, если указанный объектный файл новее, чем существующий член архива. Традиционно после создания или обновления архива использовался инструмент ranlib, который создает или обновляет таблицу символов архива, т.е. указатель символов, которые присутствуют в содержащихся в архиве объектных файлах. Сегодня на многих системах архиватор ar самостоятельно заботится об обновлении таблицы символов, так что запуск ranlib необязателен. В частности, это верно для версии GNU ar. Однако на некоторых системах компилятор GCC может использоваться в сочетании с не-GNU-версией ar, и по этой причине для безопасности лучше запустить ranlib.

Как вы можете видеть в табл. 1.10, архиватор Borland tlib использует несколько необычный синтаксис: знак «плюс» перед объектными файлами говорит tlib добавить объектные файлы в библиотеку. Остальные командные строки должны быть вам понятны без пояснений.

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

Смотри также

Рецепты 1.8, 1.11 и 1.16.

1.4. Сборка динамической библиотеки из командной строки

Проблема

Вы хотите использовать свои инструменты командной строки для сборки динамической библиотеки из набора исходных файлов С++, таких как перечисленные в примере 1.2.

Решение

Выполните следующие шаги.

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

GEORGERINGO_DLL
. Если вы собираете библиотеку, написанную кем-то другим, то макросы, которые требуется определить, должны быть описаны в инструкции по установке.

2. Используйте компоновщик для создания из объектных файлов, созданных на шаге 1, динамической библиотеки.

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

Основные команды для выполнения первого шага приведены в табл. 1.8 Вы должны соответственно изменить имена входных и выходных файлов. Команды для выполнения второго шага приведены в табл. 1.11. Если вы используете инструментарий, который поставляется как со статическим, так и с динамическим вариантами библиотек времени исполнения, укажите компилятору и компоновщику использовать динамический вариант, как описано в рецепте 1.23.


Табл. 1.11. Команды для создания динамической библиотеки libgeorgeringo.so, libgeorgeringo.dll или libgeorgeringo.dylib

Инструментарии Командная строка
GCC g++ -shared -fPIC -o libgeorgeringo.so george.o ringo.с georgeringo.о
GCC (Mac OS X) g++ -dynamclib -fPIC -o libgeorgeringo.dylib george.o ringo.о georgeringo.o
GCC (Cygwin) g++ -shared -o libgeorgeringo.dll -Wl,--out-implib,libgeorgeringo.dll,a -Wl,--export- all-symbols -Wl,--enable-auto-image-base george.o ringo.o georgeringo.o
GCC (MinGW) g++ -shared -о libgeorgeringo.dll -Wl,-out-implib,libgeorgeringo.a -Wl,--export-all- symbols, -Wl,--enable-auto-image-base george.о ringo.о georgeringo.o
Visual C++ link -nologo -dll -out:libgeorgeringo.dll -implib:libgeorgeringo.lib george.obj ringo.obj georgeringo.obj
Intel (Windows) xilink -nologo -dll -out:libgeorgeringo.dll -implib:libgeorgeringo.lib george.obj ringo.obj georgeringo.obj
Intel (Linux) g++ -shared -fPIC -lrt -o libgeorgeringo.so george.o ringo.о georgeringo.o georgeringo.obj
Metrowerks (Windows) mwld -shared -export dllexport -runtime dm -o libgeorgeringo.dll implib libgeorgeringo.lib george.obj ringo.obj georgeringo.obj
Metrowerks (Mac OS X) mwld -shared -export pragma -o libgeorgeringo.dylib george.o ringo.о georgeringo.о
CodeWarrior 10.0 (Mac OS X)¹ Сверьтесь с документацией Metrowerks
Borland bcc32 -q -WD -WR -elibgeorgeringo.dll george.obj ringo.obj georgeringo.obj implib -c libgeorgeringo.lib libgeorgeringo.dll
Digital Mars dmc -WD -L/implib:libgeorgeringo.lib -о libgeorgeringo.dll george.obj ringo.obj georgeringo.obj user32.lib kernel32.lib

¹ CodeWarrior 10.0 для Mac OS X будет содержать динамический вариант своих библиотек. При сборке libgeorgeringo.dylib следует использовать именно их. (См. рецепт 1.23.)

По состоянию на сентябрь 2005 года инструментарий Comeau не поддерживал сборку динамических библиотек в Unix или Windows. Однако Comeau Computing работает над поддержкой динамических библиотек, и ожидается, что к концу 2005 года эта поддержка будет реализована для некоторых платформ Unix, включая Linux.

Например, чтобы скомпилировать исходные файлы из примера 1.2 в объектные файлы с помощью компилятора Borland, предполагая, что директория, в которой находятся инструменты Borland, включена в переменную

PATH
, перейдите в директорию georgeringo и введите следующие команды.

> bcc32 -с -a -WR -о george.obj george.cpp

george.cpp:

> bcc32 -c -q -WR -o ringo.obj ringo.cpp

ringo.cpp:

> bcc32 -c -q -WR -DGERORGERINGO_DLL -o georgeringo.obj georgeringo.cpp

georgeringo.cpp:

Здесь опция компилятора -WR используется для указания того, что применяется динамический вариант рабочей библиотеки. Эти три команды сгенерируют объектные файлы george.obj, ringo.obj и georgeringo.obj. Затем введите команду:

> bcc32 -q -WD -WR -elibgeorgeringo.dll george.obj ringo.obj georgeringo.obj

Она сгенерирует динамическую библиотеку libgeorgeringo.dll. Наконец, введите команду:

> implib -с libgeorgeringo.lib libgeorgeringo.dll

Она сгенерирует библиотеку импорта libgeorgeringo.lib.

Обсуждение

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

Видимость символов

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

В случае с большинством инструментариев для Windows, чтобы символ, определенный в динамической библиотеке, был доступен коду, использующему эту библиотеку, он должен быть явно экспортирован при сборке динамической библиотеки и импортирован при сборке исполняемого файла или другой динамической библиотеки, использующей эту библиотеку. Некоторые инструменты для Unix также предлагают такие возможности. Это верно для последних версий GCC для некоторых платформ, для Metrowerks на Mac OS X и для Intel для Linux. Однако в некоторых случаях нет никакого выбора, кроме как сделать все символы видимыми.

Передача библиотек компоновщику

В Unix динамическая библиотека может быть указана как входной файл компоновщика при компоновке кода, использующего эту динамическую библиотеку. В Windows, кроме случаев использования GCC, динамические библиотеки не указываются напрямую как вход компоновщика, а вместо этого используется библиотека импорта или файл определения модуля.

Библиотеки импорта и файлы определения модулей

Библиотеки импорта, грубо говоря, являются статическими библиотеками, содержащими информацию, необходимую для вызова функций, содержащихся в DLL, во время исполнения. Нет необходимости знать, как они работают, надо только знать, как их создавать и использовать. Большинство компоновщиков создает библиотеки импорта автоматически при сборке DLL, но в некоторых случаях может оказаться необходимо использовать отдельный инструмент, который называется библиотекарь импорта (import librarian). В табл. 1.11 с целью избежать запутанного синтаксиса командной строки, требуемого компоновщиком Borland ilink32.exe, я использовал библиотекарь импорта Borland implib.exe.

Файл определения модуля, или файл .def — это текстовый файл, который описывает функции и данные, экспортируемые из DLL. Файл .def может быть написан вручную или автоматически сгенерирован каким-либо инструментом. Пример файла .def для библиотеки libgeorgeringo.dll показан в примере 1.5.

Пример 1.5. Файл определения модуля для libgeorgeringo.dll

LIBRARY LIBGEORGERINGO.DLL

EXPORTS

 Georgeringo @1

Экспорт символов из DLL

Имеется два стандартных метода экспорта символов из Windows DLL.

• Использование атрибута

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

Атрибут

__dеclspec(dllexport)
должен указываться в начале объявления экспортируемой функции или данных, вслед за какими-либо спецификаторами сборки, и сразу за ним должно следовать ключевое слово
class
или
struct
для экспортируемого класса. Это проиллюстрировано в примере 1.6. Заметьте, что
__declspec(dllexport)
не является частью языка С++; это расширение языка, реализованное для большинства компиляторов для Windows.

• Создание файла .def, описывающего функции и данные, экспортируемые из динамической библиотеки.

Пример 1.6. Использование атрибута __declspec(dllexport)

__declspec(dllexport) int m = 3; // Экспортируемое определение данных

extern __declspec(dllexport) int n; // Экспортируемое объявление данных

__declspec(dllexport) void f(); // Экспортируемое объявление функции class

__declspec(dllexport) c { // Экспортируемое определение класса

 /* ... */

};

Использование файла .def имеет несколько преимуществ — например, он может позволить осуществлять доступ к функциям в DLL по номеру, а не по имени, что сокращает размер DLL. Он также устраняет необходимость запутанных директив препроцессора, таких как показанные в примере 1.2 в заголовочном файле georgeringo.hpp. Однако он также имеет и несколько серьезных недостатков. Например, файл .def не может использоваться для экспорта классов. Более того, можно забыть обновить свой файл .def при добавлении, удалении или изменении функций в вашей DLL. Таким образом, я рекомендую вам всегда использовать

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

Импорт символов из DLL

Как есть два способа экспорта символов из DLL, так есть и два способа импорта символов.

• В заголовочных файлах, включенных в исходный код, использующий DLL, используйте атрибут

__declspec(dllimport)
и при сборке этого кода передайте библиотеку импорта компоновщику.

• При сборке кода, использующего DLL, укажите файл .def.

Как и в случае с экспортом символов, я рекомендую вместо файлов .def использовать в вашем исходном коде атрибут

__declspec(dllimport)
. Атрибут
__declspec(dllimport)
используется точно так же, как и атрибут
__declspec(dllexport)
, обсуждавшийся ранее. Аналогично
__declspec(dllexport)
атрибут
__declspec(dllimport)
не является частью языка С++, а является расширением языка, реализованным для большинства компиляторов для Windows.

Если вы выбрали использование

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

Вместо этого обычно используют определение макроса, который при сборке DLL расширяется как

__declspec(dllexport)
, а в противном случае — как
__declspec(dllimport)
. В примере 1.2 я использовал для этой цели макроопределение
GEORGERINGO_DECL
. В Windows, если определен символ
GEORGERINGO_SOURCE
, то
GEORGERINGO_DECL
раскрывается как
__declspec(dllexport)
, а в противном случае — как
__declspec(dllimport)
. Описанный результат вы получите, определив
GEORGERINGO_SOURCE
при сборке DLL libgeorgeringo.dll, но не определяя его при компиляции кода, использующего libgeorgeringo.dll.

Сборка DLL с помощью GCC

Порты GCC Cygwin и MinGW, обсуждавшиеся в рецепте 1.1, работают с DLL по-другому, нежели остальные инструменты для Windows. При сборке DLL с помощью GCC по умолчанию экспортируются все функции, классы и данные. Это поведение можно изменить, использовав опцию компоновщика --no-export-all-symbols, применив в исходных файлах атрибут

__declspec(dllexport)
или используя файл .def. В каждом из этих трех случаев, если вы не используете опцию --export-all-symbols, чтобы заставить компоновщик экспортировать все символы, экспортируются только те функции, классы и данные, которые помечены атрибутом
__declspec(dllexport)
или указаны в файле .def.

Таким образом, инструментарий GCC можно использовать для сборки DLL двумя способами: как обычный инструментарий Windows, экспортирующий символы явно с помощью

__declspec
, или как инструментарий Unix, автоматически экспортирующий все символы[1]. В примере 1.2 и табл. 1.11 я использовал последний метод. Если вы выберете этот метод, вы должны в целях предосторожности использовать опцию --export-all-symbols — на тот случай, если у вас окажутся заголовки, содержащие
__declspec(dllexport)
.

GCC отличается от других инструментов для Windows и еще в одном: вместо того чтобы передавать компоновщику библиотеку импорта, связанную с DLL, вы можете передать саму DLL. Это обычно быстрее, чем использование библиотеки импорта. Однако это может привести к проблемам, так как в одной и той же системе может существовать несколько версий одной DLL, и вы должны быть уверены, что компоновщик выберет правильную версию. В табл. 1.11 при демонстрации того, как создавать библиотеки импорта с помощью GCC, я решил не использовать эту возможность.

В случае с Cygwin библиотека импорта для DLL xxx.dll обычно называется xxx.dll.a, в то время как в случае с MinGW она обычно называется xxx.a. Это просто вопрос соглашения.

Опция -fvisibility в GCC 4.0

Последние версии GCC на некоторых платформах, включая Linux и Mac OS X, дают программистам возможность более тонкого управления экспортом символов из динамических библиотек: опция командной строки -fvisibility используется для указания видимости символов динамической библиотеки по умолчанию, а специальный атрибут, аналогичный

__declspec(dllexport)
в Windows, используется в исходном коде для изменения видимости символов по отдельности. Опция
-fvisibility
имеет несколько различных значений, но два наиболее интересных — это default и hidden. Грубо говоря, видимость default означает, что символ доступен для кода других модулей, а видимость hidden означает, что не доступен. Чтобы включить выборочный экспорт символов, укажите в командной строке -fvisibility=hidden и используйте атрибут visibility (видимость) для пометки символов как видимых, как показано в примере 1.7.

Пример 1.7. Использование атрибута visibility с опцией командной строки -fvisibility=hidden

extern __attribute__((visibility("default"))) int m; // экспортируется

extern int n; // не экспортируется


__attribute__((visibility("default"))) void f(); // экспортируется

void g(); // не экспортируется

struct __attribute__((visibility("default"))) S { }; // экспортируется

struct T { }; //не экспортируется

В примере 1.7 атрибут

__attribute__((visibility("default")))
играет ту же роль, что и
__declspec(dllexport)
в коде Windows.

Использование атрибута

visibility
представляет те же проблемы, что и использование
__declspec(dllexport)
и
__declspec(dllimport)
, так как вам требуется, чтобы этот атрибут присутствовал при сборке общей библиотеки и отсутствовал при компиляции кода, использующего эту общую библиотеку, и чтобы он полностью отсутствовал на платформах, его не поддерживающих. Как и в случае с
__declspec(dllexport)
и
__declspec(dllimport)
, эта проблема решается с помощью препроцессора. Например, вы можете изменить заголовочный файл georgeringo.hpp из примера 1.2 так, чтобы использовать атрибут видимости, следующим образом.

georgeringo/georgeringo.hpp


#ifndef GEORGERINGO_HPP_INCLUDED

#define GEORGERINGO_HPP_INCLUDED


// определите GEORGERINGO_DLL при сборке libgeorgeringo

#if defined(_WIN32) && !defined(__GNUC__)

#ifdef GEORGERINGO_DLL

#define GEORGERINGO_DECL __declspec(dllexport)

#else

#define GEORGERINGO_DECL __declspec(dllimport)

#endif

#else // Unix

# if defined(GEORGERINGO_DLL) && defined(HAS_GCC_VISIBILITY)

# define GEORGERINGO_DECL __attribute__((visibility("default")))

# else

#define GEORGERINGO_DECL

#endif

# endif


// Печатает "George, and Ringo\n"

GEORGERINGO_DECL void georgeringo();


#endif // GEORGERINGO_HPP_INCLUDED

Чтобы заставить это работать, вы должны при сборке в системах, поддерживающих опцию -fvisibility, определить макрос

HAS_GCC_VISIBILITY
.

Последние версии компилятора Intel для Linux также поддерживают опцию -fvisibility.

Видимость символов в Metrowerks для Mac OS X

Metrowerks для Mac OS X предоставляет несколько опций для экспорта символов из динамической библиотеки. При использовании IDE CodeWarrior вы можете использовать файл экспорта символов, который играет роль файла .def в Windows. Вы также можете экспортировать все символы с помощью опции -export all, что при сборке из командной строки является поведением по умолчанию. Я рекомендую метод, использующий для пометки в вашем исходном коде экспортируемых функций

#pragma export
, и указание в командной строке -export pragma при сборке динамической библиотеки. Использование
#pragma export
иллюстрируется в примере 1.2: просто вызовите
#pragma export on
в ваших заголовочных файлах сразу перед группой функций, которые требуется экспортировать, а сразу после нее —
#pragma export off
. Если вы хотите, чтобы ваш код работал с инструментарием, отличным от Metrowerks, вы должны поместить обращения к
#pragma export
между директивами
#ifdef
/
#endif
, как показано в примере 1.2.

Опции командной строки

Давайте кратко посмотрим на опции, использованные в табл. 1.11. Каждая строка команды определяет:

• имя (имена) входного файла (файлов): george.obj, ringo.obj и georgeringo.obj;

• имя создаваемой динамической библиотеки;

• в Windows имя библиотеки импорта.

Кроме того, компоновщик требует опции, которая говорит ему создать динамическую библиотеку, а не исполняемый файл. Большинство компоновщиков используют опцию -shared, но Visual C++ и Intel для Windows используют -dll, Borland и Digital Mars используют -WD, a GCC для Mac OS X использует -dynamiclib.

Несколько опций в табл. 1.11 способствуют более эффективному использованию динамических библиотек во время выполнения. Например, некоторым компоновщикам для Unix требуется с помощью опции -fPIC сгенерировать независимый от положения код (position- independent code) (GCC и Intel для Linux). Эта опция приводит к тому, что несколько процессов смогут использовать единственную копию кода динамической библиотеки. На некоторых системах отсутствие этой опции приведет к ошибке компоновщика. Аналогично в Windows опция компоновщика GCC --enable-auto-image-base снижает вероятность того, что операционная система попытается загрузить две динамические библиотеки в одно и то же место. Использование этой опции помогает ускорить загрузку DLL.

Передать опцию в компоновщик GCC можно через компилятор, используя опцию g++ -Wl,. (За буквой W следует строчная буква l.)

Большая часть других опций используется для указания вариантов рабочей библиотеки и описывается в рецепте 1.23.

Смотри также

Рецепты 1.9, 1.12, 1.17, 1.19 и 1.23.

1.5. Сборка сложного приложения из командной строки

Проблема

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

Решение

Начните со сборки статических и динамических библиотек, от которых зависит ваше приложение. Если библиотеки получены от сторонних разработчиков, следуйте инструкциям, поставляемым с этими библиотеками; в противном случае соберите их так, как описано в рецептах 1.3 и 1.4.

Затем скомпилируйте в объектные файлы .cpp-файлы своего приложения, как описано в разделе «Сборка простой программы «Hello, World» из командной строки». Чтобы сказать компилятору, где искать заголовочные файлы, требуемые для вашего приложения, используйте опцию -I, как показано в табл. 1.12.


Табл. 1.12. Указание директорий для поиска заголовочных файлов

Инструментарий Опция
Все -I<директория>

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

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

Таблица 1.13 предоставляет команды для компоновки приложения hellobeatles из примера 1.3. Она предполагает, что:

• текущей директорией является hellobeatles;

• статическая библиотека libjohnpaul.lib или libjohnpaul.а была создана в директории johnpaul;

• динамическая библиотека georgeringo.dll, georgeringo.so или georgeringo.dylib и, если есть, ее библиотека импорта были созданы в директории georgeringo.

Так как Comeau, как сказано в рецепте 1.4, не может создавать динамические библиотеки, строка для Comeau в табл. 1.13 предполагает, что libgeorgeringo была создана как статическая, а не как динамическая библиотека. Чтобы собрать libgeorgeringo как статическую библиотеку, в примере 1.2 удалите из объявления функции

georgeringo()
модификатор
GEORGERINGO_DECL
.


Табл. 1.13. Команды для компоновки приложения hellobeatles.exe

Инструментарий Входные файлы Командная строка
GCC (Unix) hellobeatles.o libjohnpaul.a libgeorgeringo.so g++ -о hellobeatles hellobeatles.o -L ./johnpaul -L./georgeringo -ljohnpaui -lgeorgeringo или g++ -o hellobeatles hellobeatles.o ./johnpaul/libjohnpaul.a ./georgeringo/libgeorgeringo.so
Intel (Linux) icpc -o hellobeatles hellobeatles.o -L./johnpaul -L./georgeringo -ljohnpaul -lgeorgeringo или icpc -о hellobeatles hellobeatles.o ./johnpaul/libjohnpaul.a ./georgeringo/libgeorgeringo.so
Comeau (Unix) como -no_prelink_verbose -o hellobeatles hellobeatles.o -L./johnpaul L./georgeringo -ljohnpaul -lgeorgeringo или como -no_prelink_verbose -o hellobeatles hellobeatles.о ./johnpaul/libjohnpaul.a ./georgeringo/libgeorgeringo.a
GCC (Mac OS X) hellobeatles.о libjohnpaul.a libgeorgeringo.dylib g++ -o hellobeatles hellobeatles.o -L/johnpaul -L./georgeringo -ljohnpaul -lgeorgeringo или g++ -o hellobeatles hellobeatles.o ./johnpaul/libjohnpaul.a ./georgeringo/libgeorgeringo.dylib
Metrowerks (Mac OS X) mwld -o hellobeatles hellobeatles.о -search -L/johnpaul -search -L ./georgeringo -ljohnpaui -lgeorgeringo или mwld -о hellobeatles hellobeatles.о ./johnpaul/libjohnpaul.a ./georgeringo/libgeorgeringo.dylib
GCC (Cygwin) hellobeatles.о libjohnpaul.a libgeorgeringo.dll.a g++ -о hellobeatles hellobeatles.o -L./johnpaul -L./georgeringo -Ijohnpaul -Igeorgeringo или g++ -o hellobeatles hellobeatles.о ./johnpaul/libjohnpaul.a ./georgeringo/libgeorgeringo.dll.a
GCC (MinGW) hellobeatles.о libjohnpaul.a libgeorgeringo.a g++ -o hellobeatles hellobeatles.o -L./johnpaul -L./georgeringo -Ijohnpaul -Igeorgeringo или g++ -о hellobeatles hellobeatles.o ./johnpaul/libjohnpaul.a. /georgeringo/libgeorgeringo.a
Visual C++ hellobeatles.obj libjohnpaul.lib libgeorgeringo.lib link -nologo -out:hellobeatles.exe -libpath:./johnpaul -libpath:./georgeringo libjohnpaul.lib libgeorgeringo.lib hellobeatles.obj
Intel (Windows) xilink -nologo -out:hellobeatles -libpath:./johnpaul -libpath:./georgeringo.lib johnpaul.lib libgeorgeringo.lib hellobeatles.obj
Metrowerks (Windows) mwld -o hellobeatles -search -L./johnpaul libjohnpaul.lib -search -L./georgeringo libgeorgeringo.lib hellobeatles.obj
Metrowerks (Mac OS X)¹ mwld -o hellobeatles hellobeatles.o -search -L./johnpaul -search -L./georgeringo libjohnpaul libgeorgeringo.dylib
CodeWarrior 10.0 (Mac OS X)² Сверьтесь с документацией Metrowerks
Borland bcc32 -q -WR -WC -ehellobeatles -L./johnpaul -L./georgeringo/libjohnpaul.lib libgeorgeringo.lib hellobeatles.obj
Digital Mars link -noi hellobeatles.obj,hellobeatles.exe,NUL,user32.lib kernel32.lib ..\johnpaul\ .\georgeringo\libjohnpaul.lib libgeorgeringo.lib,, или link -noi hellobeatles.obj,hellobeatles.exe,NUL,user32.lib kernel32.lib ..\johnpaul\libjohnpaul.lib ..\georgeringo\libgeorgeringo.lib,,
Comeau (Windows) hellobeatles.obj libjohnpaul.lib libgeorgeringo.lib como -no_prelink_verbose -o hellobeatles ./johnpaul/libjohnpaul.lib ./georgeringo/libgeorgeringo.lib hellobeatles.obj

¹ При сборке с помощью указанной командной строки hellobeatles может работать неправильно, так как это приложение будет использовать две копии рабочих библиотек Metrowerks. (См. рецепт 1.23.)

² CodeWarrior 10.0 для Mac OS X будет содержать динамический вариант своих библиотек. При сборке hellobeatles следует использовать именно их. (См. рецепт 1.23.)


Например, при использовании Microsoft Visual Studio .NET 2003 и если она установлена в стандартную директорию на диске С, чтобы собрать hellobeatles.exe из командной строки, перейдите в директорию hellobeatles и введите следующие команды.

> "С:Program Files\Microsoft Visual Studio .NET 2003\VC\bin\vcvars32.bat"

Setting environment for using Microsoft Visual Studio 2005 tools.

(IF you have another version of Visual Studio or Visual C++ installed

and wish to use its tools from the command line, run vcvars32.bat for

that version.)

> cl -c -nologo -EHsc -GR -Zc:forScope -Zc:wchar_t -MD -I.. -Fohollobeatles hellobeatles.cpp

hellobeatles.cpp

> link -nologo -out:hellobeatles.exe -libpath:../johnpaul -libpath:../georgeringo libjohnpaul.lib libgeorgeringo.lib

> hellobeatles.obj

Обсуждение
Поиск включенных заголовочных файлов

Опция -I используется для указания пути, где находятся заголовочные файлы. Когда компилятор — а на самом деле препроцессор — встречает директиву

include
в виде:

#include "file"

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

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

#include 

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

Передача библиотек компоновщику

Есть несколько интересных аспектов, связанных с командными строками из табл. 1.13.

В Windows вход компоновщика состоит из объектных файлов, статических библиотек и библиотек импорта. В Unix он состоит из объектных файлов, статических и динамических библиотек.

Как в Windows, так и в Unix библиотеки могут передаваться компоновщику двумя способами:

• с помощью указания пути в командной строке;

• с помощью указания только имени библиотеки и места поиска библиотек.

Таблица 1.13 иллюстрирует оба метода.

Места поиска библиотек обычно могут быть указаны в командной строке. Большинство компоновщиков для этой цели используют опцию -L, но Visual C++ и Intel для Windows используют опцию -lipath: , a Metrowerks использует опцию -search -L. Компоновщик Digital Mars позволяет указывать пути поиска библиотек в командной строке вместе с файлами библиотек, при условии, что пути поиска отличаются от файлов библиотек завершающей обратной косой чертой. Также он требует, чтобы эти обратные слеши использовались как разделители в путях.

Comeau в Windows не имеет опции для указания путей поиска библиотек.

Кроме явно указанных директорий компоновщики обычно используют список собственных директорий, который часто может быть настроен с помощью переменных среды. В Windows список директорий обычно включает lib-поддиректорию пути установки инструментария. В результате, если скопировать .lib-файлы в эту директорию, их можно будет указать в командной строке по имени, не указывая их местоположения. Если объединить этот метод с действиями, описанными в рецепте 1.25, то можно вообще избежать передачи компоновщику какой-либо информации о библиотеке.

Способ, которым имя библиотеки передается компоновщику, для Unix и Windows различается. В Windows указывается полный путь библиотеки, включая расширение файла. В Unix — и в Windows при использовании инструментария GCC — библиотеки указываются с помощью опции -l, за которой следует имя библиотеки с удаленными из него расширением файла и префиксом lib. Это означает, что для того, чтобы компоновщик автоматически находил библиотеку, ее имя должно начинаться с префикса lib. Еще интереснее то, что это дает компоновщику возможность выбрать между несколькими версиями библиотек. Если компоновщик находит как статическую, так и динамическую версии библиотеки, выбирается, если не указано другого, динамическая библиотека. На некоторых системах компоновщик может выбрать между несколькими версиями динамической библиотеки, используя часть имени файла, за которой следует .so.

Metrowerks поддерживает как Windows, так и Unix-стили указания имен библиотек.

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

Запуск вашего приложения

Если ваше приложение использует динамический вариант библиотеки времени исполнения инструментария, то эта библиотека должна быть доступна приложению при его запуске и должна находиться в таком месте, где динамический загрузчик операционной системы сможет автоматически найти ее. Обычно это означает, что динамическая библиотека времени исполнения должна находиться либо в той же директории, что и ваше приложение, либо в одной из директорий, указанных системе. Это больше относится к разработке для Windows, чем для Unix, так как в Unix соответствующие библиотеки обычно уже установлены по правильным путям. Имена динамических библиотек времени исполнения, поставляемых с различным инструментарием, приведены в рецепте 1.23.

Смотри также

Рецепты 1.10, 1.13, 1.18 и 1.23.

1.6. Установка Boost.Build

Проблема

Вы хотите получить и установить Boost.Build.

Решение

Обратитесь к документации Boost.Build по адресу www.boost.org/boost-build2 или выполните эти шаги.

1. Перейдите на домашнюю страницу Boost — www.boost.org и проследуйте по ссылке Download (скачать) на страницу SourceForge Boost.

2. Скачайте и распакуйте либо самый последний релиз пакета boost, либо самый последний релиз пакета boost-build. Первый включает полный набор библиотек Boost, а второй — это отдельный релиз Boost.Build. Распакованные файлы поместите в подходящую временную директорию.

3. Скачайте и распакуйте последнюю версию пакета boost-jam для вашей платформы. Этот пакет включает собранный исполняемый файл bjam. Если пакет boost-jam для вашей платформы недоступен, то для сборки исполняемого файла из исходников следуйте инструкциям, прилагаемым к пакету, скачанному вами на шаге 2.

4. Скопируйте bjam в директорию, указанную в переменной среды

PATH
.

5. Установите переменную среды

BOOST_BUILD_PATH
в значение корневой директории BoostBuild. Если вы на шаге 1 скачали пакет boost, то корневая директория — это поддиректория tools/build/v2 установки Boost, а в противном случае это директория boost-build.

6. Настройте BoostBuild на ваш инструментарий и библиотеки, отредактировав файл user-config.jam, расположенный в корневой директории Boost.Build. Файл user-config.jam содержит комментарии, поясняющие, как это сделать.

Обсуждение

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

Целью пятого шага является помощь инструменту сборки — bjam в поиске корневой директории системы сборки. Однако этот шаг необязателен, так как есть другой способ выполнить эту же задачу: просто создайте файл, который называется boost-build.jam, с единственной строкой:

boost-build boost-build-root ;

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

Шестой шаг, вероятно, является наиболее сложным, но на практике он обычно довольно прост. Если у вас установлена только одна версия инструментария, и она установлена в стандартном месте, то файл user-config.jam может содержать всего одну строку вида:

using  ;

Например, при использовании Visual C++ будет достаточно следующего:

using msvc ;

А при использовании GCC просто напишите:

using gcc ;

Дела становятся несколько более сложными при использовании нескольких версий инструментария или при установке инструментария не по стандартному пути. Если ваш инструментарий установлен в нестандартную директорию, скажите Boost.Build, где искать его, передав ему в качестве третьего аргумента

using
команду на вызов компилятора инструментария. Например:

using msvc : : "С:/Tools/Compilers/Visual Studio/Vc7/bin/cl" ;

Если у вас установлено несколько версий инструментария, вы можете указать правило

using
несколько раз с одним и тем же именем инструментария, передавая ему в качестве второго аргумента номер версии, а в качестве третьего — команду компилятора. Например, чтобы настроить две версии инструментария Intel, используйте следующее:

using intel : 7.1 : "C:/Program Files/Intel/Compiler70/IA32/Bin/icl" ;

using intel : 8.0 : "C./Program Files/Intel/CPP/Compiler80/IA32/Bin/icl" ;

Имена, используемые Boost.Build для нескольких разных инструментариев, описываемых в этой главе, приведены в табл 1.14.


Табл. 1.14. Имена инструментариев Boost.Build

Инструментарий Имя
GCC gcc
Visual C++ msvc
Intel intel
Metrowerks cw
Comeau como
Borland borland
Digital Mars dmc

1.7. Сборка простого приложения «Hello, World» с помощью Boost.Build

Проблема

Вы хотите собрать простую программу «Hello, World», подобную приведенной в примере 1.4, с помощью BoostBuild.

Решение

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

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

Например, чтобы собрать исполняемый файл hello или hello.exe из файла hello.cpp из примера 1.4, создайте в директории, содержащей файл hello.cpp, файл с именем Jamroot с содержимым, показанным в примере 1.8.

Пример 1.8. Jamfile для проекта hello

# jamfile для проекта hello

exe hello : hello.cpp ;

install dist : hello : . ;

Далее перейдите в директорию, содержащую hello.cpp и Jamroot, и введите следующую команду.

> bjam hello

Эта команда собирает исполняемый файл hello или hello.exe в поддиректории текущей директории. Наконец, введите команду:

> bjam dist

Эта команда копирует исполняемый файл в директорию, указанную в свойстве

location
, которое в нашем случае равно текущей директории.

В момент сдачи этой книги в печать разработчики Boost.Build готовят официальный релиз BoostBuild версии 2. К моменту, когда вы будете это читать, версия 2 уже, возможно, будет выпущена. Если нет, вы можете задействовать поведение, описанное в этой главе, передав в bjam опцию командной строки --v2. Например, вместо ввода

bjam hello
введите
bjam --v2 hello
.

Обсуждение

Файл Jamroot является примером файла Jamfile. В то время как для управления небольшим набором исходных файлов C++ можно использовать один Jam-файл, большой набор файлов обычно требует нескольких Jam-файлов с иерархической организацией. Каждый Jam-файл находится в отдельной директории и соответствует отдельному проекту. Большая часть Jam-файлов просто называется Jamfile, но самый верхний Jam-файл — Jam-файл, который расположен в директории, родительской по отношению ко всем другим директориям, содержащим остальные Jam-файлы, — называется Jamroot. Проект, определяемый этим верхним Jam- файлом, называется корнем проекта. Каждый проект, за исключением корня проекта, имеет родительский проект определяемый проектом, расположенным в ближайшей к нему родительской директории, содержащей Jam-файл.

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

Каждый проект — это набор целей. Цели объявляются с помощью вызова правил, таких как правило

exe
и правило
install
. Большая часть целей соответствует двоичным файлам или, более точно, набору связанных двоичных файлов, таких как отладочная и финальная (релиз) сборки приложения.

Правило

exe
используется для объявления исполняемой цели. Вызов этого правила имеет вид, показанный в примере 1.9.

Пример 1.9. Вызов правила exe

exe имя-целевого-файла

 : исходные-файлы

 : требования

 : сборка-по-умолчанию

 : требования-к-использованию

 ;

Здесь

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

Свойства указываются в виде

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

exe hello

 : hello.cpp

 : multi

 ;

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

Некоторые часто используемые функции и их возможные значения перечислены в табл. 1.15.


Табл. 1.15. Часто используемые функции Boost.Build

Функция Значение Эффект
include Path Определяет путь для поиска заголовочных файлов
define name=[value] Определяет макрос
threading
multi
или
single
Включает или отключает поддержку потоков
runtime-link
static
или
shared
Определяет тип компоновки с библиотекой времени выполнения¹
variant
debug
или
release
Запрашивает отладочную или окончательную сборку

¹ См. рецепт 1.23.

Когда собирается целевой исполняемый файл, или цель, соответствующая статической или динамической библиотеке, файл, соответствующий этой цели, создается в директории, дочерней по отношению к директории, содержащей Jam-файл. Относительным путь этой директории зависит от инструментария и конфигурации сборки, но он всегда начинается с bin. Например, исполняемый файл из примера 1.8 может быть создан в директории bin/msvc/debug.

Для простоты я попросил вас создать Jam-файл из примера 1.8 в той же директории, в которой находится исходный файл hello.cpp. Однако в реальных проектах вам часто придется хранить исходные и двоичные файлы в различных директориях. В примере 1.8 Jam-файл можно поместить в любое место при условии, что вы укажете путь hello.cpp так, что он будет указывать на реальный файл hello.cpp.

Правило

install
указывает Boost.Build скопировать один или несколько файлов, указанных как имена файлов или как имена главных целей, в указанное место. Вызов этого правила имеет вид, показанный в примере 1.10.

Пример 1.10. Вызов правила install

install имя-цели

 : файлы

 : требования

 : сборка-по-умолчанию

 : требования-к-использованию

 ;

Здесь

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

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

location
требований цели. Например, в примере 1.8 можно написать цель
install
следующим образом.

install . : hello ;

Затем установка исполняемого файла выполняется так:

> bjam .

Однако метод, использованный в примере 1.8, предпочтителен, так как проще запомнить именованную цель, чем путь файла.

Наконец, давайте быстро взглянем на синтаксис командной строки bjam. Чтобы собрать цель

xxx
, используя инструментарий по умолчанию, введите команду:

> bjam xxx

Чтобы собрать цель

xxx
, используя инструментарий
yyy
, введите команду:

> bjam xxx toolset=yyy

Чтобы собрать цель

xxx
, используя версию
vvv
инструментария
yyy
, введите команду:

> bjam хххtoolset=yyy-vvv

Чтобы в командной строке указать использовать при сборке стандартную библиотеку

zzz
, используйте синтаксис:

> bjam xxx stdlib=zzz

Чтобы собрать несколько целей одновременно, введите в командной строке несколько имен целей, а чтобы собрать все цели данного проекта, не указывайте целей. Следовательно, чтобы собрать и установить исполняемый файл из примера 1.9, просто введите:

> bjam

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

> bjam --clean

Свойство в виде

<функция>значение
может быть указано в командной строке как
функция=значение
.

Смотри также

Рецепты 1.2 и 1.15.

1.8. Сборка статической библиотеки с помощью Boost.Build

Проблема

Вы хотите использовать Boost.Build для сборки статической библиотеки из набора исходных файлов С++, таких как перечисленные в примере 1.1.

Решение

В директории, где вы хотите создать статическую библиотеку, создайте файл Jamroot. В файле Jamroot вызовите правило

lib
, объявляющее целевую библиотеку, указав в качестве исходных файлов свои файлы .cpp и используя в качестве требования свойство
static
. Чтобы указать директорию поиска заголовочных файлов библиотеки, т. е. директорию, относительно которой должны разрешаться директивы
include
для заголовочных файлов этой библиотеки, добавьте требование к использованию в виде
путь
. Чтобы указать компилятору, где искать включенные заголовки, может потребоваться использовать несколько директив вида
путь
. Наконец, в директории, содержащей Jamroot, запустите bjam, как описано в рецепте 1.7.

Например, чтобы собрать статическую библиотеку из исходных файлов, перечисленных в примере 1.1, ваш Jamroot может выглядеть как в примере 1.11.

Пример 1.11. Jam файл для сборки статической библиотеки libjohnpaul.lib или libjohnpaul.a

# Jamfile для проекта libjohnpaul

lib libjohnpaul

: # исходники

john.cpp paul.cpp johnpaul.cpp

: # требования

static

: # сборка-по-умолчанию

: # требования-к-использованию

..

;

Чтобы собрать библиотеку, введите:

> bjam libjohnpaul

Обсуждение

Правило

lib
используется для объявления цели, представляющей статическую или динамическую библиотеку. Как показано в примере 1.9, оно имеет такой же вид, что и правило exe. Использование требования
..
освобождает проект, который зависит от вашей библиотеки, от необходимости явно указывать в своих требованиях директорию заголовочных файлов вашей библиотеки. Требование
static
указывает, что ваша цель должна всегда собираться как статическая библиотека. Если вы хотите сохранить возможность сборки целевой библиотеки как статической и как динамической, опустите требование
static
. Должна ли библиотека собираться как статическая или как динамическая, может быть указано в командной строке или в требованиях цели, которая зависит от целевой библиотеки. Например, если в примере 1.11 требование
static
опустить, то чтобы собрать цель
libjohnpaul
как статическую библиотеку, потребуется ввести команду:

> bjam libjohnpaul link=static

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

Смотри также

Рецепты 1.3, 1.11 и 1.16.

1.9. Сборка динамической библиотеки с помощью Boost.Build

Проблема

Вы хотите использовать Boost.Build для сборки динамической библиотеки из набора исходных файлов С++, таких как перечисленные в примере 1.2.

Решение

В директории, где вы хотите создать динамическую библиотеку и, если надо, библиотеку импорта, создайте файл Jamroot. В файле Jamroot вызовите правило

lib
, объявляющее целевую библиотеку, указав в качестве исходных файлов свои файлы .cpp и используя в качестве требования свойство
shared
. Чтобы указать директорию поиска заголовочных файлов библиотеки, т.е. директорию, относительно которой должны разрешаться директивы
include
для заголовочных файлов этой библиотеки, добавьте требование к использованию в виде
путь
. Если исходные файлы включают заголовки от других библиотек, то чтобы сказать компилятору, где искать заголовочные файлы, вам может потребоваться добавить несколько требований в виде
путь
. Чтобы гарантировать, что символы вашей динамической библиотеки будут экспортированы в Windows с помощью директивы
__declspec(dllexport)
, вам также может потребоваться добавить одно или несколько требований в виде
символ
. Наконец, в директории, содержащей Jamroot, запустите bjam, как описано в рецепте 1.7.

Например, чтобы собрать из исходных файлов, перечисленных в примере 1.2, динамическую библиотеку, создайте в директории georgeringo файл с именем Jamroot, показанный в примере 1.12.

Пример 1.12. Jam-файл для сборки динамической библиотеки georgeringo.so, georgeringo.dll или georgeringo.dylib

# Jamfile для проекта georgeringo

lib libgeorgeringo

 : # исходники

  george.cpp ringo.cpp georgeringo.cpp

 : # требования

  shared

  GEORGERINGO_DLL

 : # сборка-по-умолчанию

 : # требования-к-использованию

  ..

 ;

Чтобы собрать библиотеку, введите:

> bjam libgeorgeringo

Обсуждение

Как обсуждалось в рецепте 1.8, правило

lib
используется для объявления цели, представляющей статическую или динамическую библиотеку. Использование требования
..
освобождает проект, который зависит от вашей библиотеки, от необходимости явно указывать в своих требованиях директорию заголовочных файлов вашей библиотеки. Требование
shared
указывает, что цель должна всегда собираться как динамическая библиотека. Если вы хотите иметь возможность собирать библиотеку и как статическую, и как динамическую, опустите требование
shared
и укажите это свойство в командной строке или в требованиях цели, которая зависит от вашей целевой библиотеки. Однако написание библиотеки, которая может быть собрана и как статическая, и как динамическая, требует особого внимания, так как для правильного экспорта символов в Windows требуется использовать директивы препроцессора. Хорошим упражнением является переписывание примера 1.2 так, чтобы его можно было собрать и как статическую, и как динамическую библиотеку.

Смотри также

Рецепты 1.4, 1.12, 1.17 и 1.19.

1.10. Сборка сложного приложения с помощью BoostBuild

Проблема

Вы хотите использовать Boost.Build для сборки исполняемого файла, зависящего от нескольких статических и динамических библиотек.

Решение

Выполните следующие шаги.

1. Для каждой библиотеки, от которой зависит исполняемый файл, — при условии, что она не распространяется в виде готового бинарного файла, — создайте Jam-файл, как описано в рецептах 1.8 и 1.9.

2. В директории, где вы хотите создать исполняемый файл, создайте файл Jamroot.

3. В файле Jamroot вызовите правило exe, объявляющее целевой исполняемый файл. Укажите свои файлы .cpp и цели библиотек, от которых исполняемый файл зависит как от источников. Также, если требуется, добавьте свойства вида

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

4. В файле Jamroot вызовите правило

install
, определяющее в качестве требований свойства
on
,
EXE
и
SHARED_LIB
.

5. В директории, содержащей Jamroot, запустите bjam, как описано в рецепте 1.7.

6. Например, чтобы собрать из исходных файлов, перечисленных в примере 1.3, исполняемый файл, создайте в директории hellobeatles файл с именем Jamroot, показанный в примере 1.13.

Пример 1.13. Jam-файл для сборки исполняемого файла hellobeatles.exe или hellobeatles

# Jamfile для проекта hellobeatles

exe hellobeatles

 : # исходники

 ../johnpaul//libjohnpaul

 ../georgeringo//libgeorgeringo

 hellobeatles.cpp

 ;


install dist

 : # исходники

  hellobeatles

 : # требования

  on

  EXE

  SHARED_LIB

  .

 ;

Теперь введите:

> bjam hellobeatles

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

hellobeatles
. Наконец, введите:

> bjam dist

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

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

Обсуждение
Цели библиотек

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

path//target-name
. В рецептах 1.8 и 1.9 я показал, как объявлять цель для сборки библиотеки из исходного кода с помощью Boost.Build Однако если библиотека доступна в виде готового двоичного файла, вы можете объявить цель для нее следующим образом.

lib имя-цели

 :

 : имя-файла

 ;

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

lib имя цели

 :

 : имя-файла требования

 ;

lib имя-цели

 : другое-имя-файла другие-требования

 ;

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

lib cryptolib

 :

 :  ../libraries/cryptolib/cryptolib_debug.lib

  debug

 ;

 lib cryptolib

 :  ../libraries/cryptolib/cryptolib.lib

  release

 ;

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

lib имя-цели

 : имя-библиотеки

 ;

Здесь

имя-библиотеки
— это имя, которое должно быть передано компоновщику и которое может отличаться от реального имени файла, как обсуждалось в рецепте 1.5. Чтобы дать указание компоновщику искать в определенной директории, напишите

lib имя-цели

 : имя-библиотеки

 путь-к-библиотеке

 ;

Установка

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

install-dependencies
, которая позволяет вам указать только главный исполняемый файл и тип зависимостей, которые должны быть установлены. В примере 1.13 требование
on
включает функцию
install-dependencies
, а требования
EXE
и
SHARED_LIB
говорят BoostBuild установить все зависимости, которые являются исполняемыми файлами или динамическими библиотеками. Другие возможные значения функции
install-type
включают
LIB
и
IMPORT_LIB
.

Организация проекта

Все три Jam-файла, используемые при сборке исполняемого файла hellobeatles, называются Jamroot. Это хорошо для такого простого проекта, но обычно следует организовывать набор Jam-файлов в иерархию с единственным высшим Jam-файлом, определяющим корень проекта. Организация проектов подобным образом позволяет использовать некоторые из более сложных функций Boost.Build's, таких как наследование свойств дочерними проектами. Одним из способов сделать такую организацию в нашем случае является изменение имен Jam-файлов в директориях johnpaul, georgeringo и hellobeatles с Jamroot на Jamfile и добавление файла Jamroot со следующим содержимым в родительскую директорию.

# jamfile для примера приложения

build-project hellobeatles ;

Правило

build-project
просто говорит bjam собрать данный проект, который может быть указан либо по пути, либо с помощью символьного идентификатора. Если вы перейдете в директорию, содержащую Jamroot, и запустите bjam, то будут собраны три дочерних проекта.

Смотри также

Рецепты 1.5, 1.13 и 1.18.

1.11. Сборка статической библиотеки с помощью IDE

Проблема

Вы хотите использовать IDE для сборки статической библиотеки из набора исходных файлов С++, таких как перечисленные в примере 1.1.

Решение

Основная процедура выглядит следующим образом.

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

2. Выберите конфигурацию сборки (т. е. отладочную или окончательную версию и поддержку или отсутствие поддержки многопоточности).

3. Укажите имя библиотеки и директорию, в которой она должна быть создана.

4. Добавьте в проект исходные файлы.

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

6. Соберите проект.

Шаги этой процедуры могут варьироваться в зависимости от IDE — например, для некоторых IDE некоторые шаги будут объединены в один или изменится их порядок. Второй шаг подробно описывается в рецептах 1.21, 1.22 и 1.23. А сейчас вы. насколько это возможно, должны использовать параметры по умолчанию.

Например, вот как надо собирать статическую библиотеку из исходных файлов из примера 1.1 с помощью Visual C++ IDE.

В меню File выберите New→Project, в левой панели выберите Visual С++[2], выберите Win32 Console Application и введите в качестве имени проекта libjohnpaul. В мастере Win32 Application Wizard перейдите в раздел Application Settings (Параметры приложения), выберите Static library, отключите опцию Precompiled header (Прекомпилированные заголовочные файлы) и нажмите на Finish (Готово). Теперь у вас должен иметься пустой проект с двумя конфигурациями сборки — Debug и Release, и первая будет активной.

Затем, сделав щелчок правой кнопкой мыши на Solution Explorer и выбрав Properties, отобразите страницы свойств проекта. Перейдите в раздел Configuration Properties (Свойства конфигурации)→Librarian (Библиотекарь)→General (Общие) и в поле с именем Output File (Выходной файл) введите имя и путь выходного файла проекта. Директория этого пути должна указывать на директорию binaries, созданную в начале этой главы, а имя должно быть libjohnpaul.lib.

Наконец, чтобы добавить в проект исходные файлы из примера 1.1, используйте Add Existing Item (добавить существующий элемент) из меню Project. Теперь страницы свойств проекта должны содержать узел с именем «C/C++». Перейдите к Configuration Properties→C/C++→Code Generation (Генерация кода) и укажите в качестве библиотеки времени выполнения Multi-threaded Debug DLL (многопоточная отладочная динамическая библиотека). Теперь можно собрать проект, выбрав в меню Build пункт Build Solution. Проверьте, что в директории binaries был создан файл с именем libjohnpaul.lib.

Вместо использования опции Add Existing Item, добавляющей в проект исходные файлы из примера 1.1, можно использовать Add New Item (Добавить новый элемент), добавляющую в проект пустые исходные файлы. После этого во вновь созданные файлы требуется ввести или вставить через буфер обмена содержимое из примера 1.1. Аналогичные замечания действительны и для других IDE.

Обсуждение

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

При изучении того, как использовать новую IDE, вам следует обратить особое внимание на следующие моменты.

• Как создать новый проект.

• Как указать тип проекта (исполняемый файл, статическая библиотека или динамическая библиотека).

• Как добавить в проект существующий файл.

• Как создать и добавить в проект новый файл.

• Как указать имя выходного файла проекта.

• Как указать пути для включаемых заголовков.

• Как указать пути для поиска библиотек.

• Как указать библиотеки, от которых зависит проект.

• Как собрать проект.

• Как организовать набор проектов в группу и указать их зависимости.

Этот рецепт демонстрирует многие из этих функций. Большая часть других функций описывается в рецептах 1.12 и 1.13.

Теперь давайте посмотрим на то, как собрать статическую библиотеку с помощью CodeWarrior, C++Builder и Dev-C++.

CodeWarrior

В меню File выберите New… и в диалоге New выберите вкладку Project. В качестве имени проекта введите

libjohnpaul.mcp
, выберите место для сохранения настроечных файлов проекта и дважды щелкните мышью на Mac OS C++ Stationery. В диалоге New Project раскройте узел Mac OS X Mach-O and Standard Console, а затем дважды щелкните на C++ Console Mach-O. Теперь у вас должен быть проект с двумя целями — Mach-O C++ Console Debug и Mach-O C++ Console Final, и активной должна быть первая из них.

Так как при создании проекта, зависящего от этого проекта, вам придется ссылаться на эти цели по их имени, им следует дать понятные имена. Сейчас переименуйте только отладочную цель. Выберите вкладку Targets окна проекта и дважды щелкните мышью на имени отладочной цели, чтобы: отобразить окно Target Settings (Параметры цели). Затем перейдите к Target→Target Settings и в первом поле Target Name (Имя цели) введите

libjohnpaul Debug
.

Далее в окне Target Settings перейдите к Target→PPC Mac OS X Target. В качестве Project Туре (Тип проекта) укажите Library, а в поле с именем File Name (Имя файла) введите

libjohnpaul.а
. Чтобы указать в качестве места для создания выходного файла libjohnpaul.a директорию binaries; перейдите к Target→Target Settings и нажмите на Choose….

Наконец выберите вкладку files окна проекта и удалите существующие исходные файлы и файлы библиотек, перетащив их в Trash (корзину). Затем, чтобы добавить в проект исходные файлы из примера 1.1, используйте Add Files (Добавить файлы) из меню Project. Теперь можно собрать проект, выбрав в меню Project пункт Make. Проверьте, что в директории binaries был создан файл с именем libjohnpaul.a.

C++Builder

В меню File выберите New→Other и выберите Library. Теперь у вас должен иметься пустой проект. В меню File выберите Save Project As, выберите директорию для сохранения настроечных файлов проекта и в качестве имени проекта введите libjohnpaul.bpr.

Затем, чтобы отобразить диалог Project Options (Параметры проекта), в меню Project выберите Options. Затем перейдите в Directories and Conditionals (Директории и условия) и используйте элемент управления рядом с надписью Final output (Окончательный вывод), чтобы указать, где должен создаваться выходной файл libjohnpaul.lib. По умолчанию этот файл будет создан в той же директории, где находится libjohnpaul.bpr, но вы должны сказать C++Builder, что его требуется создать в директории binaries. Если хотите, то также можно использовать элемент управления рядом с Intermediate output (Промежуточный вывод) и указать место создания объектных файлов. По умолчанию они создаются в той же директории, где находятся исходные файлы.

Наконец, чтобы добавить в проект исходные файлы из примера 1.1, используйте Add to Project (Добавить в проект) из меню Project. Теперь можно собрать проект, выбрав в меню Project пункт Make libjohnpaul. Проверьте, что в директории binaries был создан файл с именем libjohnpaul.lib.

Dev-C++

В меню File выберите New→Project. В диалоге New project (Новый проект) выберите Static Library и C++ Project и в качестве имени проекта введите libjohnpaul. После нажатия на OK укажите место для сохранения настроечных файлов проекта.

Затем, чтобы отобразить диалог Project Options, в меню Project выберите Project Options. Затем перейдите к Build Options и проверьте, что в качестве имени выходного файла проекта указано libjohnpaul.a. В поле Executable output directory (Директория для записи исполняемого файла) введите путь к директории binaries. Если хотите, то в поле Object File output directory (Директория для записи объектных файлов) можно указать директорию для создания объектных файлов.

Наконец, чтобы добавить в проект исходные файлы из примера 1.1, используйте Add to project (Добавить в проект) из меню Project. Теперь можно собрать проект, выбрав в меню Execute пункт Compile. Проверьте, что в директории binaries был создан файл с именем libjohnpaul.a.

Смотри также

Рецепты 1.3, 1.8 и 1.16.

1.12. Сборка динамической библиотеки с помощью IDE

Проблема

Вы хотите использовать IDE для сборки динамической библиотеки из набора исходных файлов С++, таких как перечисленные в примере 1.2.

Решение

Основная процедура выглядит следующим образом.

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

2. Выберите конфигурацию сборки (т. е. отладочную или окончательную версию и поддержку или отсутствие поддержки многопоточности).

3. Укажите имя библиотеки и директорию, в которой она должна быть создана.

4. Добавьте в проект исходные файлы.

5. В Windows определите макросы, необходимые для организации экспорта символов динамической библиотеки с помощью

__declspec(dllexport)
.

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

7. Соберите проект.

Как и в рецепте 1.11, шаги в этой процедуре будут различаться в зависимости от IDE. Второй шаг подробно описывается в рецептах 1.21, 1.22 и 1.23. А сейчас вы, насколько это возможно, должны использовать параметры по умолчанию.

Например, вот как надо собирать динамическую библиотеку из исходных файлов из примера 1.2 с помощью Visual C++ IDE.

В меню File выберите New→Project, в левой панели выберите Visual С++[3], выберите Win32 Console Application и в качестве имени проекта введите libgeorgeringo. В мастере Win32 Application Wizard перейдите к Application Settings, выберите DLL и Empty Project (Пустой проект) и нажмите на Finish. Теперь у вас должен иметься пустой проект с двумя конфигурациями сборки — Debug и Release, и первая будет активной.

Затем, сделав щелчок правой кнопкой мыши на Solution Explorer и выбрав Properties, отобразите страницы свойств проекта. Перейдите в раздел Configuration Properties (Свойства конфигурации)→Linker (Компоновщик)→General (Общие) и в поле с именем Output File (Выходной файл) введите имя и путь выходного файла проекта. Директория этого пути должна указывать на директорию binaries, созданную в начале этой главы, а имя должно быть libgeorgeringo.dll. Аналогично перейдите в раздел Configuration Properties (Свойства конфигурации)→Linker (Компоновщик)→Advanced (Дополнительно) и в поле с именем Import Library (Библиотека импорта) введите имя и путь библиотеки импорта проекта. Директория этого пути должна указывать на директорию binaries, созданную в начале этой главы, а имя должно быть libgeorgeringo.lib.

Затем, чтобы добавить в проект исходные файлы из примера 1.2, используйте Add Existing Item… (Добавить существующий элемент…) из меню Project.

Вместо использования опции Add Existing Item…, добавляющей в проект исходные файлы из примера 1.2, можно использовать Add New Item… (Добавить новый элемент…), добавляющую в проект пустые исходные файлы. После этого во вновь созданные файлы требуется ввести или вставить через буфер обмена содержимое из примера 1.2. Аналогичные замечания действительны и для других IDE.

Теперь страницы свойств проекта должны содержать узел с именем «С/С++». Перейдите к Configuration Properties→С/С++→Code Generation (Генерация кода) и, как описано в рецепте 1.19, определите макрос

GEORGERINGO_DLL
. Затем перейдите к Configuration Properties→C/C++→Code Generation и укажите в качестве библиотеки времени выполнения Multi-threaded Debug DLL (многопоточная отладочная динамическая библиотека).

Теперь можно собрать проект, выбрав в меню Build пункт Build Solution. Проверьте, что в директории libgeorgeringo.lib были созданы два файла с именами libgeorgeringo.dll и libgeorgeringo.lib.

Обсуждение

Как вы уже видели в рецепте 1.11, каждая IDE предоставляет свой собственный способ создания проекта, указания свойств конфигурации и добавления в него файлов. Теперь давайте посмотрим на то, как собрать динамическую библиотеку с помощью CodeWarrior, C++Builder и Dev-C++.

CodeWarrior

В меню File выберите New… и в диалоге New выберите вкладку Project. В качестве имени проекта введите

libgeorgeringo.mcp
, выберите место для сохранения настроечных файлов проекта и дважды щелкните мышью на Mac OS C++ Stationery. В диалоге New Project раскройте узел Mac OS X Mach-O and Standard Console, а затем дважды щелкните на C++ Console Mach-O. Теперь у вас должен быть проект с двумя целями — Mach-O C++ Console Debug и Mach-О C++ Console Final, и активной должна быть первая из них.

Так как при создании проекта, зависящего от этого проекта, вам придется ссылаться на эти цели по их именам, им следует дать понятные имена. Сейчас переименуйте только отладочную цель. Выберите вкладку Targets окна проекта и дважды щелкните мышью на имени отладочной цели, чтобы отобразить окно Target Settings (Параметры цели). Затем перейдите к Target→Target Settings и в первом поле Target Name (Имя цели) введите

libgeorgeringo Debug
.

Далее в окне Target Settings перейдите к Target→PPC Mac OS X Target. В качестве Project Туре (Тип проекта) укажите Dynamic Library, а в поле с именем File Name (Имя файла) введите

libgeorgeringo.dylib
. Чтобы в качестве места для создания выходного файла libjohnpaul.а указать директорию binaries, перейдите к Target→Target Settings и нажмите на Choose…. Затем перейдите к Linker→PPC Mac OS X Linker. В раскрывающемся списке Export Symbols (Экспорт символов) выберите Use #pragma и проверьте, что поле Main Entry Point (Главная точка входа) пусто.

Наконец выберите вкладку Files окна проекта и удалите существующие исходные файлы и файлы библиотек, перетащив их в Trash (корзину). Чтобы добавить в проект исходные файлы из примера 1.2, используйте Add Files… (Добавить файлы…) из меню Project. Затем используйте Add Files…, чтобы добавить файл dylib1.о из директории /usr/lib и файлы MSL_All_Mach-O_D.dylib и MSL_Shared_AppAndDylib_Runtime_D.lib из директории Metrowerks CodeWarrior/MacOS X Support/Libraries/Runtime/Runtime PPC/Runtime_MacOSX/Libs. Если бы вы вместо отладочной цели настраивали окончательную, то вместо этих библиотек должны были бы добавить библиотеки MSL_All_Mach-O.dylib и MSL_Shared_AppAndDylib_Runtime.lib. Теперь можно собрать проект, выбрав в меню Project пункт Make. Проверьте, что в директории binaries был создан файл с именем libgeorgeringo.dylib.

C++Builder

В меню File выберите New→Other и затем выберите DLL Wizard. В диалоге DLL Wizard выберите C++ и Multi Threaded. Теперь у вас должен быть проект, содержащий один исходный файл Unit1.cpp. Удалите Unit1.cpp из проекта, сделав для этого щелчок правой кнопкой мыши и выбрав Remove From Project (Удалить из проекта). В меню File выберите Save Project As, выберите директорию для сохранения настроечных файлов проекта и в качестве имени проекта введите libgeorgeringo.bpr.

Затем, чтобы отобразить диалог Project Options (Параметры проекта), в меню Project выберите Options…. Затем перейдите в Directories and Conditionals (Директории и условия) и используйте элемент управления рядом с надписью Final output (Окончательный вывод), чтобы указать, что выходные файлы проекта должны создаваться в директории binaries. По умолчанию они создаются в той же директории, где находится libjohnpaul.bpr. Если хотите, то также можно использовать элемент управления рядом с Intermediate output (Промежуточный вывод) и указать место создания объектных файлов. По умолчанию они создаются в той же директории, где находятся исходные файлы.

Далее определите макрос

GEORGERINGO_DLL
, как описано в рецепте 1.19.

Наконец, чтобы добавить в проект исходные файлы из примера 1.2, используйте Add to Project (Добавить в проект) из меню Project. Теперь можно собрать проект, выбрав в меню Project пункт Make libgeorgeringo. Проверьте, что в директории libgeorgeringo.lib были созданы два файла с именами libgeorgeringo.dll и libgeorgeringo.lib.

Dev-C++

В меню File выберите New→Project. В диалоге New project (Новый проект) выберите DLL и C++ Project, а в качестве имени проекта введите libgeorgeringo. После нажатия на OK укажите место для сохранения настроечных файлов проекта.

Затем, чтобы отобразить диалог Project Options, в меню Project выберите Project Option. Затем перейдите к Build Options и проверьте, что в качестве имени выходного файла проекта указано libjohnpaul.dll. В поле Executable output directory (Директория для записи исполняемого файла) введите путь к директории binaries. Если хотите, то в поле Object file output directory (Директория для записи объектных файлов) можно указать директорию для создания объектных файлов.

Теперь определите макрос

GEORGERINGO_DLL
, как описано в рецепте 1.19.

Наконец удалите из проекта все существующие исходные файлы, сделав щелчок правой кнопкой мыши и выбрав Remove file. Для сохранения настроечного файла проекта libgeorgeringo.dev используйте Save Project as из меню File. Затем, чтобы добавить в проект исходные файлы из примера 1.2, используйте Add to project (Добавить в проект) из меню Project. Соберите проект, в меню Execute выбрав Compile, и проверьте, что в директории binaries был создан файл с именем libjohnpaul.a.

Смотри также

Рецепты 1.4, 1.9, 1.17, 1.19 и 1.23.

1.13. Сборка сложного приложения с помощью IDE

Проблема

Вы хотите использовать IDE для сборки исполняемого файла, зависящего от нескольких статических и динамических библиотек.

Решение

Основная процедура выглядит следующим образом.

1. При сборке из исходного кода библиотек, от которых зависит исполняемый файл, при том, что они не имеют своих собственных проектов IDE или make-файлов, создайте для них проекты, как описано в рецептах 1.11 и 1.12.

2. Создайте новый проект и укажите, что требуется собрать исполняемый файл, а не библиотеку.

3. Выберите конфигурацию сборки (т.е. отладочную или окончательную версию и поддержку или отсутствие поддержки многопоточности).

4. Укажите имя исполняемого файла и директорию, в которой он должен быть создан.

5. Добавьте в проект исходные файлы.

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

7. Скажите компоновщику, какие библиотеки требуется использовать и где их искать.

8. Если IDE поддерживает группы проектов, добавьте все проекты, указанные выше, в единую группу и укажите зависимости между ними.

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

Как и в рецептах 1.11 и 1.12, шаги в этой процедуре будут различаться в зависимости от IDE. Третий шаг подробно описывается в рецептах 1.21, 1.22 и 1.23. А сейчас вы, насколько это возможно, должны использовать параметры по умолчанию.

Например, вот как надо собирать исполняемый файл из исходных файлов из примера 1.3 с помощью Visual C++ IDE.

В меню File выберите New→Project, в левой панели выберите Visual С++[4], выберите Win32 Console Application и в качестве имени проекта введите hellobeatles. В мастере Win32 Application Wizard перейдите к Application Settings, выберите Console Application (Консольное приложение) и Empty Project (Пустой проект) и нажмите на Finish. Теперь у вас должен иметься пустой проект hellobeatles.vcproj с двумя конфигурациями сборки — Debug и Release, и первая будет активной. Также у вас должно быть решение hellobeatles.sln, содержащее один проект hellobeatles.vcproj.

Затем, сделав щелчок правой кнопкой мыши на Solution Explorer и выбрав Properties, отобразите страницы свойств проекта. Перейдите в раздел Configuration Properties (Свойства конфигурации)→Linker (Компоновщик)→General (Общие) и в поле с именем Output File (Выходной файл) введите имя и путь выходного файла проекта. Директория этого пути должна указывать на директорию binaries, созданную в начале этой главы, а имя файла должно быть hellobeatles.exe.

Затем, чтобы добавить в проект исходный файл hellobeatles.cpp из примера 1.3, используйте Add Existing Item (Добавить существующий элемент) из меню Project. Теперь страницы свойств проекта должны содержать узел с именем «C/C++». Перейдите к Configuration Properties→C/C++→Code Generation (Генерация кода) и укажите в качестве библиотеки времени выполнения Multi-threaded Debug DLL (многопоточная отладочная динамическая библиотека).

Вместо использования опции Add Existing Item, добавляющей в проект исходные файлы из примера 1.1, можно использовать Add New Item (Добавить новый элемент), добавляющую в проект пустые исходные файлы. После этого во вновь созданные файлы требуется ввести или вставить через буфер обмена содержимое из примера 1.1. Аналогичные замечания действительны и для других IDE.

Затем перейдите к Configuration Properties→C/C++→General и в поле редактирования с именем Additional Include Directories (Дополнительные директории заголовочных файлов) введите директорию, которая содержит директории johnpaul и georgeringo, — директорию, являющуюся «дедушкой» по отношению к файлам john.hpp, ringo.hpp и другим. Это позволит корректно разрешить директивы

include
в заголовочном файле hellobeatles.hpp.

Далее, используя Add→Existing Project… (Существующий проект…) из меню File добавьте в решение hellobeatles файлы проектов libjohnpaul.vcproj и libgeorgeringo.vcproj. Чтобы отобразить диалог Project Dependencies… (Зависимости проектов…), в меню Project выберите Project Dependencies. В раскрывающемся списке выберите hellobeatles и щелкните на флажках рядом с libjohnpaul и libgeorgeringo.

Если вы знаете, что будете добавлять несколько проектов в одно решение, нет необходимости создавать для каждого из них отдельное решение. Просто создайте пустое решение, выбрав в меню File пункт New→Blank Solution…, а затем добавив в него новые проекты, выбрав в меню File пункт New→Project….

Наконец соберите проект, выбрав в меню Build пункт Build Solution. Проверьте, что в директории binaries были созданы файлы с именами libjohnpaul.lib, libgeorgeringo.dll, libgeorgeringo.lib и hellobeatles.exe. Теперь, чтобы запустить это приложение, в меню Debug выберите Start Without Debugging (Запустить без отладки).

Обсуждение

В предыдущем примере было достаточно просто указать, что hellobeatles.exe зависит от библиотек libjohnpaul.lib и libgeorgeringo.dll, так как обе эти библиотеки собирались в проектах Visual C++ из исходного кода. При сборке приложения, которое зависит от библиотек, распространяемых в виде готовых бинарных и заголовочных файлов, указать Visual C++, как их найти, можно следующим способом. Во-первых, перейдите к Configuration Properties→C/C++→General и в поле редактирования Additional Include Directories введите директории, которые содержат заголовочные файлы библиотек. Затем перейдите в раздел Configuration Properties→Linker→Input и в поле с именем Additional dependencies (Дополнительные зависимости) введите имена этих библиотек. Наконец перейдите к Configuration Properties→Linker→General и в поле редактирования Additional Include Directories введите директории, которые содержат бинарные файлы этих библиотек. Теперь давайте посмотрим на то, как из исходного кода из примера 1.3 собрать исполняемый файл с помощью CodeWarrior, C++Builder и Dev-C++.

CodeWarrior

В меню File выберите New… и в диалоге New выберите вкладку Project. В качестве имени проекта введите

hellobeatles.mcp
, выберите место для сохранения настроечных файлов проекта и дважды щелкните мышью на Mac OS C++ Stationery. В диалоге New Project раскройте узел Mac OS X Mach-O and Standard Console, а затем дважды щелкните на C++ Console Mach-O. Теперь у вас должен быть проект с двумя целями — Mach-O C++ Console Debug и Mach-O C++ Console Final, и активной должна быть первая из них.

Так как при добавлении в проект зависимостей вам придется ссылаться на эти цели по их именам, им следует дать понятные имена. Сейчас переименуйте только отладочную цель. Выберите вкладку Targets окна проекта и дважды щелкните мышью на имени отладочной цели, чтобы отобразить окно Target Settings (Параметры цепи). Затем перейдите к Target→Target Settings и в первом поле Target Name (Имя цели) введите

hellobeatles Debug
.

Далее выберите вкладку Targets окна проекта и дважды щелкните мышью на имени отладочной цели, чтобы отобразить окно Target Settings, Перейдите к Target→PPC Mac OS X Target, в качестве Project Туре (Тип проекта) укажите Executable (Исполняемый файл), а в поле с именем File Name (Имя файла) введите hellobeatles. Чтобы в качестве места для создания выходного файла hellobeatles указать директорию binaries, перейдите к Target→Target Settings и нажмите на Choose….

Выберите вкладку Files окна проекта и удалите существующие исходные файлы и файлы библиотек MSL, перетащив их в Trash (корзину). Чтобы добавить в проект исходный файл hellobeatles.cpp из примера 13, используйте Add Files… (Добавить файлы…) из меню Project. Затем используйте Add Files…, чтобы добавить файлы MSL_All_Mach-O_D.dylib и MSL_Shared AppAndDylib_Runtime_D.lib, находящиеся в директории Metrowerks CodeWarrior/MacOS X Support/Libraries/Runtime/Runtime PPC/Runtime_MacOSX/Libs. Если бы вы вместо отладочной цели настраивали окончательную, то вместо этих библиотек должны были бы добавить библиотеки MSL_All_Mach-О.dylib и MSL_Shared_AppAndDylib_Runtime.lib. В окне Target Settings перейдите к Target→Access Paths (Пути доступа) и щелкните на панели, которая называется User Paths (Пути пользователя). Чтобы добавить директорию, которая содержит директории johnpaul и georgeringo, — директорию, являющуюся «дедушкой» по отношению к исходным файлам ringo.hpp, ringo.hpp и другим, — используйте элемент управления с именем Add…. Это позволит корректно разрешить директивы

include
в заголовочном файле hellobeatles.hpp.

Используя Add Files… из меню Project, добавьте в проект hellobeatles.mcp файлы проектов libjohnpaul.mcp и libgeorgeringo.mcp. Перейдите на вкладку Targets и раскройте узлы, которые называются hellobeatles Debug, libjohnpaul.mcp и libgeorgeringo.mcp. Щелкните на пиктограммах целей, расположенных рядом с дочерними узлами libjohnpaul.mcp и libgeorgeringo.mcp, которые называются libjohnpaul Debug и libgeorgeringo Debug. На обеих пиктограммах должны появиться жирные стрелки. Если требуется, увеличьте окно проекта так, чтобы увидеть небольшую пиктограмму связи у правого края окна. Дважды щелкните в этом столбце — напротив пиктограмм целей со стрелками. В этом столбце должны появиться две черные точки.

Соберите проект, выбрав в меню Project пункт Make. Компоновщик может отобразить несколько предупреждений о символах, определенных несколько раз, но ими можно пренебречь. Вы можете подавить их, перейдя к Linker→Mac OS X Linker и установив опцию Suppress Warning Messages (Подавлять предупреждающие сообщения).

Проверьте, что в директории binaries были созданы файлы с именами libjohnpaul.a, libgeorgeringo.dylib и hellobeatles. Теперь запустите hellobeatles, поместив в директорию binaries копию библиотек MSL_All_Mach-O_D.dylib, перейдя в директорию binaries и введя в командной строке

./hellobeatles
.

C++Builder

В меню File выберите New и затем выберите Console Wizard. В диалоге Console Wizard выберите C++, Multi Threaded (Многопоточное) и Console Application (Консольное приложение). Теперь у вас должен быть проект, содержащий один исходный файл Unit1.cpp. Удалите Unit1.cpp из проекта, сделав для этого щелчок правой кнопкой мыши и выбрав Remove From Project (Удалить из проекта). В меню File выберите Save Project As, выберите директорию для сохранения настроечных файлов проекта и в качестве имени проекта введите hello_beatles. Я добавил в имя проекта знак подчеркивания, потому что C++Builder не позволяет указывать для проекта то же имя, что и для исходного файла.

Затем, чтобы отобразить диалог Project Options (Параметры проекта), в меню Project выберите Options…. Далее перейдите в Directories and Conditionals (Директории и условия) и используйте элемент управления рядом с надписью Final output (Окончательный вывод), чтобы указать, где должен создаваться выходной файл hello_beatles.exe. По умолчанию этот файл будет создан в той же директории, где находится hello_beatles.bpr. Скажите C++Builder, что его требуется создать в директории binaries. Если хотите, то также можете использовать элемент управления рядом с Intermediate output (Промежуточный вывод) и указать место создания объектных файлов. По умолчанию они создаются в той же директории, где находятся исходные файлы.

После этого, чтобы добавить в проект исходный файл hellobeatles.cpp из примера 1.3, используйте Add to Project (Добавить в проект) из меню Project.

Затем из Project Options перейдите к Directories and Conditionals и, используя элемент управления рядом с Include path (Путь заголовочных файлов), выберите директорию, которая содержит директории johnpaul и georgeringo, — директорию, являющуюся «дедушкой» по отношению к исходным файлам john.hpp, ringo.hpp и другим. Это позволит корректно разрешить директивы

include
в заголовочном файле hellobeatles.hpp.

Далее сделайте щелчок правой кнопкой мыши на ProjectGroup1, выберите Save Project Group As, выберите директорию, содержащую файл hello_beatles.bpr, и введите имя группы проектов hello_beatles.bpg.

После этого в группу проектов добавьте проекты libjohnpaul.bpr и libgeorgeringo.bpr, сделав щелчок правой кнопкой мыши на надписи hello_beatles и выбрав Add Existing Project. Соберите эти два проекта, как описано в рецептах 1.11 и 1.12, если этого еще не сделано, а затем с помощью Add to Project из меню Project добавьте выходные файлы libjohnpaul.lib и libgeorgeringo.lib в проект hello_beatles. Используя клавишу со стрелкой при нажатой клавише Ctrl, переместите в Project Manager проекты libjohnpaul и libgeorgeringo выше проекта hello_beatles так, чтобы гарантировать, что они будут собираться первыми.

Наконец соберите решение, выбрав в меню Build пункт Make All Projects. Проверьте, что в директории binaries был создан файл с именем hellobeatles.exe. Чтобы запустить приложение, выберите Run в меню Run.

Dev-C++

В меню File выберите New→Project. В диалоге New project (Новый проект) выберите Console Application и C++ Project, а в качестве имени проекта введите hellobeatles. После нажатия на OK укажите место для сохранения настроечных файлов проекта.

Затем от Project Options перейдите к Build Options и проверьте, что в качестве имени выходного файла проекта указано hellobeatles.exe. В поле Executable output directory (Директория для записи исполняемого файла) введите путь к директории binaries. Если хотите, то в поле Object file output directory (Директория для записи объектных файлов) можно указать директорию для создания объектных файлов.

Далее удалите из проекта все существующие исходные файлы, сделав щелчок правой кнопкой мыши и выбрав Remove file (Удалить файл). Для сохранения настроечного файла проекта hellobeatles.dev используйте Save Project as из меню File. Наконец, чтобы добавить в проект исходный файл hellobeatles.cpp из примера 1.3, используйте Add to project (Добавить в проект) из меню Project

Затем, чтобы отобразить диалог Project Options, в меню Project выберите Project Options. Затем перейдите к Directories→Include Directories, выберите директорию, которая содержит директории johnpaul и georgeringo — директорию, являющуюся «дедушкой» по отношению к исходным файлам ringo.hpp, ringo.hpp и другим, — и нажмите на Add. Это позволит корректно разрешить директивы include в заголовочном файле hellobeatles.hpp.

Наконец от Project Options перейдите к Directories→Libraries Directories и добавьте директорию, которая содержит выходные файлы libjohnpaul.a и libgeorgeringo.c проектов libjohnpaul и libgeorgeringo. Затем перейдите к Parameters→Linker и введите опции -ljohnpaul и -lgeorgeringo.

Теперь с помощью Compile из меню Execute соберите все три проекта по отдельности, проверив, что hellobeatles собирается последним. Запустите hellobeatles.exe, выбрав в меню Execute пункт Run.

Смотри также

Рецепты 1.5, 1.10 и 1.18.

1.14. Получение GNU make

Проблема

Вы хотите получить и установить утилиту GNU make, используемую для сборки библиотек и исполняемых файлов из исходного кода.

Решение

Решение зависит от вашей операционной системы.

Windows

Хотя в некоторых источниках можно получить готовые бинарные файлы GNU make, чтобы использовать возможности GNU make по максимуму, она должна быть установлена как часть Unix-подобной среды. Я рекомендую использовать либо Cygwin, либо MSYS, являющуюся частью проекта MinGW.

Cygwin и MinGW описаны в рецепте 1.1.

Если вы установили Cygwin, как описано в рецепте 1.1, то GNU make у вас уже есть. Чтобы запустить ее из оболочки Cygwin, просто введите команду make.

Чтобы установить MSYS, начните с установки MinGW, как описано в рецепте 1.1. Будущие версии инсталлятора MinGW могут предоставить опцию для автоматической установки MSYS. Но пока выполните следующие дополнительные действия.

Во-первых, на домашней странице MinGW http://www.mingw.org перейдите на страницу закачки MinGW и скачайте самую последнюю стабильную версию программы установки MSYS. Имя этой программы установки должно иметь вид MSYS-<версия>.exe.

Далее запустите программу установки. После этого будет выдан запрос на ввод пути, где находится установка MinGW, и пути, куда следует устанавливать MSYS. Когда программа установки завершит работу, директория установки MSYS должна содержать файл msys.bat. Запуск этого скрипта приведет к отображению оболочки MSYS — порта оболочки bash, из которой можно запускать GNU make и другие программы MinGW, такие как ar, ranlib и dlltool.

Для использования MSYS не требуется, чтобы поддиректории bin установок MinGW или MSYS были записаны в переменной среды PATH.

Unix

Вначале, введя в командной строке make -v, проверьте, установлена ли в вашей системе утилита GNU make. Если GNU make установлена, она должна вывести сообщение, подобное следующему:

GNU Make 3.90

Copyright (С) 2002 Free Software Foundation, Inc.

This is free software; see the source for copying conditions.

Если в системе имеется не-GNU-версия make, то, возможно, GNU-версия установлена под именем gmake. Это можно проверить, введя в командной строке gmake -v.

При использовании Mac OS X простейшим способом получения GNU make является скачивание с web-сайта Apple среды разработки Xcode и следование простым инструкциям ее установки. В настоящий момент Xcode доступен по адресу developer.apple.com/tools.

В других случаях скачайте самую свежую версию GNU make с сайта ftp://ftp.gnu.org/pub/gnu/make, распакуйте ее и следуйте инструкциям по установке.

Обсуждение

Утилита make имеет множество разновидностей. Большая часть инструментариев содержит собственные варианты make. Например, Visual C++ поставляется с утилитой, которая называется nmake.exe. Обычно эти специфичные версии make содержат встроенные функции, которые облегчают их использование с конкретным инструментарием. В результате обсуждение make, которое охватывает множество инструментариев, должно будет описать несколько версий make или иметь дело с ситуациями, когда между какой-то отдельной версией make и конкретным инструментарием не будет соответствия.

Вместо того чтобы описывать несколько утилит make, я решил сконцентрировать внимание на GNU make, которая является наиболее мощным и переносимым вариантом make. GNU make в первую очередь предназначена для работы с GCC. В результате использование GNU make с другим инструментарием, в частности для Windows, иногда может оказаться нетривиальным. Тем не менее, так как GNU make обладает достаточной гибкостью, гораздо проще использовать GNU make с нe-GNU-инструментам и, чем большую часть других make, типа nmake.exe, с инструментарием, отличным от того, для которого они были разработаны.

Основные преимущества GNU make происходят от ее способности исполнять сложные сценарии оболочки. Если вы работаете одновременно и в Unix, и в Windows, вы знаете, что оболочка Windows cmd.exe оставляет желать много большего; в ней отсутствуют многие полезные команды, она имеет ограниченную способность по выполнению сценариев и накладывает жесткие ограничения на длину командной строки. Следовательно, если заставить GNU make использовать cmd.exe, то ее возможности будут сильно ограничены. К счастью, Cygwin и MSYS предоставляют прекрасные среды для использования GNU make в Windows.

MSYS предоставляет минимальную среду, необходимую для запуска в Windows make-файлов и сценариев configure в стиле Unix. Среди прочих полезных инструментов она предоставляет awk, cat, cp, grep, ls, mkdir, mv, rm, rmdir и sed. MSYS была предназначена для работы с GCC и прекрасно с этим справляется. Однако с другими инструментариями для Windows, в частности теми, которые предоставляют .bat-файлы для установки переменных среды и используют для опций командной строки слеши (/) вместо тире (-), она работает несколько менее гладко.

Так, где MSYS минимальна, Cygwin максимальна. Cygwin make может делать все, что может MSYS make, и даже много больше. Однако переносимые make-файлы ограничены узким диапазоном утилит GNU, и они все поддерживаются в MSYS.

Смотри также

Рецепт 1.1.

1.15. Сборка простого приложения «Hello, World» с помощью GNU make

Проблема

Вы хотите с помощью GNU make собрать простую программу «Hello, World», подобную приведенной в примере 1.4.

Решение

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

цели: пререквизиты

 команда-сценари
й

Здесь

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

Пробелы в make-файлах значимы. Строки, содержащие командные сценарии, должны начинаться с Tab, а не с пробелов — это источник некоторых наиболее распространенных ошибок новичков. В следующих примерах строки, которые начинаются с Tab, указаны с помощью отступа на четыре символа.

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

all
и в качестве ее пререквизита укажите имя собираемого исполняемого файла. Она не должна содержать командного сценария. Второй цели присвойте имя исполняемого файла. В качестве ее пререквизитов укажите имена исходных файлов, а в качестве командного сценария укажите команды, которые требуется выполнить для сборки исполняемого файла из исходных файлов. Третья цель должна называться
install
. У нее не должно быть пререквизитов и должен быть командный сценарий, копирующий исполняемый файл из директории, содержащей make-файл, в директорию, где он должен быть установлен. Последняя цель должна называться
clean
. Как и
install
, она не должна иметь пререквизитов. Ее командный сценарий должен удалять из текущей директории исполняемый файл и промежуточные объектные файлы. Цели
clean
и
install
должны быть помечены как phony targets (фиктивные цели), для чего используется атрибут
PHONY
.

Например, чтобы с помощью GCC собрать исполняемый файл из исходного кода из примера 1.4, make-файл может иметь вид, показанный в примере 1.14.

Пример 1.14. make-файл для сборки исполняемого файла с помощью GCC

# Это цель по умолчанию, которая будет собрана при

# вызове make

.PHONY: all

all: hello


# Это правило говорит make, как собрать hello из hello.cpp

hello: hello.cpp

   g++ -o hello hello.cpp


# Это правило говорит make скопировать hello в поддиректорию binaries,

# создав ее, если требуется

.PHONY: install

install:

   mkdir -p binaries

   cp -p hello binaries


# Это правило говорит make удалить hello и hello.о

.PHONY: clean

clean:

   rm -f hello

Чтобы собрать исполняемый файл из исходного кода из примера 1.4 с помощью Visual С++, используйте make-файл, показанный в примере 1.15.

Пример 1.15. make-файл для сборки исполняемого файла с помощью Visual С++.

#цель по умолчанию

.PHONY: all

all: hello.exe


#правило для сборки hello.exe

hello.exe: hello.cpp

   cl -nologo -EHsc -GR -Zc:forScope -Zc:wchar_t \

   -Fehello hello.cpp


.PHONY: install

install:

   mkdir -о binaries

   cp -p hello.exe binaries


.PHONY: clean

clean:

   rm -f hello.exe

Команды и списки целей или пререквизитов могут занимать несколько строк текста make-файла, для чего используется символ продолжения строки \, как и в исходных файлах С++.

Чтобы собрать исполняемый файл, установите переменные среды, необходимые для инструментов командной строки, перейдите в директорию, содержащую makefile, и введите

make
. Чтобы скопировать исполняемый файл в поддиректорию binaries, введите
make install
. Чтобы удалить из директории make-файла исполняемый файл и промежуточный объектный файл, введите
make clean
.

Если вы установили среду Cygwin, описанную в рецепте 1.1, можете выполнить make-файл из примера 1.15 непосредственно из оболочки Windows cmd.exe.

Также этот make-файл можно выполнить из оболочки Cygwin, как описано далее. В cmd.exe запустите vcvars32.bat, устанавливающий переменные среды Visual С++. Затем запустите cygwin.bat, запускающий оболочку Cygwin. Если директория установки Cygwin добавлена в

PATH
, то оболочку Cygwin можно запустить из cmd.exe, просто введя cygwin. Наконец, перейдите в директорию, содержащую make-файл, и введите
make
.

Аналогично можно выполнить make-файл из оболочки MSYS: в cmd.exe запустите vcvars32.bat, затем запустите msys.bat, запускающий оболочку MSYS.

Если ваш инструментарий предоставляет сценарий для установки переменных среды, запуск make-файла из Cygwin или MSYS несколько более трудоемок, чем его запуск из cmd.exe. Однако для некоторых make-файлов это обязательное условие, так как они не будут работать из-под cmd.exe.

Обсуждение

В нескольких последующих рецептах вы увидите, что GNU make является достаточно мощным инструментом для сборки сложных проектов. Но что же она делает? Вот как она работает. При вызове make без аргументов она просматривает текущую директорию в поисках файла с именем GNUmakefile, makefile или Makefile и пытается собрать первую содержащуюся в нем цель, которая называется целью по умолчанию (default target). Если цель по умолчанию не устарела, что означает, что она существует, все ее пререквизиты существуют и ни один из них не был изменен с момента ее сборки, то работа make закончена. В противном случае она пытается сгенерировать цель по умолчанию из ее пререквизитов, выполнив соответствующий командный сценарий. Аналогично определению устаревания этот процесс рекурсивен: для каждого устаревшего пререквизита make ищет правило, содержащее этот пререквизит в качестве цели, и начинает весь процесс заново. Так продолжается до тех пор, пока цель по умолчанию не будет обновлена или пока не возникнет ошибка.

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

Пометив цель атрибутом .PHONY, как в примерах 1.14 и 1.15, можно сказать make, что цель не соответствует файлу, и, таким образом, она всегда должна собираться заново.

И наоборот, пререквизит, соответствующий существующему файлу, никогда не устаревает при условии, что этот файл не указан в качестве цели одного из правил.

Теперь давайте посмотрим на то, что происходит при выполнении make-файла из примера 1.14. Фиктивная цель

all
всегда устаревшая: единственная ее цель — сказать make собрать hello.exe. В таком простом make-файле нет необходимости в цели
all
, но в более сложных примерах цель
all
может иметь несколько пререквизитов, правило с целью
hello
говорит make собрать, если требуется, hello с помощью g++. Если предположить, что текущая директория не содержит ничего, кроме файлов makefile и hello.cpp, цель
hello
будет устаревшей. Однако пререквизит не устарел, так как файл hello.cpp существует и так как
hello.cpp
не является целью одного из правил. Следовательно, make для компиляции и компоновки hello.cpp вызывает g++, генерируя тем самым файл hello. Пререквизит цели
all
обновляется, так что make собирает цель
all
— исполняя пустой командный сценарий — и выходит.

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

make install
приводит к выполнению следующих команд:

mkdir -p binaries

cp -p hello binarie
s

Первая команда создает, если она не существует, директорию binaries, а вторая команда копирует в эту директорию файл hello. Точно так же

make clean
вызывает команду

rm -f hello

которая удаляет hello.

При использовании Windows команды

mkdir
,
cp
и
rm
, используемые целями
install
и
clean
, указывают на инструменты GNU, поставляющиеся в составе Cygwin или MSYS

После того как вы поймете, как make анализирует зависимости, пример 1.14 покажется очень простым. Однако на самом деле он значительно сложнее, чем требуется. Рассмотрение различных методов его упрощения является хорошим способом узнать некоторые из основ make-файлов.

Переменные make

GNU make поддерживает переменные, чьими значениями являются строки. Наиболее часто переменные используются в make-файлах как символьные константы. Вместо того чтобы жестко указывать в нескольких местах make-файла имя файла или команды оболочки, вы можете присвоить имя файла или команды переменной и далее использовать эту переменную. Это дает возможность облегчить сопровождение make-файлов. Например, make-файл из примера 1.14 можно переписать с помощью переменных make так, как показано в примере 1.16.

Пример 1.16. make-файл для сборки исполняемого файла hello с помощью GCC, измененный с помощью переменных

# Указываем целевой файл и директорию установки

OUTPUTFILE=hello

INSTALLDIR=binaries


# Цель по умолчанию

.PHONY all

all: $(OUTPUTFILE)


# Собрать hello из hello.cpp

$(OUTPUTFILE): hello cpp

   g++ -o hello hello.cpp


#Скопировать hello в поддиректорию binaries

.PHONY: install

install:

   mkdir -p $(INSTALLDIR)

   cd -p $(OUTPUTFILE) $(INSTALLDIR)


# Удалить hello

.PHONY: clean

clean:

   rm -f $(OUTPUTFILE)

Здесь я ввел две переменные make —

OUTPUTFILE
и
INSTALLDIR
. Как вы можете видеть, значения переменным make присваиваются с помощью оператора присвоения =, и они вычисляются с помощью заключения их в круглые скобки с префиксом в виде знака доллара.

Также установить значение переменной make можно в командной строке с помощью записи make X=Y. Кроме того, при запуске make все переменные среды используются для инициализации переменных make с такими же именами и значениями. Значения, указанные в командной строке, имеют приоритет перед значениями, унаследованными от переменных среды. Значения, указанные в самом make-файле, имеют приоритет перед значениями, указанными в командной строке.

Также GNU make поддерживает автоматические переменные (automatic variables), имеющие специальные значения при выполнении командного сценария. Наиболее важные из них — это переменная

$@
, представляющая имя файла цели, переменная
$<
, представляющая имя файла первого пререквизита, и переменная
$^
,представляющая последовательность пререквизитов, разделенных пробелами. Используя эти переменные, мы можем еще сильнее упростить make-файл из примера 1.16, как показано в примере 1.17.

Пример 1.17. make-файл для сборки исполняемого файла hello с помощью GCC, измененный с помощью автоматических переменных

# Указываем целевой файл и директорию установки

OUTPUTFILE=hellо

INSTALLDIR=binaries


# Цель по умолчанию

.PHONY all

all: $(OUTPUTFILE)


# Собрать hello из hello.cpp

$(OUTPUTFILE) hello.cpp

   g++ -o $@ $<


# Цели Install и clean как в примере 1 16

В командном сценарии

g++ -o $@ $<
переменная
$@
раскрывается как
hello
, а переменная
$<
раскрывается как
hello.cpp
. Следовательно, make-файл из примера 1.17 эквивалентен файлу из примера 1.16, но содержит меньше дублирующегося кода.

Неявные правила

make-файл в примере 1.17 может быть еще проще. На самом деле командный сценарий, связанный с целью

hello
, избыточен, что демонстрируется выполнением make-файла из примера 1.18.

Пример 1.18. make-файл для сборки исполняемого файла hello с помощью GCC, измененный с помощью неявных правил

# Указываем целевой файл и директорию установки

OUTPUTFILE=hello

INSTALLDIR=binaries


# Цель по умолчанию

.PHONY: all

all: $(OUTPUTFILE)


# Говорим make пересобрать hello тогда, когда изменяется hello.cpp

$(OUTPUTFILE): hello.cpp


# Цели Install и clean как в примере 1.16

Откуда make знает, как собирать исполняемый файл hello из исходного файла hello.cpp, без явного указания? Ответ состоит в том, что make содержит внутреннюю базу данных неявных правил, представляющих операции, часто выполняемые при сборке приложений, написанных на С и С++. Например, неявное правило для генерации исполняемого файла из файла .cpp выглядит так, как в примере 1.19.

Пример 1.19. Шаблон правила из внутренней базы данных make

%: %.cpp

# исполняемые команды (встроенные):

   $(LINK.cpp) $(LOADLIBS) $(LDLIBS) -о $@

Правила, первые строки которых имеют вид

%xxx:%yyy
, известны как шаблонные правила (pattern rules), а символ
%
действует как подстановочный знак (wildcard). Когда устаревшему пререквизиту не соответствует ни одно из обычных правил, make ищет доступные шаблонные правила. Для каждого шаблонного правила make пытается найти строку, которая при подстановке подстановочного знака в целевую часть правила даст искомый устаревший пререквизит. Если make находит такую строку, make заменяет подстановочные знаки для цели и пререквизитов шаблонного правила и создает новое правило. Затем make пытается собрать устаревший пререквизит с помощью этого нового правила.

Чтобы напечатать базу данных неявных правил GNU make, используйте make -p.

Например, при первом выполнении make-файла из примера 1.18 пререквизит

hello
цели по умолчанию
all
является устаревшим. Хотя
hello
фигурирует как цель правила
$(OUTPUTFILE): hello.cpp
, это правило не содержит командного сценария, и, таким образом, оно бесполезно для сборки файла hello. Следовательно, make выполняет поиск в своей внутренней базе данных и находит правило, показанное в примере 1.19. Подставляя в правило из примера 1.19 вместо подстановочного знака строку
hello
, make генерирует следующее правило с
hello
в качестве цели.

hello: hello.cpp

   $(LINK.cpp) $(LOADLIBS) $(LDLIBS) -o $@

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

LINK.cpp
по умолчанию раскрывается как
$(LINK.cc)
. В свою очередь
LINK.cc
по умолчанию раскрывается как

$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)

Наконец, переменная

CXX
по умолчанию раскрывается как
g++
, а четыре другие переменные —
$(CXXFLAGS)
,
$(CPPFLAGS)
,
$(LDFLAGS)
и
$(TARGET_ARCH)
— раскрываются как пустые строки. После выполнения всех этих подстановок получается следующее правило, которое теперь выглядит более знакомо.

hello: hello.cpp

   g++ $^ -o $@

Запутались? Это не страшно. Если вы изучите приведенное объяснение и потратите некоторое время на изучение внутренней базы данных make, неявные правила приобретут смысл.

Возможности для настройки

Теперь, когда вы увидели, как шаблонное правило из примера 1.19 приводит к тому, что make собирает исполняемый файл hello из исходного файла hello.cpp, вы можете спросить, почему было необходимо использовать столько промежуточных шагов. Почему вместо сложного правила из примера 1.19 во внутреннюю базу данных make просто не добавить правило

%: %.cpp

   g++ $^ -о $@

Ответ состоит в том, что промежуточные переменные, такие как

$(CXX)
,
$(CXXFLAGS)
,
$(CPPFLAGS)
и
$(LDFLAGS)
, служат как точки настройки (customization points). Например, указав значение
LDFLAGS
в командной строке, в make-файле или установив значение переменной среды, можно указать дополнительные флаги, передаваемые компоновщику. Переменные
CPPFLAGS
и
CXXFLAGS
играют схожую роль для опций препроцессора и компилятора C++ соответственно. А установив значение переменной
CXX
, можно указать компилятор, отличный от GCC. Например, чтобы собрать hello с помощью Intel для Linux и используя make-файл из примера 1.18, вы должны в командной строке ввести
make CXX=icpc
, предполагая, что переменные среды, необходимые для компилятора Intel, уже установлены.

VPATH и директива vpath

В примере 1.18 make может применить правильное шаблонное правило, потому что файл .cpp находится в той же директории, в которой создается выходной файл. Если исходные файлы находятся в другой директории, то для указания make, где искать цели или пререквизиты, используется переменная

VPATH
.

VPATH = <путь-к-файлам-cpp>

Чтобы сказать make, что выполнять поиск определенных типов файлов требуется в определенном месте, используйте директиву

vpath
.

# искать файлы .exp в ../lib

vpath %. exp../lib

Смотри также

Рецепты 1.2 и 1.7.

1.16. Сборка статической библиотеки с помощью GNU Make

Проблема

Вы хотите использовать GNU make для сборки статической библиотеки из набора исходных файлов C++, таких как перечисленные в примере 1.1.

Решение

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

all
, единственным пререквизитом которой будет статическая библиотека. Затем объявите цель статической библиотеки. Ее пререквизитами должны быть объектные файлы, входящие в состав библиотеки, а ее командный сценарий должен представлять собой командную строку для сборки библиотеки из набора объектных файлов, аналогично показанному в рецепте 1.3. При использовании GCC или компилятора с похожим синтаксисом командной строки настройте, если требуется, неявные правила шаблонов, изменив одну или более переменных
CXX
,
CXXFLAGS
и т.п., используемых в базе данных неявных правил make, как показано в рецепте 1.15. В противном случае, используя синтаксис шаблонных правил, описанный в рецепте 1.16, напишите шаблонное правило, говорящее make, как с помощью командной строки из табл. 1.8 компилировать .cpp-файлы в объектные. Далее явно или неявно объявите цели, указывающие, как каждый из исходных файлов библиотеки зависит от включаемых в него заголовков. Эти зависимости можно описать вручную или сгенерировать их автоматически. Наконец, добавьте цели
install
и
clean
, как показано в рецепте 1.15.

Например, чтобы с помощью GCC в Unix собрать из исходных файлов, перечисленных в примере 1.2, статическую библиотеку, создайте в директории johnpaul make-файл, показанный в примере 1.20.

Пример 1 20. make-файл для создания libjohnpaul.a с помощью GCC в Unix

# Укажите расширения файлов, удаляемых при очистке

CLEANEXTS - о а


# Укажите целевой файл и директорию установки

OUTPUTFILE = libjohnpaul.a

INSTALLDIR = ../binaries


# Цель по умолчанию

.PHONY: all

all: $(OUTPUTFILE)

# Соберите libjohnpaul.a из john.o. paul.o и johnpaul.с

$(OUTPUTFILE): john.o paul.o johnpaul.о

   ar ru $@ $^

   ranlib $@


# Для сборки john.o, paul.o и johnpaul.о из файлов .cpp

# правила не требуются; этот процесс обрабатывается базой данных

# неявных правил make

.PHONY: install

install:

   mkdir -p $(INSTALLDIR)

   cp -p $(OUTPUTFILE) $(INSTALLDIR)


.PHONY: clean

clean:

   for file in $(CLEANEXTS); do rm -f *.$$file; done


# Укажите зависимости файлов cpp от файлов .hpp

john.o: john.hpp

paul.o: paul.hpp

johnpaul.o: john.hpp paul.hpp johnpaul.hpp

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

Пример 1.21. make-файл для создания libjohnpaul.lib с помощью Visual C++

# Укажите расширения файлов, удаляемых при очистке

CLEANEXTS = obj lib


# Specify the target file and the install directory

OUTPUTFILE = libjohnpaul.lib

INSTALLDIR = ./binaries

# Шаблонное правило для сборки объектного файла из файла .cpp

%.obj: %.cpp

   "$(MSVCDIR)/bin/cl" -с -nologo -EHsc -GP -Zc:forScope -Zc:wchar_t \

   $(CXXFLAGS) S(CPPFLAGS) -Fo"$@" $<


# Фиктивная цель

.PHONY: all

all: $(OUTPUTFILE)


# Соберите libjohnpaul.lib из john.obj, paul.obj и johnpaul.obj

$(OUTPUTFILE): john.obj paul.obj johnpaul.obj

   "$(MSVCDIR)/bin/link" -lib -nologo -out:"$@" $^


.PHONY: install

install:

   mkdir -p $(INSTALLDIR)

   cp -p $(OUTPUTFILE) $(INSTALLDIR)


.PHONY: clean

clean:

   for file in $(CLEANEXTS); do rm -f *.$$file; done


# Укажите зависимости файлов .cpp от файлов .hpp

john.obj: john.hpp

paul.obj: paul.hpp

johnpaul.obj: john.hpp paul.hpp johnpaul.hpp

В примере 1.21 я с помощью переменной среды MSVCDIR, устанавливаемой в vcvars32.bat, показал команду Visual C++ link.exe как

"$(MSVCDIR)/bin/link"
. Это позволяет избежать путаницы между компоновщиком Visual C++ и командой Unix link, поддерживаемой Cygwin и MSYS. Для полноты картины я также использовал MSVCDIR для команды компиляции Visual С++.

Обсуждение

Давайте подробно рассмотрим пример 1.20. Вначале я определяю переменные, представляющие выходной файл, директорию установки и расширения файлов, которые должны удаляться при сборке цели

clean
. Затем я объявляю цель по умолчанию
all
, как в примере 1.14.

Правило для сборки статической библиотеки выглядит так.

$(OUTPUTFILE): john.o paul.o johnpaul.о

   ar ru $@ $^

   ranlib $@

Это непосредственная адаптация записи для GCC из табл. 1.10. Здесь

$(OUTPUTFILE)
и
$@
раскрываются как
libjohnpaul.a
, а
$^
раскрывается в виде списка пререквизитов
john.o paul.o johnpaul.о
.

Следующие два правила объявляют цели

install
и
clean
, как в рецепте 1.15. Единственное отличие состоит в том, что в примере 1.20 для удаления всех файлов, чьи расширения имеются в списке
о а
— т. е. все объектные файлы и файлы статической библиотеки, - я использую цикл оболочки.

for file in $(CLEANEXTS); do rm -f *.$$file; done

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

$$file
при передаче ее оболочке.

Три последних правила указывают отношения зависимостей между файлами .cpp библиотеки и включаемыми в них заголовочными файлами. Здесь указано по одному правилу для каждого .cpp-файла. Целью такого правила является объектный файл, собираемый из .cpp-файла, а пререквизитами являются заголовочные файлы, явно или неявно включаемые .cpp-файлом.

john.o: john.hpp

paul.o: paul.hpp

johnpaul.o. john.hpp paul.hpp johnpaul.hpp

Это можно понять следующим образом. Если .cpp-файл явно или косвенно включает заголовочный файл, то он должен быть пересобран при каждом изменении этого заголовочного файла. Однако, так как .cpp-файл существует и не является целью какого-либо правила, он никогда не устаревает, как описано в рецепте 1.15. Следовательно, при изменении заголовочного файла перекомпиляции не происходит. Чтобы исправить эту ситуацию, требуется объявить правило, сделав эти зависимости явными; когда один из используемых заголовочных файлов изменяется, объектный файл, соответствующий .cpp-файлу, устаревает, что приводит к перекомпиляции .cpp-файла.

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

# Генерируем зависимости .cpp-файлов от .hpp-файлов

include john.o paul.o johnpaul.о


%.d: %.cpp

   $(CC) -M $(CPPFLAGS) $< > $@.$$$$; \

   sed 's.\($*\)\.o[ :]*.\1.o $@ : ,g' < $@.$$$$ > $@, \

   rm -f $@.$$$$

Этот фрагмент кода основан на опции компилятора -M, которая заставляет GCC вывести в make-файл информацию о зависимостях. За подробным описанием того, как это работает, и почему иногда не подходит, обратитесь к книге Managing Projects with GNU make, Third Edition, написанной Робертом Мекленбургом (Robert Mecklenburg) (O'Reilly).

Код для генерации зависимостей помещайте в конец make-файла.

Так как большинство компиляторов имеет опцию, аналогичную опции GCC, этот метод может быть адаптирован для работы с большинством инструментов. На самом деле обычно эта опция называется или -m. Однако Visual C++ не имеет опции для генерации зависимостей в make-файле. При использовании Visual C++ есть две возможности. Можно использовать опцию -Gm совместно с опциями -Zi или -ZI, обсуждаемыми в рецепте 1.21. Опция -Gm говорит компилятору создать базу данных, сохраняемую в файле с расширением idb, содержащую информацию о зависимостях между исходными файлами. Файл .idb создается при первоначальной компиляции файла или набора файлов .cpp. При последующих компиляциях перекомпилируются только те исходные файлы, которые были изменены или зависят от изменившихся заголовочных файлов.

Кроме того, можно использовать опцию -showIncludes совместно с опцией -E. Опция -showIncludes приводит к тому, что компилятор при каждом обнаружении директивы include выводит в стандартный поток ошибок сообщение. Опция -E говорит компилятору запустить препроцессор и выйти, не создавая никаких двоичных файлов. С помощью небольшого сценария оболочки можно использовать вывод, сгенерированный -showIncludes; для создания зависимостей в make-файле.

include john.d paul.d johnpaul.d


%d: %.cpp

   "$(MSVCDIR)/bin/cl" -E -showIncludes $< 2> $@.$$$$ > /dev/null; \

   sed -n 's/^Note: including file: *\(.*\)/$*.obj•$*.d:\1/gp' \

   < $@.$$$$ | sed "s:\\:/:g:s: :\\ :gp" > $@; \

   rm -f $@.$$$$

В этом примере символ • обозначает Tab.

Давайте сделаем еще одно последнее усовершенствование примера 1.20. В настоящий момент последовательность

john paul johnpaul
содержится в двух местах — в пререквизитах правила для сборки статической библиотеки и в директиве
include
, используемой для генерации зависимостей. Если список исходных файлов изменится, вам придется вносить изменения в двух местах make-файла. Гораздо лучше определить переменную
SOURCES
и заменить оба использования последовательности
john paul johnpaul
на выражения, использующие
SOURCES
.

SOURCES = john.cpp paul.cpp johnpaul.cpp

...

# Собираем libjohnpaul.a из john.о, paul.o и johnpaul.о

$(OUTPUTFILE): $(subst .cpp, .o,$(SOURCES))

   ar ru $@ $^

   ranlib $@

...

# Генерируем зависимости .cpp-файлов от .hpp-файлов

include $(subst .cpp,.d,$(SOURCES))

%d: %.cpp

   $(CC) -M $(CPPFLAGS) $< > $@.$$$$; \

   sed 's,\($*\)\.o[ :]*.\1.o $@ : .g' < $@ $$$$ > $@; \

   rm -f $@.$$$$

Здесь я использую функцию make

$(subst x, y, str)
, которая заменяет в строке
str
все вхождения
x
на
y
.

GNU make поддерживает большое количество функций обработки строк и имен файлов, а также много других. Также она поддерживает определение пользовательских функций. Как обычно, за подробным описанием обратитесь к Managing Projects with GNU make, Third Edition Роберта Мекленбурга (O'Reilly).

Смотри также

Рецепты 1.2 и 1.7.

1.17. Сборка динамической библиотеки с помощью GNU Make

Проблема

Вы хотите использовать GNU make для сборки динамической библиотеки из набора исходных файлов С++, таких как перечисленные в примере 1.2.

Решение

Вначале в директории, где должна быть создана динамическая библиотека, создайте make-файл и объявите фиктивную

цель
all, единственным пререквизитом которой будет эта динамическая библиотека. Затем объявите цель динамической библиотеки. Ее пререквизитами должны быть объектные файлы, из которых она собирается, а ее командный сценарий должен представлять собой командную строку для сборки библиотеки из набора объектных файлов, аналогично показанному в рецепте 1.4. При использовании GCC или компилятора с похожим синтаксисом командной строки настройте, если требуется, неявные правила шаблонов, изменив одну или более переменных
CXX
,
CXXFLAGS
и т.п., используемых в базе данных неявных правил make, как показано в рецепте 1.15. В противном случае, используя синтаксис шаблонных правил, описанный в рецепте 1.16, напишите шаблонное правило, говорящее make, как с помощью командной строки из табл. 1.8 скомпилировать .cpp-файлы в объектные. Наконец добавьте цели
install
и
clean
, как показано в рецепте 1.15, и механизм для автоматической генерации зависимостей исходных файлов, как показано в рецепте 1.16.

Например, чтобы из исходных файлов, перечисленных в примере 1.2, собрать динамическую библиотеку с помощью GCC в Unix, в директории georgeringo создайте make-файл, показанный в примере 1.22.

Пример 1.22. make-файл для libgeorgeringo.so с использованием GCC

# Укажите расширения файлов, удаляемых при очистке

CLEANEXTS = o so


# Укажите исходные файлы, целевой файл и директорию установки

SOURCES = george.cpp ringo.cpp georgeringo.cpp

OUTPUTFILE = libgeorgeringo.so

INSTALLDIR = ../binaries


.PHONY: all

all: $(OUTPUTFILE)


# Соберите libgeorgeringo.so из george.o, ringo.о

# и georgeringo.o; subst - это функция поиска и замены.

# показанная в рецепте 1.16

$(OUTPUTFILE): $(subst .cpp,.o,$(SOURCES))

   $(CXX) -shared -fPIC $(LDFLAGS) -о


.PHONY: install

install:

   mkdir -p $(INSTALLDIR)

   cp -p $(OUTPUTFILE) $(INSTALLDIR)


.PHONY: clean

clean:

   for file in $(CLEANEXTS); do rm -f *.$$file; done


# Сгенерируйте зависимости файлов .cpp от файлов .hpp

   include $(subst .cpp,.d,$(SOURCES))


%.d: %.cpp

   $(CC) -M $(CPPFLAGS) $< > $@.$$$$; \

   sed 's. \($*\)\.o[ :]*.\1.o $@ : ,g' < $@.$$$$ > $@; \

   rm -f $@.$$$$

Обсуждение

make-файл из примера 1.22 — это прямое применение идей из рецептов 1.4, 1.15 и 1.16. Главным отличием между примерами 1.22 и 1.20 является правило для сборки libgeorgeringo.so из объектных файлов george.o, ringo.o и georgeringo.о.

$(OUTPUTFILE): $(subst .cpp,.o,$(SOURCES))

$(CXX) -shared -fPIC $(LDFLAGS) -о $@ $^

Здесь

$(OUTPUTFILE)
раскрывается как
libgeorgeringo.so
, а выражение
$(subst.cpp, .o, $(SOURCES))
раскрывается как
george.о
,
ringo.о
и
georgeringo.o
, как показано в рецепте 1.16. Командный сценарий
$(CXX) -shared -fPIC $(LDFLAGS) -о
— это адаптация командной строки GCC, показанной в табл. 1.11.

Смотри также

Рецепты 1.4, 1.9, 1.12, 1.19 и 1.23.

1.18. Сборка сложного приложения с помощью GNU make

Проблема

Вы хотите использовать GNU make для сборки исполняемого файла, зависящего от нескольких статических и динамических библиотек.

Решение

Выполните следующие действия.

1. Создайте make-файлы для библиотек, используемых приложением, как описано в рецептах 1.16 и 1.17. Эти make-файлы должны находиться в отдельных директориях.

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

all
, чьим пререквизитом будет являться исполняемый файл. Объявите цель исполняемого файла с пререквизитами, состоящими из библиотек, используемых приложением, а также объектных файлов, которые собираются из .cpp-файлов приложения. Напишите командный сценарий для сборки исполняемого файла из набора библиотек и объектных файлов, как описано в рецепте 1.5. Если необходимо, напишите шаблонное правило для генерации объектных файлов из .cpp-файлов, как показано в рецепте 1.16. Добавьте цели install и clean, как показано в рецепте 1.15, и механизм для автоматической генерации зависимостей исходных файлов, как показано в рецепте 1.16.

3. В директории, родительской по отношению к директориям, содержащим все остальные make-файлы, создайте новый make-файл — давайте называть его главным (top-level) make-файлом, а все остальные — подчиненными. Объявите цель по умолчанию all с пререквизитами в виде директории, содержащей make файл, созданный на шаге 2. Объявите правило, чьи цели состоят из директорий, содержащих подчиненные make-файлы, а командный сценарий вызывает make в каждой целевой директории для цели, указанной в виде значения переменной

TARGET
. Наконец, объявите цели, указывающие зависимости между целями по умолчанию подчиненных make-файлов.

Например, чтобы из исходных файлов из примера 1.3 собрать исполняемый файл с помощью GCC в Unix, создайте такой make-файл, как показанный в примере 1.23.

Пример 1.23. make файл для hellobeatles.exe с использованием GCC

# Укажите исходные файлы, целевой файл, директории сборки

# и директорию установки

SOURCES = hellobeatles.cpp

OUTPUTFILE = hellobeatles

LIBJOHNPAUL = libjohnpaul.a

LIBGEORGERINGO = libgeorgeringo.so

JOHNPAULDIR = ../johnpaul

GEORGERINGODIR = ../georgeringo

INSTALLDIR = ../binaries


#

# Добавьте в путь поиска заголовочных файлов родительскую директорию

#

CPPFLAGS += -I..


#

# Цель по умолчанию

#

.PHONY: all

all: $(HELLOBEATLES)


#

# Цель для сборки исполняемого файла.

#

$(OUTPUTFILE): $(subst .cpp,.о,$(SOURCES)) \

 $(JOHNPAULDIR)/$(LIBJOHNPAUL) \

 $(GEORGERINGODIR)/$(LIBGEORGERINGO)

   $(CXX) $(LDFLAGS) -o $@ $^


.PHONY: install

install:

   mkdir -p $(INSTALLDIR)

   cp -p $(OUTPUTFILE) $(INSTALLDIR)


.PHONY: clean

clean:

   rm -f *.o

   rm -f $(OUTPUTFILE)


#Сгенерируйте зависимости .cpp-файлов от .hpp-файлов

include $(subst .cpp,.d,$(SOURCES))


%.d: %.cpp

   $(CC) -M $(CPPFLAGS) $< > $@.$$$$; \

   sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@..$$$$ > $@; \

   rm -f $@.$$$$

Далее в директории, содержащей johnpaul, georgeringo, hellobeatles и binaries, создайте главный make-файл, как показано в примере 1.24.

Пример 1.24. Главный make-файл для исходного кода из примеров 1.1, 1.2 и 1.3

# Все цели в этом make-файле — фиктивные

PHONY: all johnpaul georgeringo hellobeatles


# Цель по умолчанию

all: hellobeatles


# Цели johnpaul, georgeringo и hellobeatles представляют

# директории, командный сценарий вызывает make в каждой из них

johnpaul georgeringo hellobeatles

$(MAKE) --directory=$@ $(TARGET)


# Это правило указывает что цель по умолчанию make-файла

# в директории hellobeatles зависит от целей по умолчанию

# make-файлов из директорий johnpaul и georgeringo

.PHONY: hellobeatles

hellobeatles: johnpaul georgeringo

Чтобы собрать hellobeatles, перейдите в директорию, содержащую главный make-файл, и введите

make
. Чтобы скопировать файлы libjohnpaul.a, libgeorgeringo.so и hellobeatles в директорию binaries, введите
make TARGET=install
. Чтобы очистить проект, введите
make TARGET=clean
.

Обсуждение

Подход к управлению сложными проектами, продемонстрированный в этом рецепте, известен как рекурсивный make (recursive make). Он позволяет организовать проект в виде набора модулей, каждый со своим собственным make-файлом, и указать зависимости между этими модулями. Он не ограничен одним главным make-файлом с набором дочерних make-файлов: эта методика может применяться для обработки многоуровневых древовидных структур. В то время, пока рекурсивный make был стандартной методикой управления большими проектами с помощью make, появились другие методы, которые теперь рассматриваются как более качественные. За подробностями снова обратитесь к Managing Projects with GNU make, Third Edition Роберта Мекленбурга (O'Reilly).

Пример 1.23 — это прямое применение методик, продемонстрированных в рецептах 1.15, 1,16 и 1.17. В нем есть один очень интересный момент. Как показано в рецепте 1.15, при компиляции hellobeatles.cpp из командной строки необходимо использовать опцию -I.., говорящую компилятору, где искать заголовочные файлы johnpaul.hpp и georgeringo.hpp. Одним из возможных решений является написание явного правила сборки hellobeatles.o с помощью командного сценария, содержащего опцию -I.. подобно этому.

hellobeatles.o: hellobeatles.cpp

g++ -с -I.. -о hellobeatles.o hellobeatles.cpp

Вместо этого я использовал точку настройки

CPPFLAGS
, описанную в рецепте 1.15, и указал, что всегда, когда происходит компиляция объектного файла из файла .cpp, в командную строку должна быть добавлена опция -I..:

CPPFLAGS += -I..

Вместо оператора присвоения

=
я использовал
+=
, так что новое значение будет добавляться к тому значению
CPPFLAGS
, которое могло быть указано в командной строке или в переменной среды.

Теперь давайте посмотрим на то, как работает пример 1.24. Наиболее важным правилом является то, которое заставляет make вызываться для каждой из директорий johnpaul, georgeringo и hellobeatles.

johnpaul georgeringo hellobeatles:

$(MAKE) --directory=$@ $(TARGET)

Чтобы понять это правило, вы должны знать три вещи. Во-первых, переменная

MAKE
раскрывается как имя запущенного в данный момент экземпляра make. Обычно это будет
make
, но на некоторых системах это может быть
gmake
. Во-вторых, опция командной строки --directory= заставляет make вызваться с
<path>
в качестве текущей директории. В-третьих, правило с несколькими целями эквивалентно набору правил, каждое из которых имеет по одной цели и которые содержат одинаковые командные сценарии. Так что приведенное выше правило эквивалентно следующим.

johnpaul:

   $(MAKE) --directory=$@ $(TARGET)


georgeringo:

   $(MAKE) --directory=$@ $(TARGET)


hellobeatles:

   $(MAKE) --directory=$@ $(TARGET)

В свою очередь это эквивалентно:

johnpaul:

   $(MAKE) --directory=johnpaul $(TARGET)


georgeringo:

   $(MAKE) --directory=georgeringo $(TARGET)


hellobeatles:

   $(MAKE) -directory=hellobeatles $(TARGET)

Следовательно, эффект от этого правила состоит в вызове make-файлов в каждой из директорий johnpaul, georgeringo и hellobeatles, а в командной строке передаётся значение переменной

TARGET
. В результате для сборки цели
xxx
в каждом из подчиненных make-файлов требуется выполнить главный make-файл с опцией
TARGET=xxx
.

Последнее правило make-файла гарантирует, что подчиненные make-файлы вызываются в правильном порядке; оно просто объявляет цель

hellobeatles
, зависящую от целей
johnpaul
и
georgeringo
.

hellobeatles: johnpaul georgeringo

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

Смотри также

Рецепты 1.5, 1.10 и 1.13.

1.19. Определение макроса

Проблема

Вы хотите определить символ препроцессора

name
, присвоив ему либо неопределенное значение, либо значение
value
.

Решение

Опции компилятора для определения макросов в командной строке показаны в табл. 1.16. Инструкции для определения макросов в IDE приведены в табл. 1.17. Чтобы определить макрос с помощью Boost.Build, просто добавьте в требования цели свойство вида

name[=value]
, как показано в табл. 1.15 и примере 1.12.


Табл. 1.16. Определение макроса из командной строки

Инструментарий Опции
Все
-Dname[-value]

Табл. 1.17. Определение макроса из IDE

IDE Конфигурация
Visual C++ На страницах свойств проекта перейдите к Configuration Properties→C/C++→Preprocessor и в Preprocessor Definitions (определения препроцессора) введите
name[=value]
, для разделения нескольких записей используя точку с запятой
CodeWarrior В окне Target Settings перейдите к Language Settings→C/C++ Preprocessor и введите:
#define name[=value]
в поле с именем Prefix Text
C++Builder В Project Options перейдите к Directories/Conditionals и в Preprocessor Definitions введите
name[=value]
, для разделения нескольких записей используя точку с запятой
Dev-C++ В Project Options выберите Parameters и введите:
-Dname[=value]
в области C++ Compiler
Обсуждение

Символы препроцессора часто используются в коде C++ для того, чтобы один набор исходных файлов мог быть использован в нескольких конфигурациях сборки или операционных системах. Например, предположим, что вы хотите написать функцию, проверяющую, является ли имя объекта именем файла или директории. Сейчас стандартная библиотека C++ не предоставляет функциональности, необходимой для выполнения этой задачи. Следовательно, эта функция должна использовать функции, специфичные для платформы. Если вы хотите, чтобы этот код работал и в Windows, и в Unix, вы должны убедиться, что код, использующий специфичные для Windows функции, невидим для компилятора при компиляции под Unix, и наоборот. Обычным способом достижения этого эффекта является использование условной компиляции, иллюстрируемой в примере 1.25.

Пример 1.25. Условная компиляция с помощью предопределенных макросов

#ifdef _WIN32

# include 

#else // He Windows - предположим, что мы в Unix

# include 

#endif


bool is_directory(const char* path) {

#ifdef _WIN32

 // реализация для Windows

#else

 // реализация для Unix

#endif

}

В Windows все наборы инструментов, за исключением порта GCC Cygwin, определяют макрос

_WIN32
. Макрос, определяемый автоматически, называется предопределенным макросом. Пример 1.25 использует предопределенный макрос
WIN32
для определения, под какой операционной системой он компилируется, и для включения соответствующего специфичного для платформы кода.

Однако часто настроечная информация, необходимая для выполнения подобного рода условной компиляции, в виде предопределенных макросов недоступна. В таких случаях необходимо создать собственные макросы и с помощью методов, показанных в табл. 1.15, 1.16 и 1.17, присвоить им соответствующие значения. Хорошим примером является пример 1.2. В Windows при сборке DLL georgeringo.dll функция

georgeringo()
должна быть объявлена с атрибутом
__declspec(dllexport)
, а в остальных случаях — с атрибутом
__declspec(dllimport)
. Как описано в рецепте 1.4, этого эффекта можно достичь, определив в командной строке сборки DLL символ препроцессора
GEORGERINGO_DLL
и не определяя его при компиляции кода, использующего эту DLL.

Если вы не указали значение макроса, то большинство компиляторов присваивают ему значение 1, но некоторые присваивают ему пустое значение. При использовании макросов для включения условной компиляции, как в примере 1.25, эта разница не имеет значения. Однако, если требуется, чтобы макрос раскрывался как определенное значение, вы должны указать это значение явно, использовав запись вида

-D<name>=<value>
.

Смотри также

Рецепты 1.4, 1.9, 1.12 и 1.17.

1.20. Указание опций командной строки из IDE

Проблема

Вы хотите передать компилятору или компоновщику опцию командной строки, но она не соответствует ни одному из параметров, доступных в IDE.

Решение

Многие IDE предоставляют способ передачи опций командной строки непосредственно компилятору или компоновщику. Эти способы приведены в табл. 1.18 и 1.19.


Табл. 1.18. Указание опций компилятора из IDE

IDE Конфигурация
Visual C++ На страницах свойств проекта перейдите к Configuration Properties→С/С++→Command Line (командная строка) и введите опцию в поле Additional options (дополнительные опции)
CodeWarrior Неприменимо
C++Builder Неприменимо
Dev-C++ В Project Options выберите Parameters и введите опцию в поле C++ Compiler

Табл. 1.19. Указание опций компоновщика из IDE

IDE Конфигурация
Visual C++ На страницах свойств проекта перейдите к Configuration Properties→Linker→Command Line и введите опцию в поле Additions options
Metrowerks Неприменимо
C++Builder Неприменимо
Dev-C++ В Project Options выберите Parameters и введите опцию в поле Linker
Обсуждение

Visual C++ предоставляет опции расширенной настройки через свой графический интерфейс, но также позволяет указать опции командной строки явно. CodeWarrior и C++Builder не позволяют явно устанавливать опции командной строки, но обычно это не является проблемой, так как аналогично Visual C++ они предоставляют опции расширенной настройки через свои графические интерфейсы. С другой стороны, некоторые IDE предоставляют для настройки инструментов командной строки только самый минимум, за исключением возможности явного ввода в текстовое поле опций командной строки. Dev-C++ занимает положение где-то посередине: хотя Dev-C++ предлагает больше графических опций настройки, чем некоторые IDE, предназначенные для работы с инструментарием GCC, при его использовании обычно бывает необходимо явно ввести опции командной строки.

1.21. Создание отладочной сборки

Проблема

Вы хотите собрать версию проекта, которую можно будет легко отлаживать.

Решение

В основном для получения отладочной сборки требуется:

• отключить оптимизации;

• отключить расширение встраиваемых (inline) функций;

• включить генерацию отладочной информации.

Таблица 1.20 представляет опции компилятора и компоновщика, предназначенные для отключения оптимизаций и встраивания функций, а табл. 1.21 представляет опции компилятора и компоновщика для включения отладочной информации.


Табл. 1.20. Отключение оптимизаций и встраивания из командной строки

Инструментарии Оптимизация Встраивание
GCC
-O0
-fno-inline
¹
Visual C++ Intel (Windows)
-Od
-Ob0
Intel (Linux)
-O0
-Ob0
-opt off
-inline off
Comeau (Unix)
-O0
--no_inlining
Comeau (Windows) To же, что и у основного компилятора, но вместо тире (-) используется слеш (/)
Borland
-Od
-vi-
Digital Mars
-o+none -S
-C

¹ Эту опцию указывать не требуется, если не была указана опция

-O3
.


Табл. 1.21. Опции командной строки для включения отладочной информации

Инструментарии Опции компилятора Опции компоновщика
Comeau (Unix) GCC -g -g
Intel (Linux) Metrowerks
Visual C++ Intel (Windows) См. табл. 1.22 См. табл. 1 22
Comeau (Windows) To же, что и у основного компилятора, но вместо тире (-) используется слеш (/) То же, что и у основного компилятора, но вместо тире (-) используется слеш (/)
Borland -v -v
Digital Mars -g -co

Табл. 1.22. Включение отладочной информации при использовании Visual C++ или Intel для Windows

Опции компилятора Опции компоновщика IDE options¹ Описание
-Z7 -debug C7 Compatible (совместимость с C7) Отладочная информация сохраняется в файлах .obj и .exe
-Zi [-Fd]. -debug[-pdb:] Program Database (база данных программы) Отладочная информация сохраняется в файлах .pdb; опция в квадратных скобках используется для указания файлов .pdb
-Zi [-Fd] -debug [-pdb:] Program Database for Edit & Continue (база данных программы для редактирования и продолжения) Отладочная информация сохраняется в файлах .pdb; опция в квадратных скобках используется для указания файлов .pdb. Программа может быть перекомпилирована во время сессии отладки

¹ Чтобы получить доступ к этим опциям, перейдите к Configuration Properties→С/С++→ General→Debug Information Format (формат отладочной информации).


BoostBuild предоставляет похожий механизм создания отладочной сборки: просто добавьте к требованиям цели

debug
или используйте опцию командной строки variant=debug, которую можно сократить до просто debug.

Некоторые IDE также предоставляют простой способ создания отладочной сборки. Например, при создании нового проекта в Visual C++ IDE автоматически генерирует конфигурации для отладочной и окончательной сборок. Чтобы запросить отладочную сборку, просто выберите в меню Build опцию Configuration Manager и в качестве активной выберите конфигурацию Debug. Также можно выбрать Debug в раскрывающемся списке конфигураций на стандартной панели инструментов. При следующей сборке проекта будет создана отладочная сборка.

Аналогично при создании проекта в CodeWarrior с помощью одного из шаблонов проектов Metrowerks, называемых «принадлежности» (stationery), IDE автоматически генерирует отладочную и окончательную цели. Имя отладочной цели может быть разным, но оно всегда должно включать слово «debug». Чтобы запросить отладочную сборку, в меню Project выберите пункт Set Default Target (установить цель по умолчанию), а затем выберите элемент меню, соответствующий отладочной цели. Также можно выбрать отладочную цель в раскрывающемся списке целей в окне проекта.

C++Builder не поддерживает множественных конфигураций для одного проекта, но он предоставляет простой способ создания отладочной сборки. Чтобы запросить отладочную сборку, перейдите в Project Options→Compiler и нажмите на Full debug (полная отладка). Это отключит все оптимизации и встраивание и включит отладочную информацию.

При использовании IDE, которая не предоставляет готовых отладочной и окончательной конфигураций, такой как Dev-C++, или если вам требуется получить дополнительный контроль над параметрами проекта, обратитесь к таблицам с 1.23 до 1.25.


Табл. 1.23. Отключение оптимизаций из IDE

IDE Конфигурация
Visual C++ На страницах свойств проекта перейдите к Configuration Properties→C/C++→Optimization и установите опцию Optimization в значение Disabled (отключено). Для остальных опций на этой странице оставьте значения по умолчанию
CodeWarrior В окне Target Settings перейдите к Code Generation→Global Optimizations (генерация кода→глобальная оптимизация) и выберите Off (выкл)
C++Builder В Project Options перейдите к Compiler, в разделе Code optimization (оптимизация кода) выберите None
Dev-C++ В Project Options перейдите к Compiler→Optimization и установите опцию Perform a number of minor optimizations (выполнить несколько незначительных оптимизаций) в значение No (нет), затем перейдите к Compiler→Optimization→Further optimizations (дополнительные оптимизации) и установите опции Optimize (оптимизировать), Optimize more (дополнительно оптимизировать) и Best Optimization (наилучшая оптимизация) в значение No

Табл. 1.24. Отключение встраивания из IDE

IDE Конфигурация
Visual C++ На страницах свойств проекта перейдите к Configuration Properties→C/C++→Optimization и установите опцию Inline Function Expansion (расширение встраиваемых функций) в значение Default (по умолчанию)
CodeWarrior В окне Target Settings перейдите к Language Settings→C/C++ Language и установите Inline Depth (глубина встраивания) в значение Don't Inline (не встраивать)
C++Builder В Project Options перейдите к Compiler и в разделе Debugging установите флажок Disable inline expansions (отключить встраивание функций)
Dev-C++ См. запись для GCC в табл. 1.20 и обратитесь к рецепту 1.20

Табл. 1.25. Включение отладочной информации в IDE

IDE Конфигурация
Visual C++ См. табл. 1.22
CodeWarrior В окне Target Settings перейдите к Language Settings→Linker→PPC Mac OS X Linker и установите флажки Generate SYM File (генерировать SYM-файл) и Full Path in SYM Files (полные пути в SYM-файлах)
C++Builder В Project Options перейдите к Compiler и установите флажки Debug information и Line Number Information (информация о номерах строк)
Dev-C++ См. запись для GCC в табл. 1.21 и обратитесь к рецепту 1.20
Обсуждение

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

Большинство наборов инструментов сохраняют отладочную информацию непосредственно в объектных и исполняемых файлах, но некоторые также предоставляют опцию для генерации отладочной информации в отдельных файлах базы данных. Например, в Visual C++ опция компилятора -Z7 указывает, что отладочная информация должна быть помещена в объектные и исполняемые файлы, а опции -Zi и -ZI указывают, что она должна быть сохранена в файлах базы данных программы, имеющих расширение .pdb. Опция -ZI включает функцию, которая называется Edit and Continue (отредактировать и продолжить), которая позволяет пользователям IDE изменять и перекомпилировать код, не прерывая сессии отладки. Аналогично CodeWarrior для Mac OS X по умолчанию генерирует отладочную информацию в файлах .SYM.

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

По этим причинам обычно при создании отладочной сборки принято отключать оптимизации и встраивание.

Смотри также

Рецепт 1.22.

1.22. Создание окончательной сборки

Проблема

Вы хотите создать небольшой быстрый исполняемый файл или динамическую библиотеку, предназначенные для распространения среди покупателей.

Решение

В основном для получения окончательной сборки требуется:

• включить оптимизации;

• включить расширение встраиваемых (inline) функций;

• отключить генерацию отладочной информации.

В табл 1.26 представлены опции компилятора и компоновщика, включающие оптимизацию и встраивание. Опций командной строки для отключения отладочной информации не существует: при сборке из командной строки отладочная информация по умолчанию отключена. Однако при использовании инструментария GCC размер исполняемых файлов и динамических библиотек можно уменьшить, указав компоновщику опцию -s.


Табл. 1.26. Опции компилятора, включающие оптимизации и встраивание

Инструментарий Оптимизация Встраивание
GCC -O3 -finline-functions¹
Visual C++ Intel -O2 -Оb1
Metrowerks -opt full -inline auto -inline level=8
Comeau (Unix) -O3
Comeau (Windows) To же, что и у основного компилятора, но вместо тире (-) используется слеш (/) -inlining
Borland -O2 -vi
Digital Mars -o+time Включено по умолчанию

¹ Эта опция автоматически включается при указании -O3.


Boost.Build предоставляет похожий механизм создания окончательной сборки: просто добавьте к требованиям цели

release
или используйте опцию командной строки variant=release, которую можно сократить до просто release.

Некоторые IDE также предоставляют простой способ создания окончательной сборки. Например, как я говорил в рецепте 1.21, при создании нового проекта в Visual C++ IDE автоматически генерирует отладочную и окончательную конфигурации. Чтобы запросить окончательную сборку, просто выберите в меню Build опцию Configuration Manager и в качестве активной выберите конфигурацию Release. Также можно выбрать Release в раскрывающемся списке конфигураций на стандартной панели инструментов. При следующей сборке проекта будет создана окончательная сборка.

Аналогично при создании проекта в CodeWarrior с помощью одного из шаблонов проектов Metrowerks, называемых «принадлежности» (stationery), IDE автоматически генерирует отладочную и окончательную цели. Имя окончательной цели может быть разным, но оно всегда должно включать слово «release» или «final». Чтобы запросить окончательную сборку, в меню Project выберите пункт Set Default Target (установить цель по умолчанию), а затем выберите элемент меню, соответствующий окончательной цели. Также можно выбрать окончательную цель в раскрывающемся списке целей в окне проекта.

C++Builder не поддерживает множественных конфигураций для одного проекта, но он предоставляет простой способ создания окончательной сборки. Чтобы запросить окончательную сборку, перейдите в Project Options→Compiler и нажмите на Release. Это включит все оптимизации и встраивание и отключит отладочную информацию.

При использовании IDE, которая не предоставляет готовых отладочной и окончательной конфигураций, такой как Dev-C++, или если требуется получить дополнительный контроль над параметрами проекта, обратитесь к таблицам с 1.27 до 1.29.


Табл. 1.27. Включение оптимизаций из IDE

IDE Конфигурация
Visual C++ На странице свойств проекта перейдите к Configuration Properties→C/C++→Optimization и установите параметр Optimization в значение Maximize Speed (Максимизировать быстродействие), Favor Size or Speed (Отдавать предпочтение размеру или скорости) в значение Favor Fast Code (Отдавать предпочтение быстроте кода), а параметры Global Optimizations (Глобальная оптимизация). Enable Intrinsic Functions (Включить встроенные функции) и Omit Frame Pointers (Не включать указатели фрейма) в значение Yes (Да). Для остальных опций на этой странице оставьте значения по умолчанию
CodeWarrior В окне Target Settings перейдите к Code Generation→Global Optimizations (Генерация кода→Глобальная оптимизация) и выберите Level 4 (Уровень 4)
C++Builder В Project Options перейдите к Compiler, в разделе Code optimization (оптимизация кода) выберите Speed (Скорость)
Dev-C++ См запись для GCC в табл. 1 26 и обратитесь к рецепту 1.20

Табл. 1.28. Включение встраивания из IDE

IDE Конфигурация
Visual C++ На страницах свойств проекта перейдите к Configuration Properties→C/C++→Optimization и установите опцию Inline Function Expansion (расширение встраиваемых функций) в значение Any Suitable (Все подходящие)
CodeWarrior В окне Target Settings перейдите к Language Settings→C/C++ Language. Установите параметр Inline Depth (Глубина встраивания) в значение 8, а остальные опции встраивания оставьте неотмеченными
C++Builder В Project Options перейдите к Compiler и в разделе Debugging снимите флажок Disable inline expansions (Отключить встраивание функций)
Dev-C++ См. запись для GCC в табл. 1.26 и обратитесь к рецепту 1.20

Табл. 1.29. Отключение отладочной информации в IDE

IDE Конфигурация
Visual C++ На страницах свойств проекта перейдите к Configuration Properties→С/С++→General и для опции Debug Information Formal (Формат отладочной информации) выберите значение Disabled
Metrowerks В окне Target Settings перейдите к Language Settings→Linker→х86 Linker и снимите флажки Store full paths (Сохранять полные пути). Link debug info (Компоновать с отладочной информацией), и Debug inline functions (Отлаживать встраиваемые функции)
C++Builder В Project Options перейдите к Compiler и снимите флажки Debug Information и Line Number Information (информация о номерах строк)
Dev-C++ Убедитесь, что. как описано в рецепте 1.20 не указана опция командной строки -g
Обсуждение

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

Таблицы 1.26 и 1.27 показывают опции обобщенных оптимизаций, но для достижения наилучших результатов следует тщательно рассмотреть все требования, изучить документацию по инструментарию и провести всестороннее тестирование.

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

Смотри также

Рецепт 1.21.

1.23. Указание варианта библиотеки времени выполнения

Проблема

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

Решение

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

При использовании Boost.Build эти три выбора можно сделать, использовав функции

threading
,
runtime-link
и
variant
, описанные в табл. 1.15. Например, чтобы указать статическую библиотеку времени выполнения, добавьте к требованиям цели
static
или используйте опцию командной строки runtime-link=static. Чтобы указать многопоточную библиотеку времени выполнения, добавьте к требованиям цели
multi
или используйте опцию командной строки threading=multi.

При сборке из командной строки используйте опции компилятора и компоновщика, представленные в таблицах с 1.30 до 1.36. Опции командной строки и имена библиотек для отладочной и окончательной конфигураций обычно очень похожи. В следующих таблицах буквы в квадратных скобках добавляются только для отладочных конфигураций. Имена динамических вариантов библиотек времени выполнения показаны в круглых скобках. При выборе динамической компоновки эти библиотеки должны быть доступны при выполнении программы.


Табл. 1.30. Опции компилятора для выбора библиотеки времени выполнения при использовании Visual C++ или Intel (Windows)

Статическая компоновка Динамическая компоновка
Однопоточная -ML[d]¹ Неприменимо
Многопоточная -MT[d] -MD[d](msvcrt[d].dll, msvcr80[d].dll)²

¹ Начиная с Visual Studio 2005, в момент написания книги, находящейся в стадии бета-тестирования, опции -ML и -MLd считаются устаревшими, а однопоточные статические библиотеки времени выполнения больше не поставляются.

² Предыдущие версии Visual C++ использовали DLL msvcr71.dll, msvcr71d.dll, msvcr70.dll, msvcr70d.dll и т.д.


Табл. 1.31. Опции компилятора для выбора библиотеки времени выполнения при использовании Metrowerks (Windows)

Статическая компоновка Динамическая компоновка
Однопоточная -runtime ss[d] Неприменимо
Многопоточная -runtime sm[d] -runtime dm[d](MSL_All-DLL90_x86[_D].dll)

Табл. 1.32. Опции командной строки для выбора библиотеки времени выполнения при использовании CodeWarrior 10 для Max OS X

Статическая компоновка Динамическая компоновка
Опции не требуется Обратитесь к документации Metrowerks по опциям командной строки (MSL_All_Mach-O[_D].dylib)

Табл. 1.33. Опции компилятора для выбора библиотеки времени выполнения при использовании Borland

Статическая компоновка Динамическая компоновка
Однопоточная -WM -WM- -WR -WC¹ (cc3260.dll)
Многопоточная -WM -WM -WR -WC (cc3260mt.dll)

¹ Опция -WC требуется только при сборке консольного приложения.


Табл. 1.34. Опции компилятора для выбора библиотеки времени выполнения при использовании Digital Mars (все библиотеки времени выполнения многопоточны)

Статическая компоновка Динамическая компоновка
Опций не требуется -ND -D_STLP_USE_DYNAMIC_LIB(sccrt70.dll, stlp45dm.dll)

Табл. 1.35. Опции компилятора для выбора библиотеки времени выполнения при использовании GCC

Статическая компоновка Динамическая компоновка
-static¹ Опций не требуется

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


Например, чтобы указать динамическую окончательную сборку библиотеки времени выполнения Visual С++, используйте опцию компилятора -MD. Чтобы указать статическую однопоточную отладочную сборку библиотеки времени выполнения Metrowerks для Windows, используйте опцию компилятора -runtime ssd. Чтобы указать однопоточную динамическую сборку библиотеки времени выполнения Borland, передайте компилятору и компоновщику опции -WM- -WR -WC.

Инструкции для указания варианта библиотеки времени выполнения в IDE приведены в табл. 1.36.


Табл. 1.36. Указание варианта библиотеки времени выполнения из IDE

IDE Конфигурация
Visual C++ На страницах свойств проекта перейдите к Configuration Properties→C/C++→Code Generation (Генерация кода) и используйте раскрывающийся список Runtime Library (библиотека времени выполнения)
CodeWarrior Для проектов динамических библиотек добавьте в проект объектный файл /usr/lib/dylib1.o и библиотеки MSL_Shared_AppAndDylib_Runtime[_D].lib и MSL_All_Mach-O[_D].dylib и удалите все библиотеки вида MSL__Mach-O[_D].lib. Для проектов исполняемых файлов добавьте в проект объектный файл /usr/lib/crtl.с и библиотеки MSL_Shared_AppAndDylib_Runtime[_D].lib и MSL_All_Mach-O[_D].dylib и удалите все библиотеки вида MSL__Mach-O[_D].lib
C++Builder Будет ли проект одно- или многопоточным, должно быть указано при его создании. Чтобы выбрать статическую или динамическую библиотеку времени выполнения, в окне Project Options перейдите к Linker и установите или снимите флажок Use dynamic RTL (Использовать динамические библиотеки времени выполнения)
Dev-C++ Чтобы выбрать статическую библиотеку времени выполнения, укажите опцию командной строки -static, как описано в рецепте 1.20
Обсуждение

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

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

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

Итак, как же решить, какую библиотеку времени выполнения использовать? Два выбора — одно- или многопоточную и отладочную или окончательную — вполне очевидны.

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

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

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

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

Смотри также

Рецепты 1.4, 1.5, 1.21 и 1.25.

1.24. Включение строгого соответствия стандарту C++

Проблема

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

Решение

Опции командной строки для указания строгого соответствия стандарту C++ приведены в табл. 1.37. Инструкции для включения строгого соответствия в IDE приведены в табл. 1.38

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


Табл. 1.37. Включение строгого соответствия из командной строки

Инструментарий Опции командной строки компилятора
GCC -ansi -pedantic-errors
Visual C++ -Za
Intel (Windows) -Za -Qms0
Intel (Linux) -strict-ansi¹
Metrowerks -ansi strict -iso_templates on -msext off
Comeau (Windows) -A
Comeau (Unix) -strict or -A
Borland -A²
Digital Mars -A

¹ Версии компилятора Intel для Linux до 9.0 использовали опцию -strict_ansi. При использовании -strict-ansi или -strict_ansi может потребоваться с помощью опции -cxxlib-icc включить стандартную библиотеку Intel

² С опцией некоторые стандартные заголовочные файлы библиотеки STLPort могут не компилироваться.


Табл. 1.38. Включение строгого соответствия в IDE

IDE Конфигурация
Visual C++ На страницах свойств проекта перейдите к Configuration Properties→C/C++→Language и установите в значение Yes (Да) опции Disable Language Extensions (Отключить расширения языка), Treat wchar_t as Built-in Type (Рассматривать wchar_t как встроенный тип) и Force Conformance in For Loop Scopes (Включить соответствие стандарту в циклах For)
Metrowerks В окне Target Settings перейдите к Language Settings→C/C++ Language и установите ISO Template Parser (Синтаксический анализ шаблонов ISO), ANSI Strict (Строгий ANSI) и ANSI Keywords Only (Только ключевые слева ANSI). Убедитесь, что выбраны опции Enable C++ Exceptions (Включить исключений С++). Enable RTTI support (Включить поддержку RTTI). Enable bool Support (Включить поддержку bool) и Enable wchar_t Support (Включить поддержку wchar_t)
C++Builder В Project Options перейдите к Advanced Compiler и в разделе Language Compliance (Соответствие языка) установите ANSI
Dev-C++ См запись для GCC в табл. 1 37 и обратитесь к рецепту 1.20
Обсуждение

Язык C++ был стандартизирован Международной организацией по стандартизации (International Standards Organization — ISO) в 1998 году. В том же году стандарт ISO был одобрен и принят Национальным институтом стандартизации США (American National Standards Institute — ANSI). В 2003 году была одобрена вторая редакция стандарта, которая содержит исправления и пояснения, но также вводит несколько новых языковых возможностей. В настоящее время ведется работа над обновленной версией стандарта С++, которая будет включать несколько важных языковых функций и расширенную стандартную библиотеку.

В момент принятия стандарта в 1998 году ни один из компиляторов не достигал полного соответствия его требованиям, хотя многие были представлены как «ANSI-совместимые». Однако в течение нескольких прошедших лет поставщики много работали над тем, чтобы сделать свои инструменты более точно и строго соответствующими стандарту. По состоянию на сентябрь 2005 года последние версии компиляторов GNU, Microsoft, Intel, Metrowerks и Comeau обладают высокой степенью соответствия. Comeau и Intel с их поддержкой экспорта шаблонов могут рассматриваться как соответствующие стандарту почти на 100%[5].

Ни один из компиляторов не может обеспечить полного соответствия стандарту с точки зрения отказа компилировать любую неверную программу. И не только из-за того, что ни один из них не соответствует стандарту на 100%: более важной причиной является то, что стандарт C++ не требует от компилятора отвергать неверные программы. Имеется четкий перечень обстоятельств, в которых компилятор должен выдавать диагностическое сообщение, указывающее на неправильно написанную программу, однако для многих некорректных программ диагностики не требуется. Это программы, которые приводят к тому, что стандарт называет неопределенным поведением программы при ее выполнении. И даже тогда, когда диагностика обязательна, компилятор волен выдать сообщение и продолжить компиляцию, в результате которой возможно успешное создание исполняемого файла или библиотеки.

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

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

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

for
, простиралась до конца блока, в котором находился этот цикл.

// ВНИМАНИЕ: некорректный код!

int main() {

 for (int i = 0; i < 10; ++i)

  ;

 int j = i; // j == 10

}

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

Другой категорией некорректного кода является код, который использует экспериментальные расширения языка, которые по какой-либо причине не вошли в конечный стандарт C++. Например, многие компиляторы предоставляют встроенный тип

long long
, длина которого гарантированно имеет не менее 64 бит. Как еще один пример, некоторые компиляторы предоставляют встроенный оператор
typeof
, имеющий такой же синтаксис, как и оператор
sizeof
, и возвращающий тип выражения. Обе эти функции, скорее всего, появятся в следующей версии стандарта C++, хотя ожидается, что написание
typeof
изменится на, возможно,
decltype
.

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

Третья категория некорректного кода — это код, который использует платформенно-зависимые расширения языка, необходимые для использования функций операционной системы. В эту категорию попадают атрибуты

__declspec(dllexport)
и
__declspec(dllimport)
, используемые для сборки динамических библиотек в Windows, и атрибуты
__stdcall
,
__fastcall
и
__cdecl
, представляющие соглашения о вызовах в Windows. Хотя это и расширения языка, большая часть компиляторов для Windows принимает код, содержащий эти расширения, даже если используется опция строгого соответствия стандарту.

Последней категорией некорректного кода является код, нарушающий стандарт С++, но полностью соответствующий некоторым другим стандартам. Главным примером такого стандарта является C++/CLI, который сейчас проходит последние стадии стандартизации в ECMA. C++/CLI — это расширение С++, которое состоит из интерфейса C++ к Command Language Infrastructure — ядру Microsoft .NET Framework. При компиляции приложения, использующего определенные расширения C++/CLI, соответствующий стандарту компилятор C++ должен выдавать диагностику, но при поддержке стандарта C++/CLI он может свободно генерировать работоспособное приложение.

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

long long
. Наконец, некоторые компиляторы предоставляют опции, заставляющие их сообщать о большинстве нарушений стандарта как о предупреждениях, а не ошибках. Например, GCC для этой цели предоставляет опцию -pedantic, a Comeau для Windows предоставляет опцию --a, а для других платформ — опции --strict_warnings или -a.

Смотри также

Рецепт 1.2.

1.25. Указание определенной библиотеки для автоматической компоновки с исходным файлом

Проблема

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

Решение

При программировании для Windows с использованием инструментария Visual C++, Intel, Metrowerks, Borland или Digital Mars для указания имен и (при необходимости) путей готовых библиотек, с которыми должен компоноваться код, включающий заголовочные файлы вашей библиотеки, используйте в этих заголовочных файлах

pragma comment
.

Например, предположим, что вы хотите распространить библиотеку из примера 1.1, состоящую из статической библиотеки libjohnpaul.lib и заголовочного файла johnpaul.hpp. Измените этот заголовочный файл так, как показано в примере 1.26.

Пример 1.26. Использование pragma comment

#ifndef JОНNPAUL_HPP_INCLUDED

#define JOHNPAUL_HPP_INCLUDED

#pragma comment(lib, "libjohnpaul")

void johnpaul();

#endif // JOHNPAUL_HPP_INCLUDED

После этого изменения компоновщики Visual С++, Intel, Metrowerks, Borland и Digital Mars при компоновке кода, включающего заголовочный файл johnpaul.hpp, будут автоматически находить библиотеку libjohnpaul.lib.

Обсуждение

В некоторых ситуациях компоновка может оказаться более сложным этапом процесса сборки, чем компиляция. Одна из наиболее часто возникающих проблем компоновки создается тогда, когда компоновщик находит неверную версию какой-либо библиотеки. Это в основном проблема Windows, где библиотеки времени выполнения и зависящие ar них библиотеки часто имеют множество вариантов. По этой причине библиотеки для Windows часто поставляются с именами, измененными так, чтобы они отражали различные конфигурации сборки. Хотя это и помогает снизить число конфликтов версий, это также затрудняет процесс компоновки, так как теперь вы должны указывать компоновщику правильное измененное имя.

По этой причине

pragma comment
очень полезна. Среди прочего она позволяет указать правильное имя библиотеки в заголовочном файле и избавить пользователя от необходимости разбираться в ваших соглашениях об изменении имен файлов. Если в дополнение к этому вы разработаете процесс установки, копирующий двоичные файлы в папку, автоматически используемую компоновщиком, — такую как поддиректория lib корневых директорий Visual С++, CodeWarrior или C++Builder, — то программисты смогут использовать вашу библиотеку, просто включив ее заголовочные файлы.

До сих пор все было хорошо. Но есть одна проблема:

pragma comment
распознается не всеми компиляторами. Если вы хотите писать портируемый код, вы должны вызывать pragma только после того, как проверите, что она поддерживается используемым инструментарием. Например, вы можете изменить johnpaul.cpp вот так.

#ifndef JOHNPAUL_HPP_INCLUDED

#define JOHNPAUL_HPP_INCLUDED


#if defined(_MSC_VER) || \

 defined(__ICL) || \

 defined(__MWERKS__) && defined(_WIN32) || \

 defined(__BORLANDC__) \

 defined(__DMC__) \

/**/

#pragma comment (lib, "libjohnpaul")

#endif


void johnpaul();


#endif // JOHNPAUL_HPP_INCLUDED

Этот пример уже стал достаточно сложным, и, к сожалению, он все еще не полностью корректен: некоторые компиляторы, не поддерживающие

pragma comment
, для совместимости в Visual C++ определяют макрос
_MSC_VER
. К счастью, Boost предоставляет простое решение.

#ifndef johnpaul_hpp_included

#define JOHNPAUL_HPP_INCLUDED


#define BOOST_LIB_NAME libjohnpaul

#define BOOSTAUTO_LINK_NOMANGLE


#include 


void johnpaul();


#endif // JOHNPAUL_HPP_INCLUDED

Здесь строка

#define BOOST_LIB_NAME libjohnpaul

определяет имя библиотеки, строка

#define BOOST_AUTO_LINK_NOMANGLE

указывает, что вы не хотите использовать соглашение об именах Boost, а строка

#include 

вызывает

pragma comment
для поддерживающих ее компиляторов.

Смотри также

Рецепт 1.23.

1.26. Использование экспортируемых шаблонов

Проблема

Вы хотите собрать программу, использующую экспортируемые шаблоны, что означает, что она объявляет шаблоны в заголовочных файлах с использованием ключевого слова

export
, а реализация шаблонов находится в файлах .cpp.

Решение

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

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


Табл. 1.39. Опции для включения экспортируемых шаблонов

Инструментарий Сценарий
Comeau (Unix) -export, -A или -strict
Comeau (Windows) -export или -A
Intel (Linux) -export или -strict-ansi¹

¹ Версии компилятора Intel для Linux до 9.0 использовали опцию -strict_ansi


Табл. 1.40. Опции, указывающие расположение реализаций шаблонов

Инструментарий Сценарий
Comeau -template_directory=
Intel (Linux) -export_dir

Например, предположим, что вы хотите скомпилировать программу, показанную в примере 1.27. Она содержит три файла.

• Файл plus.hpp содержит объявление экспортируемого шаблона функции

plus()
.

• Файл plus.cpp содержит определение

plus()
.

• Файл test.cpp включает объявление — но не определение —

plus()
и определяет функцию
main()
, использующую
plus()
.

Пример 1.27. Простая программа, использующая экспортируемые шаблоны


plus.hpp:

#ifndef PLUS_HPP_INCLUDED

#define PLUS_HPP_INCLUDED


export template

T plus(const T& lhs, const T& rhs);


#endif // #ifndef PLUS_HPP_INCLUDED


plus.cpp:

#include "plus.hpp"


template

T plus(const T& lhs, const T& rhs) {

 return rhs + lhs;

}


test.cpp:

#include 

#include "plus.hpp"


int main() {

 std::cout << "2 + 2 = " << plus(2, 2) << "\n";

}

Чтобы скомпилировать plus.cpp в объектный файл plus.obj с помощью Comeau в Unix, перейдите в директорию, содержащую plus.cpp, plus.hpp и test.cpp, и введите следующую команду.

$ como -c --export plus.cpp

Эта команда также генерирует файл plus.et, описывающий реализацию шаблона, содержащегося в plus.cpp.

Для развлечения откройте plus.et в текстовом редакторе.

Затем скомпилируйте test.cpp в объектный файл test.obj с помощью команды:

$ como -c --export test.cpp

Наконец, скомпонуйте исполняемый файл test.exe.

$ como --export -о test test.obj

Две последние команды также можно объединить в одну.

$ como --export -o test test.cpp

Теперь можно запустить test.exe.

$ ./test

2 + 2 = 4

Теперь предположите, что файлы plus.hpp и plus.cpp находятся в директории с именем plus, a test.cpp находится в дочерней директории test. Чтобы скомпилировать и скомпоновать test.cpp, перейдите в директорию test и введите:

$ como --export --template_directory=../plus -I../plus -o test

test.cpp

Обсуждение

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

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

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

Раздельная модель предлагает несколько потенциальных преимуществ.

Уменьшение времени компиляции

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

Снижение «загрязнения» символов

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

Возможность поставлять скомпилированные реализации шаблонов.

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

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

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

Смотри также

Рецепт 1.25.

Загрузка...