Модули в C++
· С целью оптимизации процесса разработки и борьбы со сложностью системы абстракций, программы на C++ разбиваются на модули (исходные файлы – единицы компиляции “.cc” и “.cpp”).
· Каждый файл может содержать объявления и определения элементов программы (переменных, функций, структур и классов, шаблонов, определений типа typedef, пространств имен и т.д.)
Каждый элемент имеет свой тип связи (linkage):
· no linkage (для элементов на стеке)
· internal linkage – внутренняя связь(область видимости – файл)
· external linkage – внешняя связь (область видимости – проект)
o Глобальные переменные и функции по умолчанию имеют внешнюю связь
o Только константные или статические имеют внутреннюю связь
o extern объявление позволяет подцепить переменную из другого модуля на стадии линковки, а также сделать внешнюю компоновку
o Чтобы сделать имена локальными для единицы компиляции их можно поместить в безымянное пространство имён
Пространство имен - некоторое множество, под которым подразумевается модель, абстрактное хранилище или окружение, созданное для логической группировки уникальных идентификаторов (имен).
Поскольку пространства с глобальной областью видимости добавляются к системе, то имеется возможность возникновения коллизии имен. Это становится особенно актуальным при использовании библиотек, разработанных различными независимыми производителями. Использование
namespace позволяет разбить глобальное пространство имен с тем, чтобы решить подобную проблему. По существу, namespace определяет область видимости.
Пространство имен определяет область видимости имен объектов, которые не входят ни в один блок, поэтому пространство имен не может быть определено внутри блока.
Объявление пространства имен имеет синтаксис:
namespaсe идентификатор {
//тело пространства имен
}
Идентификатор определяет имя пространства имен.
· Тело содержит описания и определения имен.
· Пространства имен открыты, т. е. одно и то же пространство имен можно расширять в разных исходных файлах.
· Пространства имен могут быть вложены друг в друга.
По умолчанию существует анонимное пространство имен, которое доступно во всех исходных файлах и которое не нужно объявлять, оно называется глобальным.
Можно сказать, что все пространства имен вложены в глобальное. Все имена, которые не входят ни в одно пространство имен, по умолчанию принадлежат глобальному.
В каждом исходном файле можно также объявить локальное анонимное пространство имен, которое видимо только в этом файле. Оно может быть вложено в другое, именованное пространство имен.
Для доступа к именам пространства имен используется оператор разрешения области видимости :: , который имеет синтаксис:
имя_пространства_имен ::имя_объекта
В этом случае имя пространства имен называется квалификатором имени объекта. Оператор разрешения области видимости выполнится на этапе компиляции программы и не влияет на ее производительность.
Для доступа к именам из глобального пространства имен используется оператор разрешения области видимости без квалификатора (пишем, ::имя_объекта)
int x;
void f () {
int x;
x = 1;
::x = 4;
}
Это необходимо в случае, если глобальные имена скрыты локальными именами из не глобальной области видимости.
Для доступа к именам из локального анонимного пространства оператор разрешения области видимости не используется.
Директива Using
Для объявления имени из некоторого пространства имен используется объявление using, которое может находиться в любой области видимости, включая область видимости любого пространства имен. Синтаксис:
u sing имя_пр_имен имя_объекта;
Объявление using может использоваться для вложенных пространств имен.
Для объявления сразу всех имен из некоторого пространства имен используется директива using вида:
using namespace имя _ пр _ имен ;
Чтобы избежать дублирования имен, для пространства имен может быть выбрано длинное имя. В этом случае для удобства записи определяют короткий псевдоним, который используется в тех исходных файлах, где заведомо нет имен, совпадающих с псевдонимом. Синтаксис:
namespace псевдоним = имя_пр_имен ;
(например: using FactoryMap = std::map<Key, AbstractCreator<BaseType, Args...>*>;)
Раздельная компиляция
Исходный C++ файл — это всего лишь код, но его невозможно запустить как программу или использовать как библиотеку. Поэтому каждый исходный файл требуется скомпилировать в исполняемый файл, динамическую или статическую библиотеки (данные библиотеки будут рассмотрены в следующей статье).
1) Препроцессинг
Самая первая стадия компиляции программы.
Препроцессор — это макро процессор, который преобразовывает вашу программу для дальнейшего компилирования. На данной стадии происходит происходит работа с препроцессорными директивами. Например, препроцессор добавляет хэдеры в код (#include), убирает комментирования, заменяет макросы (#define) их значениями, выбирает нужные куски кода в соответствии с условиями #if, #ifdef и #ifndef.
2) Компиляция
На данном шаге g++ выполняет свою главную задачу — компилирует, то есть преобразует полученный на прошлом шаге код без директив в ассемблерный код. Это промежуточный шаг между высокоуровневым языком и машинным (бинарным) кодом.
Ассемблерный код — это доступное для понимания человеком представление машинного кода.
3) Ассемблирование
Далее, необходимо перевести ассемблерный код в машинный с помощью ассемблера.
Ассемблер преобразовывает ассемблерный код в машинный код, сохраняя его в объектном файле.
Объектный файл — это созданный ассемблером промежуточный файл, хранящий кусок машинного кода. Этот кусок машинного кода, который еще не был связан вместе с другими кусками машинного кода в конечную выполняемую программу, называется объектным кодом.
Далее возможно сохранение данного объектного кода в статические библиотеки для того, чтобы не компилировать данный код снова.
4) Компоновка
Компоновщик (линкер) связывает все объектные файлы и статические библиотеки в единый исполняемый файл, который мы и сможем запустить в дальнейшем. Для того, чтобы понять как происходит связка, следует рассказать о таблице символов.
Таблица символов — это структура данных, создаваемая самим компилятором и хранящаяся в самих объектных файлах. Таблица символов хранит имена переменных, функций, классов, объектов и т.д., где каждому идентификатору (символу) соотносится его тип, область видимости. Также таблица символов хранит адреса ссылок на данные и процедуры в других объектных файлах.
Именно с помощью таблицы символов и хранящихся в них ссылок линкер будет способен в дальнейшем построить связи между данными среди множества других объектных файлов и создать единый исполняемый файл из них.
5) Загрузка
Последний этап, который предстоит пройти нашей программе — вызвать загрузчик для загрузки нашей программы в память. На данной стадии также возможна подгрузка динамических библиотек.
Заголовочные файлы
o используются для включения общих частей кода (обычно прототипов) для многократного использования
o обязательно нужен страж включения, чтобы избежать многократного включения хедера в одну единицу трансляции
o могут содержать:
§ именованные пространства имен
§ объявления типов, шаблонов, функций, данных, перечислений
§ определения шаблонов, inline функций, констант
§ директивы включения, макроопределения, директивы условной компиляции, комментарии
o не должны (а то Ефремов пиздить будет):
§ определения обычных функций, данных, агрегатов (массивов)
§ неименованные пространства имён, экспортируемые определения шаблонов
Инициализация переменных при запуске:
o Исполнение начинается с main
o Все нелокальные переменные инициализируются до его вызова
o Нет фиксированного порядка инициализации между единицами трансляции
o Внутри единицы трансляции - в порядке объявления
Дата: 2019-02-19, просмотров: 648.