Функции компоновщика и загрузчика. Зачем нужны и что такое UTM-метки — генераторы и компоновщики Функции компоновщика
В типичной системе одновременно выполняется множество программ. Работа каждой программы зависит от множества функций, некоторые из которых входят в состав "стандартной" Си-библиотеки (например, printf() , malloc() , write() и т. д.).Если каждая программа использует стандартную Си-библиотеку, значит каждая программа, как правило, содержит свою особую копию данной библиотеки. К сожалению, это ведет к нерациональному использованию ресурсов. Поскольку библиотека Си является общей, разумнее было бы сделать так, чтобы каждая программа ссылалась на общий экземпляр этой библиотеки, а не содержала ее копию. Такой подход имеет несколько преимуществ, и не последним из них является значительная экономия общесистемных ресурсов памяти.
Статическая компоновка Термин "статически скомпонованный" (statically linked) означает, что программа и некоторая библиотека были объединены с помощью компоновщика (linker) во время процесса компоновки. Таким образом, связь между программой и библиотекой является фиксированной и устанавливается во время процесса компоновки, т. е. до того, как программа будет работать. Кроме всего прочего, это также означает, можно изменить данную связь иначе, как посредством перекомпоновки программы с новой версией библиотеки.Статическая компоновка имеет смысл в тех случаях, когда нет уверенности в том, что правильная версия библиотеки будет доступна в момент работы программы, или в тех случаях, когда тестируется новая версия библиотеки и пока нет необходимости устанавливать ее как разделяемый компонент.
Статически скомпонованные программы компонуются с архивами объектов (
библиотеками ), которые обычно имеют расширение a. Примером такого набора объектов является стандартная Си-библиотека libc.a. Динамическая компоновка Термин "динамически скомпонованный" (dynamically linked) означает, что программа и некоторая библиотека не были объединены с помощью компоновщика во время процесса компоновки. Вместо этого, компоновщик помещает информацию в исполняемый файл, который, в свою очередь, сообщает загрузчику, в каком разделяемом объектном модуле расположен код и какой динамический компоновщик (runtime linker) должен использоваться для поиска и компоновки ссылок. Это означает, что связь между программой и разделяемым объектом устанавливается во время выполнения программы, а именно, в самом начале выполнения производится поиск и компоновка необходимых разделяемых объектов.Такой тип программ называется
частично связанным исполняемым файлом (partially bound executable) , так как в них разрешены не все ссылки, т. е. компоновщик в процессе компоновки не связал все упомянутые идентификаторы (referenced symbols) в программе с соответствующим кодом из библиотеки. Вместо этого, компоновщик указывает, в каком именно разделяемом объекте находятся функции, вызываемые программой. В результате сам процесс компоновки осуществляется потом, уже в момент выполнения программы.Динамически скомпонованные программы компонуются с разделяемым объектами с расширением so. Примером такого объекта является разделяемая стандартная Си-библиотека libc.so.
Для того, чтобы сообщить комплекту инструментов о том, какой тип компоновки применяется - статический или динамический - используется соответствующая опция командной строки утилиты qcc. Эта опция затем определяет используемое расширение (a или so).
Добавление кода в процессе работы программы При таком подходе вызываемые из программы функции будут определены только на этапе исполнения. Это предоставляет дополнительные возможности.Рассмотрим пример работы драйвера диска. Драйвер запускается, тестирует оборудование и обнаруживает жёсткий диск. Затем драйвер динамически загружает модуль io-blk, предназначенный для обработки дисковых блоков, т.к. было обнаружено блок-ориентированное устройство. После того как драйвер получает доступ к диску на блочном уровне, он обнаруживает на диске два раздела: раздел DOS и раздел QNX4. Чтобы не увеличивать размер драйвера жёсткого диска, в него вообще не включаются драйверы файловых систем. Во время работы системы драйвер может обнаружить эти два раздела (DOS и QNX4) и только после этого загрузить соответствующие модули файловых систем fs-dos.so и fs-qnx4.so.
Таким образом, откладывая вызов необходимых функций на более поздние этапы, увеличивается гибкость и компактность драйвера жёсткого диска.
Как используются разделяемые объекты Для того чтобы понять, как программа использует разделяемые объекты, необходимо сначала рассмотреть формат исполняемого модуля, а затем последовательность тех стадий, через которые программа проходит при запуске. Формат ELF В ОС QNX Neutrino используется так называемый двоичный формат исполняемых и компонуемых модулей (Executable and Linkable Format, ELF), который в настоящее время принят в системах SVR4 Unix. Формат ELF не только упрощает создание разделяемых библиотек, но также расширяет возможности динамической загрузки модулей во время работы программы.На рис. 7.1 показан ELF-файл в двух представлениях: представление компоновки и представление исполнения. Представление компоновки, используемое в процессе компоновки программы или библиотеки, касается
секций (sections) внутри объектного файла. Секции содержат большую часть информации этого файла: данные, инструкции, настроечная информация, идентификаторы, отладочная информация и т. д. Представление исполнения, используемое при выполнении программы, касается сегментов (segments) .В процессе компоновки программа или библиотека строится посредством слияния секций, имеющих одинаковые атрибуты, и преобразования их в сегменты. Как правило, все секции, содержащие данные, которые предназначены для исполнения или "только для чтения", компонуются в один сегмент
text , а данные и BSS компонуются в сегмент data . Эти сегменты называются загрузочными сегментами (load segments) , потому что они должны быть загружены в память при создании процесса. Другие секции, как, например, информация об идентификаторах и отладочная информация, объединяются в т. н. незагружаемые сегменты (nonload segments) .11. Принципы функционирования систем программирования. Функции текстовых редакторов в системах программирования. Компилятор как составная часть системы программирования.
Принципы функционирования систем программирования
Функции текстовых редакторов в системах программирования
Текстовый редактор в системе программирования - это программа, позволяющая создавать, изменять и обрабатывать исходные тексты программ на языках высокого уровня.
В принципе текстовые редакторы появились вне какой-либо связи со средствами разработки. Они решали задачи создания, редактирования, обработки и хранения на внешнем носителе любых текстов, которые не обязательно должны были быть исходными текстами программ на языках высокого уровня. Эти функции многие текстовые редакторы выполняют и по сей день.
Возникновение интегрированных сред разработки на определенном этапе развития средств разработки программного обеспечения позволило непосредственно включить текстовые редакторы в состав этих средств. Первоначально такой подход привел к тому, что пользователь (разработчик исходной программы) работал только в среде текстового редактора, не отрываясь от нее для выполнения компиляции, компоновки, загрузки и запуска программы на выполнение. Для этого
Потребовалось создать средства, позволяющие отображать ход всего процесса разработки программы в среде текстового редактора, - такие, например, как метод отображения ошибок в исходной программе, обнаруженных на этапе компиляции, с позиционированием на место в тексте исходной программы, содержащее ошибку.
Можно сказать, что с появлением интегрированных сред разработки ушло в прошлое то время, когда разработчики исходных текстов вынуждены были первоначально готовить тексты программ на бумаге с последующим вводом их в компьютер. Процессы написания текстов и собственно создание программного обеспечения стали единым целым.
Интегрированные среды разработки оказались очень удобным средством. Они стали завоевывать рынок средств разработки программного обеспечения. А с их развитием расширялись и возможности, предоставляемые разработчику в среде текстового редактора. Со временем появились средства пошаговой отладки программ непосредственно по их исходному тексту, объединившие в себе возможности отладчика и редактора исходного текста. Другим примером может служить очень удобное средство, позволяющее графически выделить в исходном тексте программы все лексемы исходного языка по их типам - оно сочетает в себе возможности редактора исходных текстов и лексического анализатора компилятора.
В итоге в современных системах программирования текстовый редактор стал важной составной частью, которая не только позволяет пользователю подготавливать исходные тексты программ, но и выполняет все интерфейсные и сервисные функции, предоставляемые пользователю системой программирования. И хотя современные разработчики по-прежнему могут использовать произвольные средства для подготовки исходных текстов программ, как правило, они все же предпочитают пользоваться именно тем текстовым редактором, который включен в состав данной системы программирования.
Компилятор как составная часть системы программирования
Компиляторы являются, безусловно, основными модулями в составе любой системы программирования. Поэтому не случайно, что они стали одним из главных предметов рассмотрения в данном учебном пособии. Без компилятора никакая система программирования не имеет смысла, а все остальные ее составляющие на самом деле служат лишь целям обеспечения работы компилятора и выполнения им своих функций.
От первых этапов развития систем программирования вплоть до появления интегрированных сред разработки пользователи (разработчики исходных программ) всегда так или иначе имели дело с компилятором. Они непосредственно взаимодействовали с ним как с отдельным программным модулем.
Сейчас, работая с системой программирования, пользователь, как правило, имеет дело только с ее интерфейсной частью, которую обычно представляет текстовый редактор с расширенными функциями. Запуск модуля компилятора и вся его работа происходят автоматически и скрытно от пользователя - разработчик видит только конечные результаты выполнения компилятора. Хотя многие современные системы программирования сохранили прежнюю возможность непосредственного взаимодействия разработчика с компилятором (это и Makefile, и так называемый «интерфейс командной строки»), но пользуется этими средствами только узкий круг профессионалов. Большинство пользователей систем программирования сейчас редко непосредственно сталкиваются с компиляторами.
На самом деле, кроме самого основного компилятора, выполняющего перевод исходного текста на входном языке в язык машинных команд, большинство систем программирования могут содержать в своем составе целый ряд других компиляторов и трансляторов. Так, большинство систем программирования содержат в своем составе и компилятор с языка ассемблера, и компилятор (транслятор) с входного языка описания ресурсов. Все они редко непосредственно взаимодействуют с пользователем.
Тем не менее, работая с любой системой программирования, следует помнить, что основным модулем ее всегда является компилятор. Именно технические характеристики компилятора, прежде всего, влияют на эффективность результирующих программ, порождаемых системой программирования.
Компоновщик. Назначение и функции компоновщика.
12. Компоновщик. Назначение и функции компоновщика
Компоновщик (или редактор связей) предназначен для связывания между собой объектных файлов, порождаемых компилятором, а также файлов библиотек, входящих в состав системы программирования 1 .
Объектный файл (или набор объектных файлов) не может быть исполнен до тех пор, пока все модули и секции не будут в нем увязаны между собой. Это и делает редактор связей (компоновщик). Результатом его работы является единый файл (часто называемый «исполняемым файлом»), который содержит весь текст результирующей программы на языке машинных кодов. Компоновщик может порождать сообщение об ошибке, если при попытке собрать объектные файлы в единое целое он не смог обнаружить какой-либо необходимой составляющей.
Функция компоновщика достаточно проста. Он начинает свою работу с того, что выбирает из первого объектного модуля программную секцию и присваивает ей начальный адрес. Программные секции остальных объектных модулей получают адреса относительно начального адреса в порядке следования. При этом может выполняться также функция выравнивания начальных адресов программных секций. Одновременно с объединением текстов программных секций объединяются секции данных, таблицы идентификаторов и внешних имен. Разрешаются межсекционные ссылки.
Процедура разрешения ссылок сводится к вычислению значений адресных констант процедур, функций и переменных с учетом перемещений секций относительно начала собираемого программного модуля. Если при этом обнаруживаются ссылки к внешним переменным, отсутствующим в списке объектных модулей, редактор связей организует их поиск в библиотеках, доступных в системе программирования. Если же и в библиотеке необходимую составляющую найти не удается, формируется сообщение об ошибке.
Обычно компоновщик формирует простейший программный модуль, создаваемый как единое целое. Однако в более сложных случаях компоновщик может создавать и другие модули: программные модули с оверлейной структурой, объектные модули библиотек и модули динамически подключаемых библиотек (работа с оверлейными и динамически подключаемыми модулями в ОС описана в первой части данного пособия).
13. Загрузчики и отладчики. Функции загрузчика
Большинство объектных модулей в современных системах программирования строятся на основе так называемых относительных адресов. Компилятор, порождающий объектные файлы, а затем и компоновщик, объединяющий их в единое целое, не могут знать точно, в какой реальной области памяти компьютера будет располагаться программа в момент ее выполнения. Поэтому они работают не с реальными адресами ячеек ОЗУ, а с некоторыми относительными адресами. Такие адреса отсчитываются от некоторой условной точки, принятой за начало области памяти, занимаемой результирующей программой (обычно это точка начала первого модуля программы).
Конечно, ни одна программа не может быть исполнена в этих относительных адресах. Поэтому требуется модуль, который бы выполнял преобразование относительных адресов в реальные (абсолютные) адреса непосредственно в момент запуска программы на выполнение. Этот процесс называется трансляцией адресов и выполняет его специальный модуль, называемый загрузчиком.
Однако загрузчик не всегда является составной частью системы программирования, поскольку выполняемые им функции очень зависят от архитектуры целевой вычислительной системы, в которой выполняется результирующая программа, созданная системой программирования. На первых этапах развития ОС загрузчики существовали в виде отдельных модулей, которые выполняли трансляцию адресов и готовили программу к выполнению - создавали так называемый «образ задачи». Такая схема была характерна для многих ОС (например, для ОСРВ на ЭВМ типа СМ-1, ОС RSX/11 или RAFOS на ЭВМ типа СМ-4 и т. п. ). Образ задачи можно было сохранить на внешнем носителе или же создавать его вновь всякий раз при подготовке программы к выполнению.
С развитием архитектуры вычислительных средств компьютера появилась возможность выполнять трансляцию адресов непосредственно в момент запуска программы на выполнение. Для этого потребовалось в состав исполняемого файла включить соответствующую таблицу, содержащую перечень ссылок на адреса, которые необходимо подвергнуть трансляции. В момент запуска исполняемого файла ОС обрабатывала эту таблицу и преобразовывала относительные адреса в абсолютные. Такая схема, например, характерна для ОС типа MS-DOS, которые широко распространены в среде персональных компьютеров. В этой схеме модуль загрузчика как таковой отсутствует (фактически он входит в состав ОС), а система программирования ответственна только за подготовку таблицы трансляции адресов - эту функцию выполняет компоновщик.
В современных ОС существуют сложные методы преобразования адресов, которые работают непосредственно уже во время выполнения программы. Эти методы основаны на возможностях, аппаратно заложенных в архитектуру вычислительных комплексов. Методы трансляции адресов могут быть основаны на сегментной, страничной и сегментно-страничной организации памяти (все эти методы рассмотрены в первой части данного пособия). Тогда для выполнения трансляции адресов в момент запуска программы должны быть подготовлены соответствующие системные таблицы. Эти функции целиком ложатся на модули ОС, поэтому они не выполняются в системах программирования.
Еще одним модулем системы программирования, функции которого тесно связаны с выполнением программы, является отладчик.
Отладчик - это программный модуль, который позволяет выполнить основные задачи, связанные с мониторингом процесса выполнения результирующей прикладной программы. Этот процесс называется отладкой и включает в себя следующие основные возможности:
последовательное пошаговое выполнение результирующей программы на ос
нове шагов по машинным командам или по операторам входного языка;
выполнение результирующей программы до достижения ею одной из задан
ных точек останова (адресов останова);
выполнение результирующей программы до наступления некоторых заданных
условий, связанных с данными и адресами, обрабатываемыми этой програм
мой;
просмотр содержимого областей памяти, занятых командами или данными
результирующей программы.
появлением интегрированных сред разработки;
появление возможностей аппаратной поддержки средств отладки во многих
вычислительных системах.
Второй шаг позволил значительно расширить возможности средств отладю Теперь для них не требовалось моделировать работу и архитектуру соответа вующей вычислительной системы. Выполнение результирующей программы режиме отладки стало возможным в той же среде, что и в обычном режиме. В зг
дачу отладчика входили только функции перевода вычислительной системы соответствующий режим перед запуском результирующей программы на отла/ ку. Во многом эти функции являются приоритетными, поскольку зачастую тр(буют установки системных таблиц и флагов процессора вычислительной сист
Отладчики в современных системах программирования представляют собо модули с развитым интерфейсом пользователя, работающие непосредственн с текстом и модулями исходной программы. Многие их функции интегрирован: с функциями текстовых редакторов исходных текстов, входящих в состав систе программирования.
14. Библиотеки подпрограмм как составная часть систем программирования
Библиотеки подпрограмм составляют существенную часть систем программир
Библиотеки подпрограмм входили в состав средств разработки, начиная с самь ранних этапов их развития. Даже когда компиляторы еще представляли собс отдельные программные модули, они уже были связаны с соответствующими би(лиотеками, поскольку компиляция так или иначе предусматривает связь програм со стандартными функциями исходного языка. Эти функции обязательно дол> ны входить в состав библиотек.
С точки зрения системы программирования, библиотеки подпрограмм состою из двух основных компонентов. Это собственно файл (или множество файло] библиотеки, содержащий объектный код, и набор файлов описаний функций, по, программ, констант и переменных, составляющих библиотеку.
15. Лексический анализ «на лету». Система подсказок и справок.
Дополнительные возможности систем программирования
Лексический анализ «на лету». Система подсказок и справок
Лексический анализ «на лету» - это функция текстового редактора в составе системы программирования. Она заключается в поиске и выделении лексем входного языка в тексте программы непосредственно в процессе ее создания разработчиком.
Реализуется это следующим образом: разработчик создает исходный текст программы (набирает его или получает из некоторого другого источника), и в то же время система программирования параллельно выполняет поиск лексем в этом тексте.
В простейшем случае обнаруженные лексемы просто выделяются в тексте с помощью графических средств интерфейса текстового редактора - цветом, шрифтом и т. п. Это облегчает труд разработчика программы, делает исходный текст более наглядным и способствует обнаружению ошибок на самом раннем этапе - на этапе подготовки исходного кода.
В более развитых системах программирования найденные лексемы не просто выделяются по ходу подготовки исходного текста, но и помещаются в таблицу идентификаторов компилятора, входящего в состав системы программирования. Такой подход позволяет экономить время на этапе компиляции, поскольку первая ее фаза - лексический анализ - уже выполнена на этапе подготовки исходного текста программы.
Следующей сервисной возможностью, предоставляемой разработчику системой программирования за счет лексического анализа «на лету», является возможность обращения разработчика к таблице идентификаторов в ходе подготовки исходного текста программы. Разработчик может дать компилятору команду найти нужную ему лексему в таблице. Поиск может выполняться по типу или по какой-то части информации лексемы (например, по нескольким первым буквам). Причем поиск может быть контекстно-зависимым - система программирования предоставит разработчику возможность найти лексему именно того типа, который может быть использован в данном месте исходного текста. Кроме самой, лексемы разработчику может быть предоставлена некоторая информация о ней - например, типы и состав формальных параметров для функции, перечень доступных методов для типа или экземпляра класса. Это опять же облегчает труд разработчика, поскольку избавляет его от необходимости помнить состав функций и типов многих модулей (прежде всего, библиотечных) или обращаться лишний раз к документации и справочной информации.
Лексический анализ «на лету» - мощная функция, значительно облегчающая труд, связанный с подготовкой исходного текста. Она входит не только в состав многих систем программирования, но также и в состав многих текстовых редакторов, поставляемых отдельно от систем программирования (в последнем случае она позволяет настроиться на лексику того или иного языка).
Другой удобной сервисной функцией в современных системах программирования является система подсказок и справок. Как правило, она содержит три основные части:
справку по семантике и синтаксису используемого входного языка;
подсказку по работе с самой системой программирования;
справку о функциях библиотек, входящих в состав системы программирова
ния.
16.Разработка программ в архитектуре «клиент-сервер»
Структура приложения, построенного в архитектуре «клиент - сервер».
Распространение динамически подключаемых библиотек и ресурсов прикладных программ привело к ситуации, когда большинство прикладных программ стало представлять собой не единый программный модуль, а набор сложным образом взаимосвязанных между собой компонентов. Многие из этих компонентов либо входили в состав ОС, либо же требовалась их поставка и установка от других разработчиков, которые очень часто могли быть никак не связаны с разработчиками самой прикладной программы.
При этом среди всего множества компонентов прикладной программы можно было выделить две логически цельные составляющие: первая - обеспечивающая «нижний уровень» работы приложения, отвечающая за методы хранения, доступа и разделения данных; вторая - организующая «верхний уровень» работы приложения, включающий в себя логику обработки данных и интерфейс пользователя.
Первая составляющая, как правило, представляла собой набор компонентов сторонних разработчиков. Часто она так или иначе была связана с доступом к базам данных, которые могли предусматривать достаточно сложную организацию. Для работы ее компонентов требовалось наличие высокопроизводительной вычислительной системы.
Вторая составляющая включала в себя собственно алгоритмы, логику и весь интерфейс, созданные разработчиками программы. Она требовала наличие связи с методами доступа к данным, содержащимся в первой составляющей. Требования к вычислительным системам, необходимым для выполнения ее компонентов, были обычно существенно ниже, чем для первой составляющей.
Тогда сложилось понятие приложения, построенного на основе архитектуры «клиент-сервер». В первую (серверную) составляющую такого приложения относят все методы, связанные с доступом к данным. Чаще всего их реализует сер-
Вер БД (сервер данных) из соответствующей СУБД (системы управления базами данных) в комплекте с драйверами доступа к нему. Во вторую (клиентскую) часть приложения относят все методы обработки данных и представления их пользователю. Клиентская часть взаимодействует, с одной стороны, с сервером, получая от него данные, а с другой стороны - с пользователем, ресурсами приложения и ОС, осуществляя обработку данных и отображение результатов. Результаты обработки клиент опять-таки может сохранить в БД, воспользовавшись функциями серверной части.
Кроме того, со временем на рынке СУБД стали доминировать несколько наиболее известных компаний-производителей. Они предлагали стандартизованные интерфейсы для доступа к создаваемым ими СУБД. На них, в свою очередь, стали ориентироваться и разработчики прикладных программ. Такая ситуация оказала влияние и на структуру систем программирования. Многие из них стали предлагать средства, ориентированные на создание приложений в архитектуре «клиент-сервер». Как правило, эти средства поставляются в составе системы программирования и поддерживают возможность работы с широким диапазоном известных серверов данных через один или несколько доступных интерфейсов обмена данными. Разработчик прикладной программы выбирает одно из доступных средств плюс возможный тип сервера (или несколько воз- : можных типов), и тогда его задача сводится только к созданию клиентской части, ; приложения, построенной на основе выбранного интерфейса. Создав клиентскую часть, разработчик может далее использовать и распространять ее только в комплексе с соответствующими средствами из состава системы программирования. Интерфейс обмена данными обычно входит в состав системы программирования. Большинство систем программирования предоставляют возможность распространения средств доступа к серверной части без каких-либо дополнительных ограничений.
Что касается серверной части, то возможны два пути: простейшие серверы БД требуют от разработчиков приобретения лицензий на средства создания и отладки БД, но часто позволяют распространять результаты работы без дополнительных ограничений; мощные серверы БД, ориентированные на работу десятков и сотен пользователей, требуют приобретения лицензий как на создание, так и на распространение серверной части приложения. В этом случае конечный пользователь приложения получает целый комплекс программных продуктов от множества разработчиков.
Более подробно об организации приложений на основе архитектуры «клиент-сервер» можно узнать в .
UTM-метки - набор данных, добавляемых к URL с целью получения дополнительной информации в рамках оценки продуктивности маркетинговых кампаний. UTM-tags были разработаны компанией Urchin Software, поглощенной Google. Пять предлагаемых этими тегами параметров позволяют оценить, насколько успешно то или иное объявление. Данные, получаемые в результате таких GET-запросов, обрабатываются в различных сервисах аналитики, среди которых самые востребованные - Google Analytics и Яндекс.Метрика.
Постановка метки - процесс простой и не требующий специфических знаний (кроме тех, о которых я расскажу в данной статье).
Итак, прежде, чем перейти к формированию URL, разберемся с тем, что позволяют оценивать имеющиеся атрибуты:
- источник перехода (Google, e-mail и т.д.);
- тип трафика (например, PPC или КМС);
- наименование кампании, обеспечившей переход;
- ключ;
- дополнительные сведения для различия объявлений.
Первые три параметра из приведенного списка являются для меток обязательными, а следующие, соответственно, необязательными. Теперь познакомимся с ними подробнее. Независимо от того, в какой сети создана кампания, применяются одни и те же метки:
- utm_source;
- utm_medium;
- utm_campaign;
- utm_term;
Каждая являет собой единство двух компонентов: параметра из числа приведенных выше и соответствующего ему значения. Между ними ставится знак равенства. Поскольку меток в каждом URL минимум три, их необходимо разделить между собой, для чего используется символ &.
Теперь, собственно, образец:
domen.com/?utm_source =google&utm_medium =cpc&utm_campaign =my_sale
Значение устанавливается так, чтобы маркетолог при анализе с помощью систем аналитики имел возможность оперативно оценивать источники переходов (например, utm_ source =adwords или utm_ source = vk) , и типы трафика (utm-medium =cpc или utm-medium =ppc) .
Как видите, никаких сложностей с созданием ссылки нет, достаточно лишь задать необходимые параметры. Чтобы не ошибиться при написании ссылки или просто облегчить себе работу, когда требуется их большое количество, можно использовать специальные сервисы - генераторы UTM-меток.
Я хочу точно понять, на какую часть компилятора программы просматривается и на что ссылается компоновщик. Поэтому я написал следующий код:
#include
У меня есть три функции:
- DefinedCorrectFunction - это нормальная функция, объявленная и определенная правильно.
- DefinedIncorrectFunction - эта функция объявлена правильно, но реализация неверна (отсутствует;)
- NonDefinedFunction - только объявление. Нет определения.
FunctionTemplate - шаблон функции.
Теперь, если я скомпилирую этот код, я получаю ошибку компилятора для отсутствующего ";" в DefinedIncorrectFunction.
Предположим, я исправить это, а затем прокомментировать testObject.NonDefinedFunction(2). Теперь я получаю ошибку компоновщика.
Теперь закомментируйте testObject.FunctionTemplate(2). Теперь я получаю ошибку компилятора для отсутствующих ";".
Для шаблонов функций я понимаю, что они не тронуты компилятором, если они не вызываются в коде. Итак, недостающие ";" не жалуется компилятором, пока я не вызвал testObject.FunctionTemplate(2).
Для testObject.NonDefinedFunction(2) компилятор не жаловался, но компоновщик сделал. Насколько я понимаю, весь компилятор должен был знать, что объявлена функция NonDefinedFunction. Он не заботился об осуществлении. Затем линкер жаловался, потому что не смог найти реализацию. Пока все хорошо.
Итак, я не совсем понимаю, что именно делает компилятор и что делает компоновщик. Мое понимание компонентов компоновщика ссылок со своими вызовами. Так что, когда NonDefinedFunction называется, он ищет скомпилированную реализацию NonDefinedFunction и жалуется. Но компилятор не заботился о реализации NonDefinedFunction, но это делалось для DefinedIncorrectFunction.
Я бы очень признателен, если кто-нибудь сможет объяснить это или дать некоторую ссылку.
8 ответов
Функция компилятора заключается в том, чтобы скомпилировать написанный вами код и преобразовать его в файлы объектов. Поэтому, если вы пропустили; или использовали переменную undefined, компилятор будет жаловаться, потому что это синтаксические ошибки.
Если компиляция выполняется без каких-либо сбоев, создаются объектные файлы . Объектные файлы имеют сложную структуру, но в основном содержат пять вещей
- Заголовки - информация о файле
- Код объекта - код в машинном языке (этот код не может работать сам по себе в большинстве случаев)
- Информация о переезде. Каким частям кода необходимо будет изменить адреса при фактическом выполнении.
- Таблица символов . Символы, на которые ссылается код. Они могут быть определены в этом коде, импортированы из других модулей или определены компоновщиком
- Отладочная информация - используется отладчиками
Компилятор компилирует код и заполняет таблицу символов каждым символом, с которым он сталкивается. Символы относятся к переменным и функциям. Ответ на Этот вопрос объясняет таблицу символов.
Здесь содержится коллекция исполняемого кода и данных, которые компоновщик может обрабатывать в рабочем приложении или в общей библиотеке. Объектный файл имеет структуру данных, называемую таблицей символов в ней, которая сопоставляет различные элементы в объектном файле именам, которые может понять компоновщик.
Точка примечания
Если вы вызываете функцию из своего кода, компилятор не ставит конечный адрес подпрограммы в объектном файле. Вместо этого он ставит значение-заполнитель в код и добавляет примечание, которое сообщает компоновщику искать ссылку в различных таблицах символов из всех объектные файлы, которые он обрабатывает, и вставьте конечное местоположение там.
Сгенерированные объектные файлы обрабатываются компоновщиком, который заполняет пробелы в таблицах символов, связывает один модуль с другим и, наконец, дает исполняемый код, который может быть загружен загрузчиком.
Итак, в вашем конкретном случае -
- DefinedIncorrectFunction() - компилятор получает определение функции и начинает компилировать его для создания объектного кода и вставки соответствующей ссылки в таблицу символов. Ошибка компиляции из-за ошибки синтаксиса, поэтому компилятор прерывается с ошибкой.
- NonDefinedFunction() - компилятор получает декларацию, но не имеет определения, поэтому добавляет запись в таблицу символов и помещает компоновщик для добавления соответствующих значений (поскольку компоновщик обрабатывает кучу объектных файлов, возможно, это определение присутствует в некоторых другой файл объекта). В вашем случае вы не указываете какой-либо другой файл, поэтому компоновщик прерывается с ошибкой undefined reference to NonDefinedFunction , потому что он не может найти ссылку на соответствующую запись в таблице символов.
Чтобы понять это, еще раз скажем, что ваш код структурирован следующим образом
#include
Файл try.cpp
#include "try.h"
void Test::DefinedCorrectFunction(int val)
{
i = val;
}
void Test::DefinedIncorrectFunction(int val)
{
i = val;
}
int main()
{
Test testObject(1);
testObject.NonDefinedFunction(2);
//testObject.FunctionTemplate
Давайте сначала скопируем и собираем код, но не свяжем его
$g++ -c try.cpp -o try.o $
Этот шаг протекает без каких-либо проблем. Таким образом, у вас есть объектный код в try.o. Попробуйте и соедините его.
$g++ try.o try.o: In function `main": try.cpp:(.text+0x52): undefined reference to `Test::NonDefinedFunction(int)" collect2: ld returned 1 exit status
Вы забыли определить Test:: NonDefinedFunction. Пусть определите его в отдельном файле.
Файл-try1.cpp
#include "try.h" void Test::NonDefinedFunction(int val) { i = val; }
Скомпилируем его в объектный код
$ g++ -c try1.cpp -o try1.o $
Снова это успешно. Попробуем связать только этот файл
$ g++ try1.o /usr/lib/gcc/x86_64-redhat-linux/4.4.5/../../../../lib64/crt1.o: In function `_start": (.text+0x20): undefined reference to `main" collect2: ld returned 1 exit status
Нет основной так выигранной; t link!!
Теперь у вас есть два отдельных объектных кода, в которых есть все необходимые компоненты. Просто передайте ОБОИХ из них в компоновщик, и пусть это сделает остальные
$ g++ try.o try1.o $
Нет ошибок! Это связано с тем, что компоновщик находит определения всех функций (даже если они разбросаны в разных объектных файлах) и заполняет пробелы в объектных кодах соответствующими значениями
Скажите, что вы хотите съесть какой-то суп, поэтому отправляйтесь в ресторан.
Вы ищете меню для супа. Если вы не найдете его в меню, вы покидаете ресторан. (вроде компилятора, жалующегося на то, что он не смог найти функцию). Если вы его найдете, что вы делаете?
Ты позвонишь официанту, чтобы пойти с твоим супом. Однако, просто потому, что он в меню, не означает, что они также имеют его на кухне. Может быть устаревшее меню, может быть, кто-то забыл сказать шеф-повару, что он должен сделать суп. Так что снова вы уходите. (например, ошибка от компоновщика, что он не мог найти символ)
Я считаю, что это ваш вопрос:
Где я запутался, когда компилятор жаловался на DefinedIncorrectFunction. Он не искал реализацию NonDefinedFunction, но прошел через DefinedIncorrectFunction.
Компилятор попытался разобрать DefinedIncorrectFunction (потому что вы предоставили определение в этом исходном файле), и произошла синтаксическая ошибка (отсутствовала точка с запятой). С другой стороны, компилятор никогда не видел определения для NonDefinedFunction , потому что в этом модуле просто не было кода. Возможно, вы указали определение NonDefinedFunction в другом исходном файле, но компилятор этого не знает. Компилятор просматривает только один исходный файл (и его включенные файлы заголовков) за раз.
Компилятор проверяет, соответствует ли исходный код языку и соответствует семантике языка. Вывод компилятора - это объектный код.
Компоновщик связывает различные объектные модули вместе, чтобы сформировать exe. Определения функций расположены в этой фазе, и на этом этапе добавляется соответствующий код для их вызова.
Компилятор компилирует код в виде единиц перевода . Он скомпилирует весь код, который включен в исходный файл.cpp ,
DefinedIncorrectFunction() определяется в вашем исходном файле, поэтому компилятор проверяет его на корректность языка.
NonDefinedFunction() имеет какое-либо определение в исходном файле, поэтому компилятору не нужно его компилировать, если определение присутствует в каком-то другом исходном файле, функция будет скомпилирована как часть этой единицы перевода, а позже линкер свяжет к нему, если на этапе связывания определение не найдено компоновщиком, тогда оно вызовет ошибку связывания.
Что делает компилятор, и что делает компоновщик, зависит от реализация: правовая реализация может просто хранить токенизированные источник в "компиляторе" и делать все в компоновщике. Современные реализации ставят все больше и больше на компоновщик, для лучшая оптимизация. И многие ранние реализации шаблонов не даже посмотрите код шаблона до тех пор, пока время ссылки, кроме соответствующих фигурных скобок достаточно знать, где шаблон закончился. С точки зрения пользователя, вас больше интересует, требует ли ошибка "диагностика" (которая может быть выбрана компилятором или компоновщиком) или undefined.
В случае DefinedIncorrectFunction вы предоставляете исходный текст который требуется для анализа. Этот текст содержит ошибка, для которой требуется диагностика. В случае NonDefinedFunction: если функция используется, отказ предоставить определение (или предоставление более одного определения) в полном программа является нарушением одного правила определения, которое undefined поведение. Диагностика не требуется (но я не могу представить которые не предусматривали какого-либо недостающего определения функция, которая была использована).
На практике ошибки, которые могут быть легко обнаружены просто путем изучения текстовый ввод одной единицы перевода определяется стандартом "требуется диагностика", и будет обнаружен компилятор. Ошибки, которые не могут быть обнаружены при рассмотрении единая единица перевода (например, отсутствующее определение, которое может быть присутствующие в другой единицы перевода) формально undefined поведение, во многих случаях ошибки могут быть обнаружены компоновщиком, и в таких случаях реализация фактически выдает ошибку.
Это несколько изменено в таких случаях, как встроенные функции, где вы разрешено повторять определение в каждой единицы перевода и измененный шаблонами, поскольку многие ошибки не могут быть обнаружены до тех пор, пока конкретизации. В случае шаблонов стандартный лист реализаций большая свобода: по крайней мере, компилятор должен проанализируйте шаблон достаточно, чтобы определить, где заканчивается шаблон. добавленные стандартные вещи, такие как typename , тем не менее, позволяют значительно больше синтаксический анализ перед созданием. Однако в зависимых контекстах некоторые ошибки не могут быть обнаружены до создания экземпляра, что может место во время компиляции или время ссылки; ранние реализации предпочтительная компоновка времени ссылки; время компиляции сегодня, и используется VС++ и g++.
[из методы]
Определение 9.22 Компоновщик (редактор связей) " это программа, предназначенная для связывания между собой объектных файлов, порождаемых компилятором, и файлов библиотек, входящих в состав системы программирования.
Объектный файл не может быть запущен до тех пор, пока все модули и секции не будут в нем увязаны между собой. Результатом работы компоновщика является исполняемый файл. Этот файл включает в себя текст программы на языке машинных кодов. При попытке создать исполняемый файл компоновщик может вывести сообщение об ошибке, если он не обнаружил какого-либо компонента.
Сначала компоновщик выбирает из первого объектного модуля программную секцию и присваивает ей начальный адрес. Программные секции остальных объектных модулей получают адреса относительно начального адреса в порядке следования. При этом может осуществляться выравнивание адресов программных секций. Одновременно с объединением текстов программных секций объединяются секции данных, таблицы идентификаторов и внешних времен. Разрешаются межсекционных ссылки.
Процедура разрешения ссылок сводится к вычислению значений адресных констант процедур, функций и переменных с учетом перемещений секций относительно начала собираемого программного модуля. Если при этом обнаруживаются ссылки к внешним переменным, отсутствующим в списке объектных модулей, редактор связей организует их поиск в библиотеке необходимую составляющую найти не удается, формируется сообщение об ошибке.
Обычно компоновщик формирует простейший программный модуль, создаваемый как единое целое. Однако в более сложных случаях компоновщик может создавать и другие модули: программные модули с оверлейной структурой, объектные модули библиотек и модули динамически подключаемых библиотек.
Компоновщик (также реда́ктор свя́зей, линкер - от англ. link editor, linker) - программа, которая производит компоновку - принимает на вход один или несколько объектных модулей и собирает по ним исполнимый модуль.
Для связывания модулей компоновщик использует таблицы имён, созданные компилятором в каждом из объектных модулей. Такие имена могут быть двух типов:
Определённые или экспортируемые имена - функции и переменные, определённые в данном модуле и предоставляемые для использования другим модулям
Неопределённые или импортируемые имена - функции и переменные, на которые ссылается модуль, но не определяет их внутри себя
Работа компоновщика заключается в том, чтобы в каждом модуле разрешить ссылки на неопределённые имена. Для каждого импортируемого имени находится его определение в других модулях, упоминание имени заменяется на его адрес.
Компоновщик обычно не выполняет проверку типов и количества параметров процедур и функций. Если надо объединить объектные модули программ, написанные на языках со строгой типизацией, то необходимые проверки должны быть выполнены дополнительной утилитой перед запуском редактора связей.