Gnu gcc компилятор для Windows
Gnu gcc compiler (GNU Compiler Collection) – это популярный набор компиляторов, разрабатываемый Фондом свободного программного обеспечения (GNU). Он предоставляет возможность компиляции исходных кодов на различных языках программирования в исполняемый код для различных платформ.
Для пользователей операционной системы Windows установка и использование Gnu gcc compiler может показаться сложной задачей. Однако, соответствующая настройка программного обеспечения является необходимым условием для разработки приложений и внедрения новых технологий в Windows-среде.
Установка Gnu gcc compiler на Windows начинается с загрузки необходимого дистрибутива с официального сайта GNU. В процессе установки можно выбрать необходимые компоненты для компиляции кода на определенном языке программирования. После завершения процесса установки следует добавить путь к папке с исполняемыми файлами компилятора в переменную среды PATH.
Использование Gnu gcc compiler после установки предоставляет возможность компиляции исходных кодов на C, C++, Fortran и других языках. Для компиляции кода следует запустить командную строку или терминал, перейти в директорию с исходными файлами и выполнить необходимую команду компиляции с указанием нужных параметров и файлов для компиляции. Результатом работы будет получение исполняемого файла.
Использование Gnu gcc compiler для windows является ключевым элементом для разработки приложений и создания программного обеспечения. Владение этим компилятором позволяет разработчикам полностью раскрыть свой потенциал и создавать мощные программы для Windows-среды.
Установка GNU GCC Compiler для Windows
Он является свободным программным обеспечением и может быть использован на различных операционных системах, включая Windows.
Для установки GNU GCC Compiler на Windows необходимо выполнить следующие шаги:
1. | Перейдите на официальный сайт GNU GCC по адресу https://gcc.gnu.org/. |
2. | На странице загрузки выберите раздел «Mirrors» и выберите зеркало загрузки, расположенное наиболее близко к вашему местоположению. |
3. | На странице выбранного зеркала загрузки найдите раздел «Downloads» и выберите версию компилятора GCC, совместимую с вашей операционной системой. |
4. | Скачайте установочный файл компилятора GCC для Windows. |
5. | Запустите скачанный установочный файл и следуйте инструкциям мастера установки. |
6. | По завершении установки проверьте, что путь к исполняемым файлам компилятора GCC добавлен в переменные среды PATH, чтобы можно было запускать компилятор из командной строки. |
7. | Для проверки правильности установки выполните команду gcc —version в командной строке. Если компилятор GCC был успешно установлен, вы увидите его версию. |
Теперь вы готовы использовать GNU GCC Compiler для разработки на языках C и C++ на платформе Windows.
Коллекция компиляторов GNU — GNU Compiler Collection
Коллекция компиляторов GNU (GCC ) — это система компилятора, созданная GNU Проект поддерживает различные языки программирования. GCC является ключевым компонентом GNU toolchain и стандартным компилятором для большинства проектов, связанных с GNU и Linux, включая ядро Linux. Фонд свободного программного обеспечения (FSF) распространяет GCC под Стандартной общественной лицензией GNU (GNU GPL). GCC сыграл важную роль в развитии бесплатного программного обеспечения как инструмент и пример.
Когда он был впервые выпущен в 1987 году, GCC 1.0 назывался GNU C Compiler, поскольку он обрабатывал только язык программирования C. В декабре того же года он был расширен для компиляции C ++. Внешние интерфейсы были позже разработаны для Objective-C, Objective-C ++, Fortran, Java, Ada и Go, среди прочего.
Версия 4.5 спецификации OpenMP теперь поддерживается в компиляторах C и C ++ и «значительно улучшена «также поддерживается реализация спецификации OpenACC 2.0a. По умолчанию текущая версия поддерживает gnu ++ 14, расширенный набор C ++ 14, и gnu11, расширенный набор C11, также доступна строгая стандартная поддержка. Начиная с GCC 9, поддержка C ++ 17 больше не является экспериментальной, и она, или строго gnu ++ 17, используется по умолчанию в (предстоящем) GCC 11. GCC также предоставляет экспериментальную поддержку для C ++ 20.
GCC был перенесен на широкий спектр архитектур наборов команд и широко используется в качестве инструмента при разработке как бесплатных, так и фирменное программное обеспечение. GCC также доступен для многих встроенных систем, включая ARM ; Микросхемы на базе AMCC и Freescale Power ISA. Компилятор может работать с широким спектром платформ.
Помимо того, что GCC является официальным компилятором операционной системы GNU, он был принят в качестве стандартного компилятора многими другими современными Unix-подобными компьютерами операционные системы, включая большинство дистрибутивов Linux. Большинство операционных систем семейства BSD также перешли на GCC, хотя с тех пор некоторые BSD, включая FreeBSD и OpenBSD, с тех пор перешли на Clang компилятор. macOS также переключился на Clang после использования GCC. Также доступны версии для Microsoft Windows и других операционных систем; GCC может компилировать код для Android и iOS.
- 1 История
- 2 Дизайн
- 2.1 Интерфейсы
- 2.2 GENERIC и GIMPLE
- 2.3 Оптимизация
- 2.4 Серверная часть
- 2.5 Возможности
- 11.1 Официальные
- 11.2 Прочие
История
В целях начальной загрузки операционной системы GNU, Ричард Столлман попросил Эндрю С. Таненбаума, автора Amsterdam Compiler Kit (также известного как Free University Compiler Kit) для разрешение на использование этого программного обеспечения для GNU. Когда Таненбаум сообщил ему, что компилятор не является бесплатным и что свободен только университет, Столлман решил написать новый компилятор. Первоначальный план Столлмана состоял в том, чтобы переписать существующий компилятор из Ливерморской национальной лаборатории с Pastel на C с некоторой помощью Len Tower и других. Столлман написал новый интерфейс C для компилятора Livermore, но затем понял, что для этого требуются мегабайты стекового пространства, что невозможно в системе 68000 Unix, имеющей только 64 КБ, и пришел к выводу, что ему придется написать новый компилятор с нуля. Ни один из кодов компилятора Pastel не попал в GCC, хотя Столлман действительно использовал написанный им интерфейс на C.
GCC был впервые выпущен 22 марта 1987 г., доступен по FTP с Массачусетский технологический институт. Столлман был указан как автор, но процитировал других за их вклад, включая Джека Дэвидсона и Кристофера Фрейзера за идею использования RTL в качестве промежуточного языка, Пола Рубина для написания большей части препроцессора и Леонарда Тауэра за » части анализатора, генератора RTL, определений RTL и описания машины Vax «. Описанный Салусом «первым хитом свободного программного обеспечения», компилятор GNU появился как раз в то время, когда Sun Microsystems отделяла свои инструменты разработки от своей операционной системы, продавая их отдельно по более высокой комбинированной цене, чем предыдущий комплект, что побудило многих пользователей Sun купить или загрузить GCC вместо инструментов поставщика. К 1990 году GCC поддерживал тринадцать компьютерных архитектур, превосходил по производительности компиляторы нескольких поставщиков, поставлялся Data General и NeXT с их рабочими станциями и использовался Lotus Development Corporation.
Поскольку GCC был лицензирован по GPL, программисты, желающие работать в других направлениях, особенно те, которые пишут интерфейсы для языков, отличных от C, могли свободно разрабатывать свои собственные fork компилятора при условии, что они соответствуют условиям GPL., включая его требования по распространению исходного кода. Однако использование нескольких форков оказалось неэффективным и громоздким, а трудность принятия работы официальным проектом GCC сильно разочаровывала многих. FSF так строго контролировал то, что было добавлено в официальную версию GCC 2.x, что GCC использовался в качестве одного из примеров «соборной» модели развития в Эрик С. Реймонд в эссе Собор и базар.
В 1997 году группа разработчиков сформировала Experimental / Enhanced GNU Compiler System (EGCS), чтобы объединить несколько экспериментальных форков в один проект. Основой слияния стал снимок состояния разработки GCC, сделанный между версиями 2.7 и 2.81. Объединенные проекты включали g77 (Fortran), PGCC (P5 Pentium -optimized GCC), множество улучшений C ++, а также множество новых архитектур и вариантов операционной системы. Разработка EGCS оказалась значительно более энергичной, чем разработка GCC, настолько, что FSF официально остановила разработку своего компилятора GCC 2.x, благословила EGCS в качестве официальной версии GCC и назначила проект EGCS сопровождающими GCC в апреле 1999 года. с выпуском GCC 2.95 в июле 1999 года эти два проекта снова были объединены.
GCC с тех пор поддерживается разнообразной группой программистов со всего мира под руководством руководящего комитета. Он был перенесен на большее количество типов процессоров и операционных систем, чем любой другой компилятор.
GCC был перенесен на широкий спектр архитектур наборов команд и широко используется в качестве инструмента для разработки как бесплатного, так и проприетарного программного обеспечения. GCC также доступен для многих встроенных систем, включая Symbian (называемый gcce), на базе ARM ; Микросхемы на базе AMCC и Freescale Power ISA. Компилятор может работать с широким спектром платформ, включая игровые консоли, такие как PlayStation 2, Cell SPE для PlayStation 3 и Dreamcast.
Design
Чтобы получить стабильный ABI, например, Linux Standard Base стремится обеспечить, важна версия компилятора.
Внешний интерфейс GCC следует соглашениям Unix. Пользователи вызывают программу драйвера для конкретного языка ( gcc для C, g ++ для C ++ и т. Д.), Которая интерпретирует аргументы команды, вызывает фактический компилятор, запускает ассемблер на выходе, а затем, при необходимости, запускает компоновщик для создания полного исполняемого файла двоичного файла.
Каждый из компиляторов языка представляет собой отдельную программу, которая считывает исходный код и выводит машинный код. Все имеют общую внутреннюю структуру. Интерфейс для каждого языка анализирует исходный код на этом языке и создает абстрактное синтаксическое дерево (сокращенно «дерево»).
Они при необходимости преобразуются во входное представление среднего конца, называемое ОБЩЕЙ формой; средний конец затем постепенно трансформирует программу в ее окончательную форму. Оптимизация компилятора и методы статического анализа кода (такие как FORTIFY_SOURCE, директива компилятора, которая пытается обнаружить некоторые переполнения буфера ) применяются к коду. Они работают с несколькими представлениями, в основном с архитектурно-независимым представлением GIMPLE и архитектурно-зависимым представлением RTL. Наконец, машинный код создается с использованием зависящего от архитектуры сопоставления с образцом, первоначально основанного на алгоритме Джека Дэвидсона и Криса Фрейзера.
GCC был написан в основном на C, за исключением частей Ada внешнего интерфейса. Дистрибутив включает стандартные библиотеки для Ada, C ++ и Java, код которых в основном написан на этих языках. На некоторых платформах дистрибутив также включает низкоуровневую библиотеку времени выполнения libgcc, написанную на комбинации машинно-независимого C и зависящего от процессора машинного кода, предназначенного в первую очередь для обработки арифметических операций. операции, которые целевой процессор не может выполнять напрямую.
В мае 2010 года руководящий комитет GCC решил разрешить использование компилятора C ++ для компиляции GCC. Компилятор был предназначен для написания на C плюс подмножество функций из C ++. В частности, это было решено, чтобы разработчики GCC могли использовать функции C ++ деструкторы и generics.
В августе 2012 года руководящий комитет GCC объявил, что GCC теперь использует C ++ в качестве языка реализации. Это означает, что для сборки GCC из исходных кодов требуется компилятор C ++, который понимает стандарт ISO / IEC C ++ 03.
18 мая 2020 г. GCC перешел от стандарта ISO / IEC C ++ 03 к стандарту ISO / IEC C ++ 11 (т.е. необходим для компиляции, bootstrap, сам компилятор; однако по умолчанию он компилирует более поздние версии C ++).
Интерфейсы
Каждый интерфейс использует синтаксический анализатор для создания абстрактное синтаксическое дерево заданного исходного файла. Из-за абстракции синтаксического дерева исходные файлы любого из различных поддерживаемых языков могут обрабатываться одной и той же серверной частью . GCC начал использовать парсеры LALR, созданные с помощью Bison, но постепенно переключился на написанные вручную парсеры с рекурсивным спуском для C ++ в 2004 году, а также для C и Objective- C в 2006 году. В настоящее время все интерфейсы используют рукописные синтаксические анализаторы с рекурсивным спуском.
До GCC 4.0 древовидное представление программы не было полностью независимым от целевого процессора.
Значение дерева было несколько различным для различных языковых интерфейсов, и внешние интерфейсы могли предоставлять свои собственные древовидные коды. Это было упрощено с появлением GENERIC и GIMPLE, двух новых форм не зависящих от языка деревьев, которые были представлены с появлением GCC 4.0. GENERIC более сложен, он основан на промежуточном представлении интерфейса Java GCC 3.x. GIMPLE — это упрощенный GENERIC, в котором различные конструкции относятся к нескольким инструкциям GIMPLE. Внешние интерфейсы C, C ++ и Java создают GENERIC непосредственно во внешнем интерфейсе. Вместо этого другие интерфейсы имеют разные промежуточные представления после синтаксического анализа и преобразования их в GENERIC.
В любом случае так называемый «gimplifier» затем преобразует эту более сложную форму в более простую форму GIMPLE на основе SSA, которая является общим языком для большого числа мощных языковых программ. и независимая от архитектуры глобальная оптимизация (объем функций).
GENERIC и GIMPLE
GENERIC — это язык промежуточного представления, используемый в качестве «среднего уровня» при компиляции исходного кода в исполняемые двоичные файлы. Подмножество, называемое GIMPLE, предназначено для всех интерфейсов GCC.
Средний этап GCC выполняет весь анализ кода и оптимизацию, работая независимо как от скомпилированного языка, так и от целевой архитектуры, начиная с GENERIC представления и расширяя его до зарегистрировать язык перевода (RTL). Представление GENERIC содержит только подмножество императивных программных конструкций, оптимизированных средним концом.
При преобразовании исходного кода в GIMPLE сложные выражения разбиваются на трехадресный код с использованием временных переменных. Это представление было вдохновлено представлением SIMPLE, предложенным в компиляторе McCAT Лори Дж. Хендрен для упрощения анализа и оптимизации императивных программ.
Оптимизация
Оптимизация может происходить во время любой этап компиляции; однако основная часть оптимизаций выполняется после синтаксиса и внешнего интерфейса и до генерации кода серверной части; таким образом, обычное, хотя и несколько противоречивое, название этой части компилятора — «средний конец».
Точный набор оптимизаций GCC варьируется от выпуска к выпуску по мере его разработки, но включает стандартные алгоритмы, такие как оптимизация цикла, потоковая передача с переходом, исключение общего подвыражения, планирование инструкций и так далее. Оптимизация RTL имеет меньшее значение с добавлением глобальных оптимизаций на основе SSA для деревьев GIMPLE, поскольку RTL-оптимизации имеют гораздо более ограниченную область действия и содержат меньше высокоуровневой информации.
Бэкэнд
Бэкэнд GCC частично определяется макросами препроцессора и функциями, специфичными для цели архитектура, например, для определения ее порядка байтов, размера слова и соглашений о вызовах. Передняя часть серверной части использует их, чтобы помочь решить генерацию RTL, поэтому, хотя RTL GCC номинально не зависит от процессора, начальная последовательность абстрактных инструкций уже адаптирована к цели. В любой момент фактические инструкции RTL, формирующие представление программы, должны соответствовать целевой архитектуре.
Файл описания машины содержит шаблоны RTL, а также ограничения операндов и фрагменты кода для вывода окончательной сборки. Ограничения указывают, что конкретный шаблон RTL может применяться (например) только к определенным аппаратным регистрам или (например) разрешать немедленные смещения операндов только ограниченного размера (например, 12, 16, 24. битовые смещения и т. Д.)). Во время генерации RTL проверяются ограничения для данной целевой архитектуры. Чтобы выдать данный фрагмент RTL, он должен соответствовать одному (или нескольким) шаблонам RTL в файле описания машины и удовлетворять ограничениям для этого шаблона; в противном случае было бы невозможно преобразовать окончательный RTL в машинный код.
К концу компиляции действительный RTL сводится к строгой форме, в которой каждая инструкция ссылается на регистры реальной машины и образец из файла описания целевой машины. Формирование строгого RTL — сложная задача; важным шагом является выделение регистров, где реальные аппаратные регистры выбираются для замены первоначально назначенных псевдорегистров. Затем следует фаза «перезагрузки»; любые псевдорегистры, которым не был назначен реальный аппаратный регистр, «переливаются» в стек, и генерируется RTL для выполнения этого распределения. Точно так же смещения, которые слишком велики для размещения в реальной инструкции, должны быть разбиты и заменены последовательностями RTL, которые будут подчиняться ограничениям смещения.
На заключительном этапе машинный код создается путем вызова небольшого фрагмента кода, связанного с каждым шаблоном, для генерации реальных инструкций из целевого набора инструкций с использованием последних регистров., смещения и адреса, выбранные на этапе перезагрузки. Фрагмент создания сборки может быть просто строкой, и в этом случае выполняется простая строковая подстановка регистров, смещений и / или адресов в строку. Фрагмент создания сборки также может быть коротким блоком кода C, выполняющим некоторую дополнительную работу, но в конечном итоге возвращающим строку, содержащую допустимый код сборки.
Возможности
Некоторые функции GCC включают:
- Оптимизация времени компоновки оптимизирует границы объектного файла для непосредственного улучшения связанного двоичного файла. Оптимизация времени компоновки полагается на промежуточный файл, содержащий сериализацию некоторого представления Gimple, включенного в объектный файл. Файл создается вместе с объектным файлом во время компиляции исходного кода. Каждая исходная компиляция создает отдельный объектный файл и вспомогательный файл времени компоновки. Когда объектные файлы связаны, компилятор запускается снова и использует вспомогательные файлы для оптимизации кода в отдельно скомпилированных объектных файлах.
- Плагины могут напрямую расширять компилятор GCC. Плагины позволяют адаптировать стандартный компилятор к конкретным потребностям с помощью внешнего кода, загружаемого в виде плагинов. Например, плагины могут добавлять, заменять или даже удалять промежуточные проходы, работающие с представлениями Gimple. Уже опубликовано несколько подключаемых модулей GCC, в частности подключаемый модуль GCC Python, который ссылается на libpython и позволяет вызывать произвольные сценарии Python изнутри компилятора. Цель состоит в том, чтобы позволить писать плагины GCC на Python. Подключаемый модуль MELT предоставляет высокоуровневый Lisp -подобный язык для расширения GCC.
- «Транзакционная память C ++ при компиляции с -fgnu-tm.»
- Начиная с GCC 10, идентификаторы допускают кодировку UTF-8 (Unicode), т.е. исходный код C по умолчанию использует кодировку UTF-8.
Языки
Стандартный компилятор выпуски, начиная с 7, включают интерфейсы для C ( gcc ), C ++ ( g ++ ), Objective-C, Objective-C ++, Фортран ( gfortran ), Ада (GNAT ) и Go ( gccgo ). Также поддерживается популярное расширение параллельного языка OpenMP. В версии 5.0 добавлена поддержка Cilk Plus, в версии 9.1 добавлена поддержка D, а начиная с версии 5.1, имеется предварительная поддержка OpenACC. Версии до GCC 7 также поддерживали Java ( gcj ), что позволяло компилировать Java в собственный машинный код.
Интерфейс Fortran был g77 до версии 4.0, которая поддерживает только FORTRAN 77. В более новых версиях g77 удаляется в пользу нового внешнего интерфейса GNU Fortran (с сохранением большинства языковых расширений g77), который поддерживает Fortran 95 и большую часть Fortran 2003 и Fortran 2008, а также. Интерфейс для CHILL был удален из-за отсутствия обслуживания.
Сторонние интерфейсы существуют для Pascal ( gpc ), Modula-2, Modula-3, PL / I и VHDL ( ghdl ).
Существует несколько экспериментальных ветвей для поддержки дополнительных языков, таких как компилятор GCC UPC для Unified Parallel C.
Архитектуры
семейства целевых процессоров GCC как версии 4.3 включают (примечание, GCC 6 и более ранние версии больше не поддерживаются):
Начиная с GCC 10, поддерживается еще несколько (например, SPU, то есть Cell, удален), например графические процессоры Nvidia, то есть промежуточный код Nvidia PTX, а также AMD Код GCN, 64-битный ARM (AArch64 ), а не только 32-битный, RISC-V, MSP430 и eBPF ( полный язык, отличный от Тьюринга, работающий в ядре Linux ).
Малоизвестные целевые процессоры, поддерживаемые в стандартном выпуске, включают изд:
GCC поддерживает дополнительные процессоры версии, поддерживаемые отдельно от версии FSF:
Компилятор Java gcj может быть нацелен либо на архитектуру машинного языка, либо на байт-код Java виртуальной машины. При перенацеливании GCC на новую платформу часто используется самонастройка. Motorola 68000, Zilog Z80 и другие процессоры также используются в версиях gcc, разработанных для различных программируемых графических калькуляторов Texas Instruments, Hewlett Packard, Sharp и Casio.
Разработка
Текущая стабильная версия GCC — 10.2, выпущенный 23 июля 2020 года.
Начиная с версии 4.8, GCC реализован на C ++.
GCC 4.6 поддерживает много новых Objective-C функции, такие как объявленные и синтезированные свойства, точечный синтаксис, быстрое перечисление, дополнительные методы протокола, атрибуты метода / протокола / класса, расширения классов и новый API времени выполнения GNU Objective-C. Он также поддерживает язык программирования Go и включает библиотеку libquadmath , которая предоставляет математические функции четверной точности для целей, поддерживающих тип данных __float128 .. Библиотека используется для предоставления типа REAL (16) в GNU Fortran для таких целей.
GCC использует в своей сборке множество стандартных инструментов, включая Perl, Flex, Bison и другие распространенные инструменты. Кроме того, в настоящее время для сборки требуется наличие трех дополнительных библиотек: GMP и MPFR.
Основная часть разработки концентрирует основную часть усилий по разработке, где внедряются новые функции и проверено.
Лицензия
GCC лицензируется в соответствии с версией 3 Стандартной общественной лицензии GNU.
Исключение времени выполнения GCC разрешает компиляцию проприетарных и бесплатных software программы с GCC и использование бесплатных программных плагинов. Наличие этого исключения не подразумевает какое-либо общее предположение о том, что на стороннее программное обеспечение не распространяются требования об авторском леве лицензии GCC.
Использует
Несколько компаний делают бизнес на поставке и поддержке портов GCC для различных платформ.
См. Также
- Портал бесплатного программного обеспечения с открытым исходным кодом
- Портал компьютерного программирования
- Список компиляторов
- MinGW
- Concepts (C ++), расширение стандарта C ++. Это расширение поддерживается исключительно GCC.
Ссылки
Дополнительная литература
- Использование коллекции компиляторов GNU (GCC), Free Software Foundation, 2008.
- Коллекция компиляторов GNU (GCC) Internals, Free Software Foundation, 2008.
- Введение в GCC, Network Theory Ltd., 2004 (пересмотрено в августе 2005 г.). ISBN 0-9541617-9-3 .
- Артур Гриффит, GCC: Полный справочник. McGrawHill / Osborne, 2002. ISBN 0-07-222405-3 .
Внешние ссылки
На Викискладе есть материалы, относящиеся к GCC. В Wikibooks есть книга по теме: Внутреннее устройство компилятора GNU C Официальный
- Официальный веб-сайт
- График выпуска GCC
- План разработки GCC
Другое
- Оптимизация GCC
- Коллекция архитектурных и внутренних документов GCC 4.0.2 в IIT Бомбей
- Кернер, Шон Майкл (2 марта 2006 г.). «Новый GCC Heavy по оптимизации». internetnews.com
- Кернер, Шон Майкл (22 апреля 2005 г.). «GCC 4.0 с открытым исходным кодом: старше, быстрее». internetnews.com
- От источника к двоичному: внутренняя работа GCC, Диего Новилло, журнал Red Hat, декабрь 2004 г.
- Статья 2003 г. о GENERIC и GIMPLE
- Маркетинг Cygnus Support, эссе, посвященное разработке GCC в 1990-е годы, с 30 ежемесячными отчетами в разделе «Inside Cygnus Engineering» ближе к концу
- Объявление EGCS 1.0
- Список функций EGCS 1.0
- Fear of Forking, эссе Рика Моэна, в котором записаны семь хорошо известных форков, включая GCC / EGCS
Незамысловатый блог
GCC — GNU Compiler Collection — набор компиляторов и сопутствующих утилит, разработанный в рамках движения GNU. GCC один из старейших Open Source проектов, первый релиз состоялся в 1985 году, автор сам Ричард Столлман. В исходном варианте поддерживал только язык C и аббревиатура GCC расшифровывалась как GNU C Compiler. Постепенно набор доступных языков расширялся, были добавлены компиляторы Fortran, C++, Ada. С уверенностью можно сказать, что современный мир Open Source обязан своим рождением GCC (по крайней мере без GCC он был бы другим). В настоящее время проект находиться под крылом Free Software Foundation. GCC выпускается под лицензией GPLv3 и является стандартным компилятором для большинства свободных UNIX-подобных операционных систем. В базовый набор входят компиляторы языков: C, C++, Objective-C, Java, Fortran, Ada. GCC поддерживает все основные процессорные архитектуры. Официальный сайт проекта gcc.gnu.org
Основы
GCC входит в состав любого дистрибутива Linux и, как правило, устанавливается по умолчанию. Интерфейс GCC, это стандартный интерфейс компилятора на UNIX платформе, уходящий своими корнями в конец 60-х, начало 70-х годов прошлого века — интерфейс командной строки. Не стоит пугаться, за прошедшее время механизм взаимодействия с пользователем был отточен до возможного в данном случае совершенства, и работать с GCC (при наличии нескольких дополнительных утилит и путного текстового редактора) проще, чем с любой из современных визуальных IDE. Авторы набора постарались максимально автоматизировать процесс компиляции и сборки приложений. Пользователь вызывает управляющую программу gcc, она интерпретирует переданные аргументы командной строки (опции и имена файлов) и для каждого входного файла, в соответствии с использованным языком программирования, запускает свой компилятор, затем, если это необходимо, gcc автоматически вызывает ассемблер и линковщик (компоновщик).
Любопытно, компиляторы одни из немногих приложений UNIX для которых не безразлично расширение файлов. По расширению GCC определяет что за файл перед ним и, что с ним нужно (можно) сделать. Файлы исходного кода на языке C должны иметь расширение .c , на языке C++, как вариант, .cpp , заголовочные файлы на языке C .h , объектные файлы .o и так далее. Если использовать неправильное расширение, gcc будет работать не корректно (если вообще согласиться, что-либо делать).
Перейдём к практике. Напишем, откомпилируем и исполним какую-нибудь незамысловатую программу. Не будем оригинальничать, в качестве исходного файла примера программы на языке C сотворим файл с вот таким содержимым:
printf( «Hello World \n » );
Теперь в каталоге c hello.c отдадим команду:
$ gcc hello.c
Через несколько долей секунды в каталоге появиться файл a.out :
$ ls
a.out hello.cЭто и есть готовый исполняемый файл нашей программы. По умолчанию gcc присваивает выходному исполняемому файлу имя a.out (когда-то очень давно это имя означало assembler output).
$ file a.out
a.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.15, not strippedЗапустим получившийся программный продукт:
$ ./a.out
Hello WorldПочему в команде запуска на исполнение файла из текущего каталога необходимо явно указывать путь к файлу? Если путь к исполняемому файлу не указан явно, оболочка, интерпретируя команды, ищет файл в каталогах, список которых задан системной переменной PATH .
$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/gamesКаталоги в списке разделены символом двоеточия. При поиске файлов, оболочка просматривает каталоги в том порядке, в котором они перечислены в списке. По умолчанию, из соображений безопасности, текущий каталог . в список не внесен, соответственно, оболочка исполняемые файлы искать в нем не будет.
Почему не рекомендуется вносить . в PATH ? Считается, что в реальной многопользовательской системе всегда найдется какой-нибудь нехороший человек, который разместит в общедоступном каталоге вредоносную программу с именем исполняемого файла, совпадающим с именем какой-нибудь команды, часто вызываемой местным администратором с правами суперпользователя. Заговор удастся если . стоит в начале списка каталогов.
Утилита file выводит информацию о типе (с точки зрения системы) переданного в коммандной строке файла, для некоторых типов файлов выводит всякие дополнительные сведения касающиеся содержимого файла.
$ file hello.c
hello.c: ASCII C program text
$ file annotation.doc
annotation.doc: CDF V2 Document, Little Endian, Os: Windows, Version 5.1, Code page: 1251, Author: MIH, Template: Normal.dot, Last Saved By: MIH, Revision Number: 83, Name of Creating Application: Microsoft Office Word, Total Editing Time: 09:37:00, Last Printed: Thu Jan 22 07:31:00 2009, Create Time/Date: Mon Jan 12 07:36:00 2009, Last Saved Time/Date: Thu Jan 22 07:34:00 2009, Number of Pages: 1, Number of Words: 3094, Number of Characters: 17637, Security: 0Вот собственно и всё, что требуется от пользователя для успешного применения gcc 🙂
Имя выходного исполняемого файла (как впрочем и любого другого файла формируемого gcc) можно изменить с помощью опции -o :
$ gcc -o hello hello.c
$ ls
hello hello.c
$ ./hello
Hello WorldВ нашем примере функция main() возвращает казалось бы ни кому не нужное значение 0 . В UNIX-подобных системах, по завершении работы программы, принято возвращать в командную оболочку целое число — в случае успешного завершения ноль, любое другое в противном случае. Интерпретатор оболочки автоматически присвоит полученное значение переменной среды с именем ? . Просмотреть её содержимое можно с помощью команды echo $? :
$ ./hello
Hello World
$ echo $?
0Выше было сказано, что gcc это управляющая программа, предназначенная для автоматизации процесса компиляции. Посмотрим что же на самом деле происходит в результате исполнения команды gcc hello.c .
Процесс компиляции можно разбить на 4 основных этапа: обработка препроцессором, собственно компиляция, ассемблирование, линковка (связывание).
Опции gcc позволяют прервать процесс на любом из этих этапов.
Препроцессор осуществляет подготовку исходного файла к компиляции — вырезает комментарии, добавляет содержимое заголовочных файлов (директива препроцессора #include ), реализует раскрытие макросов (символических констант, директива препроцессора #define ).
Воспользовавшись опцией -E дальнейшие действия gcc можно прервать и просмотреть содержимое файла, обработанного препроцессором.
$ gcc -E -o hello.i hello.c
$ ls
hello.c hello.i
$ less hello.i
. . .
# 1 «/usr/include/stdio.h» 1 3 4
# 28 «/usr/include/stdio.h» 3 4
# 1 «/usr/include/features.h» 1 3 4
. . .
typedef unsigned char __u_char;
typedef unsigned short int __u_short;
typedef unsigned int __u_int;
. . .
extern int printf (__const char *__restrict __format, . );
. . .
# 4 «hello.c» 2main (void)
printf («Hello World\n»);
return 0;
>
После обработки препроцессором исходный текст нашей программы разбух и приобрел не удобочитаемый вид. Код, который мы когда-то собственноручно набили, свелся к нескольким строчкам в самом конце файла. Причина — подключение заголовочного файла стандартной библиотеки C. Заголовочный файл stdio.h сам по себе содержит много всего разного да ещё требует включения других заголовочных файлов.
Обратите внимание на расширение файла hello.i . По соглашениям gcc расширение .i соответствует файлам с исходным кодом на языке C не требующим обработки препроцессором. Такие файлы компилируются минуя препроцессор:
$ gcc -o hello hello.i
$ ls
hello hello.c hello.i
$ ./hello
Hello WorldПосле препроцессинга наступает очередь компиляции. Компилятор преобразует исходный текст программы на языке высокого уровня в код на языке ассемблера.
Значение слова компиляция размыто. Википедисты, например, считают, ссылаясь на международные стандарты, что компиляция это «преобразование программой-компилятором исходного текста какой-либо программы, написанного на языке программирования высокого уровня, в язык, близкий к машинному, или в объектный код.» В принципе это определение нам подходит, язык ассемблера действительно ближе к машинному, чем C. Но в обыденной жизни под компиляцией чаще всего понимают просто любую операцию, преобразующую исходный код программы на каком-либо языке программирования в исполняемый код. То есть процесс, включающий все четыре означенных выше, этапа также может быть назван компиляцией. Подобная неоднозначность присутствует и в настоящем тексте. С другой стороны, операцию преобразования исходного текста программы в код на языке ассемблера можно обозначить и словом трансляция — «преобразование программы, представленной на одном из языков программирования, в программу на другом языке и, в определённом смысле, равносильную первой».
Остановить процесс создания исполняемого файла по завершении компиляции позволяет опция -S :
$ gcc -S hello.c
$ ls
hello.c hello.s
$ file hello.s
hello.s: ASCII assembler program text
$ less hello.s
.file «hello.c»
.section .rodata
.LC0:
.string «Hello World»
.text
.globl main
.type main, @function
main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $16, %esp
movl $.LC0, (%esp)
call puts
movl $0, %eax
leave
ret
.size main, .-main
.ident «GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3»
.section .note.GNU-stack,»»,@progbitsВ каталоге появился файл hello.s , содержащий реализацию программы на языке ассемблера. Обратите внимание, задавать имя выходного файла с помощью опции -o в данном случае не потребовалось, gcc автоматически его сгенерировал, заменив в имени исходного файла расширение .c на .s . Для большинства основных операций gcc имя выходного файла формируется путем подобной замены. Расширение .s стандартное для файлов с исходным кодом на языке ассемблера.
Получить исполняемый код разумеется можно и из файла hello.s :
$ gcc -o hello hello.s
$ ls
hello hello.c hello.s
$ ./hello
Hello WorldСледующий этап операция ассмеблирования — трансляция кода на языке ассемблера в машинный код. Результат операции — объектный файл. Объектный файл содержит блоки готового к исполнению машинного кода, блоки данных, а также список определенных в файле функций и внешних переменных (таблицу символов), но при этом в нем не заданы абсолютные адреса ссылок на функции и данные. Объектный файл не может быть запущен на исполнение непосредственно, но в дальнейшем (на этапе линковки) может быть объединен с другими объектными файлами (при этом, в соответствии с таблицами символов, будут вычислены и заполнены адреса существующих между файлами перекрестных ссылок). Опция gcc -c , останавливает процесс по завершении этапа ассемблирования:
$ gcc -c hello.c
$ ls
hello.c hello.o
$ file hello.o
hello.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not strippedДля объектных файлов принято стандартное расширение .o .
Если полученный объектный файл hello.o передать линковщику, последний вычислит адреса ссылок, добавит код запуска и завершения программы, код вызова библиотечных функций и в результате мы будем обладать готовым исполняемым файлом программы.
$ gcc -o hello hello.o
$ ls
hello hello.c hello.o
$ ./hello
Hello WorldТо, что мы сейчас проделали (вернее gcc проделал за нас) и есть содержание последнего этапа — линковки (связывания, компоновки).
Ну вот пожалуй о компиляции и все. Теперь коснемся некоторых, на мой взгляд важных, опций gcc.
Опция -I путь/к/каталогу/с/заголовочными/файлами — добавляет указанный каталог к списку путей поиска заголовочных файлов. Каталог, добавленный опцией -I просматривается первым, затем поиск продолжается в стандартных системных каталогах. Если опций -I несколько, заданные ими каталоги просматриваются слева на право, по мере появления опций.
Опция -Wall — выводит предупреждения, вызванные потенциальными ошибками в коде, не препятствующими компиляции программы, но способными привести, по мнению компилятора, к тем или иным проблемам при её исполнении. Важная и полезная опция, разработчики gcc рекомендуют пользоваться ей всегда. Например масса предупреждений будет выдана при попытке компиляции вот такого файла:
1 /* remark.c */
2
3 static int k = 0 ;
4 static int l( int a);
5
6 main()
7 <
8
9 int a;
10
11 int b, c;
12
13 b + 1 ;
14
15 b = c;
16
17 int *p;
18
19 b = *p;
20
21 >$ gcc -o remark remark.c
$ gcc -Wall -o remark remark.c
remark.c:7: warning: return type defaults to ‘int’
remark.c: In function ‘main’:
remark.c:13: warning: statement with no effect
remark.c:9: warning: unused variable ‘a’
remark.c:21: warning: control reaches end of non-void function
remark.c: At top level:
remark.c:3: warning: ‘k’ defined but not used
remark.c:4: warning: ‘l’ declared ‘static’ but never defined
remark.c: In function ‘main’:
remark.c:15: warning: ‘c’ is used uninitialized in this function
remark.c:19: warning: ‘p’ is used uninitialized in this functionОпция -Werror — превращает все предупреждения в ошибки. В случае появления предупреждения прерывает процесс компиляции. Используется совместно с опцией -Wall .
$ gcc -Werror -o remark remark.c
$ gcc -Werror -Wall -o remark remark.c
cc1: warnings being treated as errors
remark.c:7: error: return type defaults to ‘int’
remark.c: In function ‘main’:
remark.c:13: error: statement with no effect
remark.c:9: error: unused variable ‘a’Опция -g — помещает в объектный или исполняемый файл информацию необходимую для работы отладчика gdb. При сборке какого-либо проекта с целью последующей отладки, опцию -g необходимо включать как на этапе компиляции так и на этапе компоновки.
Опции -O1 , -O2 , -O3 — задают уровень оптимизации кода генерируемого компилятором. С увеличением номера, степень оптимизации возрастает. Действие опций можно увидеть вот на таком примере.
Компиляция с уровнем оптимизации по умолчанию:
$ gcc -S circle.c
$ less circle.s
.file «circle.c»
.text
.globl main
.type main, @function
main:
pushl %ebp
movl %esp, %ebp
subl $16, %esp
movl $0, -4(%ebp)
jmp .L2
.L3:
addl $1, -4(%ebp)
.L2:
cmpl $9, -4(%ebp)
jle .L3
movl -4(%ebp), %eax
leave
ret
.size main, .-main
.ident «GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3»
.section .note.GNU-stack,»»,@progbitsКомпиляция с максимальным уровнем оптимизации:
$ gcc -S -O3 circle.c
$ less circle.s
.file «circle.c»
.text
.p2align 4,,15
.globl main
.type main, @function
main:
pushl %ebp
movl $10, %eax
movl %esp, %ebp
popl %ebp
ret
.size main, .-main
.ident «GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3»
.section .note.GNU-stack,»»,@progbitsВо втором случае в полученном коде даже нет намёка на какой-либо цикл. Действительно, значение i , можно вычислить ещё на этапе компиляции, что и было сделано.
Увы, для реальных проектов разница в производительности при различных уровнях оптимизации практически не заметна.
Опция -O0 — отменяет какую-либо оптимизацию кода. Опция необходима на этапе отладки приложения. Как было показано выше, оптимизация может привести к изменению структуры программы до неузнаваемости, связь между исполняемым и исходным кодом не будет явной, соответственно, пошаговая отладка программы будет не возможна. При включении опции -g , рекомендуется включать и -O0 .
Опция -Os — задает оптимизацию не по эффективности кода, а по размеру получаемого файла. Производительность программы при этом должна быть сопоставима с производительностью кода полученного при компиляции с уровнем оптимизации заданным по умолчанию.
Опция -march= architecture — задает целевую архитектуру процессора. Список поддерживаемых архитектур обширен, например, для процессоров семейства Intel/AMD можно задать i386 , pentium , prescott , opteron-sse3 и т.д. Пользователи бинарных дистрибутивов должны иметь в виду, что для корректной работы программ с указанной опцией желательно, что бы и все подключаемые библиотеки были откомпилированы с той же опцией.
Об опциях передаваемых линковщику будет сказано ниже.
Собственно о компиляции все. Далее поговорим о раздельной компиляции и создании библиотек.
Выше было сказано, что gcc определяет тип (язык программирования) переданных файлов по их расширению и, в соответствии с угаданным типом (языком), производит действия над ними. Пользователь обязан следить за расширениями создаваемых файлов, выбирая их так, как того требуют соглашения gcc. В действительности gcc можно подсовывать файлы с произвольными именами. Опция gcc -x позволяет явно указать язык программирования компилируемых файлов. Действие опции распространяется на все последующие перечисленные в команде файлы (вплоть до появления следующей опции -x ). Возможные аргументы опции:
c c-header c-cpp-output
c++ c++-header c++-cpp-output
objective-c objective-c-header objective-c-cpp-output
objective-c++ objective-c++-header objective-c++-cpp-output
assembler assembler-with-cpp
f77 f77-cpp-input
f95 f95-cpp-input
Назначение аргументов должно быть понятно из их написания (здесь cpp не имеет ни какого отношения к C++, это файл исходного кода предварительно обработанный препроцессором). Проверим:
$ mv hello.c hello.txt
$ gcc -Wall -x c -o hello hello.txt
$ ./hello
Hello WorldРаздельная компиляция
Сильной стороной языков C/C++ является возможность разделять исходный код программы по нескольким файлам. Даже можно сказать больше — возможность раздельной компиляции это основа языка, без неё эффективное использование C не мыслимо. Именно мультифайловое программирование позволяет реализовать на C крупные проекты, например такие как Linux (здесь под словом Linux подразумевается как ядро, так и система в целом). Что даёт раздельная компиляция программисту?
1. Позволяет сделать код программы (проекта) более удобочитаемым. Файл исходника на несколько десятков экранов становиться практически неохватным. Если, в соответствии с некой (заранее продуманной) логикой, разбить его на ряд небольших фрагментов (каждый в отдельном файле), совладать со сложностью проекта будет гораздо проще.
2. Позволяет сократить время повторной компиляции проекта. Если изменения внесены в один файл нет смысла перекомпилировать весь проект, достаточно заново откомпилировать только этот изменённый файл.
3. Позволяет распределить работу над проектом между несколькими разработчиками. Каждый программист творит и отлаживает свою часть проекта, но в любой момент можно будет собрать (пересобрать) все получающиеся наработки в конечный продукт.
4. Без раздельной компиляции не существовало бы библиотек. Посредством библиотек реализовано повторное использование и распространение кода на C/C++, причем кода бинарного, что позволяет с одной стороны предоставить разработчикам простой механизм включения его в свои программы, с другой стороны скрыть от них конкретные детали реализации. Работая над проектом, всегда стоит задумываться над тем, а не понадобиться что-либо из уже сделанного когда-нибудь в будущем? Может стоит заранее выделить и оформить часть кода как библиотеку? По моему, такой подход, существенно упрощает жизнь и экономит массу времени.
GCC, разумеется, поддерживает раздельную компиляцию, причем не требует от пользователя каких либо специальных указаний. В общем все очень просто.
Вот практический пример (правда весьма и весьма условный).
Набор файлов исходного кода:
#include «first.h»
#include «second.h»int main( void )
printf( «Main function. \n » );
void first( void );
void first( void )
printf( «First function. \n » );
void second( void );
void second( void )
printf( «Second function. \n » );
В общем имеем вот что:
$ ls
first.c first.h main.c second.c second.hВсе это хозяйство можно скомпилировать в одну команду:
$ gcc -Wall -o main main.c first.c second.c
$ ./main
First function.
Second function.
Main function.Только это не даст нам практически ни каких бонусов, ну за исключением более структурированного и удобочитаемого кода, разнесённого по нескольким файлам. Все перечисленные выше преимущества появятся в случае такого подхода к компиляции:
$ gcc -Wall -c main.c
$ gcc -Wall -c first.c
$ gcc -Wall -c second.c
$ ls
first.c first.h first.o main.c main.o second.c second.h second.o
$ gcc -o main main.o first.o second.o
$ ./main
First function.
Second function.
Main function.Что мы сделали? Из каждого исходного файла (компилируя с опцией -c ) получили объектный файл. Затем объектные файлы слинковали в итоговый исполняемый. Разумеется команд gcc стало больше, но в ручную ни кто проекты не собирает, для этого есть утилиты сборщики (самая популярная make). При использовании утилит сборщиков и проявятся все из перечисленных выше преимуществ раздельной компиляции.
Возникает вопрос: как линковщик ухитряется собирать вместе объектные файлы, правильно вычисляя адресацию вызовов? Откуда он вообще узнаёт, что в файле second.o содержится код функции second() , а в коде файла main.o присутствует её вызов? Оказывается всё просто — в объектном файле присутствует так называемая таблица символов, включающая имена некоторых позиций кода (функций и внешних переменных). Линковщик просматривает таблицу символов каждого объектного файла, ищет общие (с совпадающими именами) позиции, на основании чего делает выводы о фактическом местоположении кода используемых функций (или блоков данных) и, соответственно, производит перерасчёт адресов вызовов в исполняемом файле.
Просмотреть таблицу символов можно с помощью утилиты nm.
$ nm main.o
U first
00000000 T main
U puts
U second
$ nm first.o
00000000 T first
U puts
$ nm second.o
U puts
00000000 T secondПоявление вызова puts объясняется использованием функции стандартной библиотеки printf() , превратившейся в puts() на этапе компиляции.
Таблица символов прописывается не только в объектный, но и в исполняемый файл:
$ nm main
08049f20 d _DYNAMIC
08049ff4 d _GLOBAL_OFFSET_TABLE_
080484fc R _IO_stdin_used
w _Jv_RegisterClasses
08049f10 d __CTOR_END__
08049f0c d __CTOR_LIST__
08049f18 D __DTOR_END__
08049f14 d __DTOR_LIST__
08048538 r __FRAME_END__
08049f1c d __JCR_END__
08049f1c d __JCR_LIST__
0804a014 A __bss_start
0804a00c D __data_start
080484b0 t __do_global_ctors_aux
08048360 t __do_global_dtors_aux
0804a010 D __dso_handle
w __gmon_start__
080484aa T __i686.get_pc_thunk.bx
08049f0c d __init_array_end
08049f0c d __init_array_start
08048440 T __libc_csu_fini
08048450 T __libc_csu_init
U __libc_start_main@@GLIBC_2.0
0804a014 A _edata
0804a01c A _end
080484dc T _fini
080484f8 R _fp_hw
080482b8 T _init
08048330 T _start
0804a014 b completed.7021
0804a00c W data_start
0804a018 b dtor_idx.7023
0804840c T first
080483c0 t frame_dummy
080483e4 T main
U puts@@GLIBC_2.0
08048420 T secondВключение таблицы символов в исполняемый файл в частности необходимо для упрощения отладки. В принципе для выполнения приложения она не очень то и нужна. Для исполняемых файлов реальных программ, с множеством определений функций и внешних переменных, задействующих кучу разных библиотек, таблица символов становиться весьма обширной. Для сокращения размеров выходного файла её можно удалить, воспользовавшись опцией gcc -s .
$ gcc -s -o main main.o first.o second.o
$ ./main
First function.
Second function.
Main function.
$ nm main
nm: main: no symbolsНеобходимо отметить, что в ходе компоновки, линковщик не делает ни каких проверок контекста вызова функций, он не следит ни за типом возвращаемого значения, ни за типом и количеством принимаемых параметров (да ему и не откуда взять такую информацию). Все проверки корректности вызовов должны быть сделаны на этапе компиляции. В случае мультифайлового программирования для этого необходимо использовать механизм заголовочных файлов языка C.
Библиотеки
Библиотека — в языке C, файл содержащий объектный код, который может быть присоединен к использующей библиотеку программе на этапе линковки. Фактически библиотека это набор особым образом скомпонованных объектных файлов.
Назначение библиотек — предоставить программисту стандартный механизм повторного использования кода, причем механизм простой и надёжный.
С точки зрения операционной системы и прикладного программного обеспечения библиотеки бывают статическими и разделяемыми (динамическими).
Код статических библиотек включается в состав исполняемого файла в ходе линковки последнего. Библиотека оказывается «зашитой» в файл, код библиотеки «сливается» с остальным кодом файла. Программа использующая статические библиотеки становиться автономной и может быть запущена практически на любом компьютере с подходящей архитектурой и операционной системой.
Код разделяемой библиотеки загружается и подключается к коду программы операционной системой, по запросу программы в ходе её исполнения. В состав исполняемого файла программы код динамической библиотеки не входит, в исполняемый файл включается только ссылка на библиотеку. В результате, программа использующая разделяемые библиотеки перестает быть автономной и может быть успешно запущена только в системе где задействованные библиотеки установлены.
Парадигма разделяемых библиотек предоставляет три существенных преимущества:
1. Многократно сокращается размер исполняемого файла. В системе, включающей множество бинарных файлов, использующих один и тот же код, отпадает необходимость хранить копию этого кода для каждого исполняемого файла.
2. Код разделяемой библиотеки используемый несколькими приложениями храниться в оперативной памяти в одном экземпляре (на самом деле не всё так просто. ), в результате сокращается потребность системы в доступной оперативной памяти.
3. Отпадает необходимость пересобирать каждый исполняемый файл в случае внесения изменений в код общей для них библиотеки. Изменения и исправления кода динамической библиотеки автоматически отразятся на каждой из использующих её программ.
Без парадигмы разделяемых библиотек не существовало бы прекомпиллированных (бинарных) дистрибутивов Linux (да ни каких бы не существовало). Представьте размеры дистрибутива, в каждый бинарный файл которого, был бы помещен код стандартной библиотеки C (и всех других подключаемых библиотек). Так же представьте что пришлось бы делать для того, что бы обновить систему, после устранения критической уязвимости в одной из широко задействованных библиотек.
Теперь немного практики.
Для иллюстрации воспользуемся набором исходных файлов из предыдущего примера. В нашу самодельную библиотеку поместим код (реализацию) функций first() и second() .
В Linux принята следующая схема именования файлов библиотек (хотя соблюдается она не всегда) — имя файла библиотеки начинается с префикса lib , за ним следует собственно имя библиотеки, в конце расширение .a (archive) — для статической библиотеки, .so (shared object) — для разделяемой (динамической), после расширения через точку перечисляются цифры номера версии (только для динамической библиотеки). Имя, соответствующего библиотеке заголовочного файла (опять же как правило), состоит из имени библиотеки (без префикса и версии) и расширения .h . Например: libogg.a , libogg.so.0.7.0 , ogg.h .
В начале создадим и используем статическую библиотеку.
Функции first() и second() составят содержимое нашей библиотеки libhello . Имя файла библиотеки, соответственно, будет libhello.a . Библиотеке сопоставим заголовочный файл hello.h .
void first( void );
void second( void );#include «first.h»
#include «second.h»в файлах main.c , first.c и second.c необходимо заменить на:
#include «hello.h»
Ну а теперь, введем следующую последовательность команд:
$ gcc -Wall -c first.c
$ gcc -Wall -c second.c
$ ar crs libhello.a first.o second.o
$ file libhello.a
libhello.a: current ar archiveКак уже было сказано — библиотека это набор объектных файлов. Первыми двумя командами мы и создали эти объектные файлы.
Далее необходимо объектные файлы скомпоновать в набор. Для этого используется архиватор ar — утилита «склеивает» несколько файлов в один, в полученный архив включает информацию требуемую для восстановления (извлечения) каждого индивидуального файла (включая его атрибуты принадлежности, доступа, времени). Какого-либо «сжатия» содержимого архива или иного преобразования хранимых данных при этом не производится.
Опция c arname — создать архив, если архив с именем arname не существует он будет создан, в противном случае файлы будут добавлены к имеющемуся архиву.
Опция r — задает режим обновления архива, если в архиве файл с указанным именем уже существует, он будет удален, а новый файл дописан в конец архива.
Опция s — добавляет (обновляет) индекс архива. В данном случае индекс архива это таблица, в которой для каждого определенного в архивируемых файлах символического имени (имени функции или блока данных) сопоставлено соответствующее ему имя объектного файла. Индекс архива необходим для ускорения работы с библиотекой — для того чтобы найти нужное определение, отпадает необходимость просматривать таблицы символов всех файлов архива, можно сразу перейти к файлу, содержащему искомое имя. Просмотреть индекс архива можно с помощью уже знакомой утилиты nm воспользовавшись её опцией -s (так же будут показаны таблицы символов всех объектных файлов архива):
$ nm -s libhello.a
Archive index:
first in first.o
second in second.ofirst.o:
00000000 T first
U putssecond.o:
U puts
00000000 T secondДля создания индекса архива существует специальная утилита ranlib. Библиотеку libhello.a можно было сотворить и так:
$ ar cr libhello.a first.o second.o
$ ranlib libhello.aВпрочем библиотека будет прекрасно работать и без индекса архива.
Теперь воспользуемся нашей библиотекой:
$ gcc -Wall -c main.c
$ gcc -o main main.o -L. -lhello
$ ./main
First function.
Second function.
Main function.Ну теперь комментарии. Появились две новые опции gcc:
Опция -l name — передаётся линковщику, указывает на необходимость подключить к исполняемому файлу библиотеку libname . Подключить значит указать, что такие-то и такие-то функции (внешние переменные) определены в такой-то библиотеке. В нашем примере библиотека статическая, все символьные имена будут ссылаться на код находящийся непосредственно в исполняемом файле. Обратите внимание в опции -l имя библиотеки задается как name без приставки lib .
Опция -L /путь/к/каталогу/с/библиотеками — передаётся линковщику, указывает путь к каталогу содержащему подключаемые библиотеки. В нашем случае задана точка . , линковщик сначала будет искать библиотеки в текущем каталоге, затем в каталогах определённых в системе.
Здесь необходимо сделать небольшое замечание. Дело в том, что для ряда опций gcc важен порядок их следования в командной строке. Так линковщик ищет код, соответствующий указанным в таблице символов файла именам в библиотеках, перечисленных в командной строке после имени этого файла. Содержимое библиотек перечисленных до имени файла линковщик игнорирует:
$ gcc -Wall -c main.c
$ gcc -o main -L. -lhello main.o
main.o: In function `main’:
main.c:(.text+0xa): undefined reference to `first’
main.c:(.text+0xf): undefined reference to `second’
collect2: ld returned 1 exit status
$ gcc -o main main.o -L. -lhello
$ ./main
First function.
Second function.
Main function.Такая особенность поведения gcc обусловлена желанием разработчиков предоставить пользователю возможность по разному комбинировать файлы с библиотеками, использовать пересекающие имена. На мой взгляд, если возможно, лучше этим не заморачиваться. В общем подключаемые библиотеки необходимо перечислять после имени ссылающегося на них файла.
Существует альтернативный способ указания местоположения библиотек в системе. В зависимости от дистрибутива, переменная окружения LD_LIBRARY_PATH или LIBRARY_PATH может хранить список разделенных знаком двоеточия каталогов, в которых линковщик должен искать библиотеки. Как правило, по умолчанию эта переменная вообще не определена, но ни чего не мешает её создать:
$ echo $LD_LIBRARY_PATH
$ gcc -o main main.o -lhello
/usr/lib/gcc/i686-pc-linux-gnu/4.4.3/../../../../i686-pc-linux-gnu/bin/ld: cannot find -lhello
collect2: выполнение ld завершилось с кодом возврата 1
$ export LIBRARY_PATH=.
$ gcc -o main main.o -lhello
$ ./main
First function.
Second function.
Main function.Манипуляции с переменными окружения полезны при создании и отладке собственных библиотек, а так же если возникает необходимость подключить к приложению какую-нибудь нестандартную (устаревшую, обновленную, изменённую — в общем отличную от включенной в дистрибутив) разделяемую библиотеку.
Теперь создадим и используем библиотеку динамическую.
Набор исходных файлов остается без изменения. Вводим команды, смотрим что получилось, читаем комментарии:
$ gcc -Wall -fPIC -c first.c
$ gcc -Wall -fPIC -c second.c
$ gcc -shared -o libhello.so.2.4.0.5 -Wl,-soname,libhello.so.2 first.o second.oЧто получили в результате?
$ file libhello.so.2.4.0.5
libhello.so.2.4.0.5: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, not strippedФайл libhello.so.2.4.0.5 , это и есть наша разделяемая библиотека. Как её использовать поговорим чуть ниже.
Опция -fPIC — требует от компилятора, при создании объектных файлов, порождать позиционно-независимый код (PIC — Position Independent Code), его основное отличие в способе представления адресов. Вместо указания фиксированных (статических) позиций, все адреса вычисляются исходя из смещений заданных в глобальной таблицы смещений (global offset table — GOT). Формат позиционно-независимого кода позволяет подключать исполняемые модули к коду основной программы в момент её загрузки. Соответственно, основное назначение позиционно-независимого кода — создание динамических (разделяемых) библиотек.
Опция -shared — указывает gcc, что в результате должен быть собран не исполняемый файл, а разделяемый объект — динамическая библиотека.
Опция -Wl,-soname,libhello.so.2 — задает soname библиотеки. О soname подробно поговорим в следующем абзаце. Сейчас обсудим формат опции. Сея странная, на первый взгляд, конструкция с запятыми предназначена для непосредственного взаимодействия пользователя с линковщиком. По ходу компиляции gcc вызывает линковщик автоматически, автоматически же, по собственному усмотрению, gcc передает ему необходимые для успешного завершения задания опции. Если у пользователя возникает потребность самому вмешаться в процесс линковки он может воспользоваться специальной опцией gcc -Wl, -option , value1 , value2 . . Что означает передать линковщику ( -Wl ) опцию -option с аргументами value1 , value2 и так далее. В нашем случае линковщику была передана опция -soname с аргументом libhello.so.2 .
Теперь о soname. При создании и распространении библиотек встает проблема совместимости и контроля версий. Для того чтобы система, конкретно загрузчик динамических библиотек, имели представление о том библиотека какой версии была использована при компиляции приложения и, соответственно, необходима для его успешного функционирования, был предусмотрен специальный идентификатор — soname, помещаемый как в файл самой библиотеки, так и в исполняемый файл приложения. Идентификатор soname это строка, включающая имя библиотеки с префиксом lib , точку, расширение so , снова точку и оду или две (разделенные точкой) цифры версии библиотеки — lib name .so. x . y . То есть soname совпадает с именем файла библиотеки вплоть до первой или второй цифры номера версии. Пусть имя исполняемого файла нашей библиотеки libhello.so.2.4.0.5 , тогда soname библиотеки может быть libhello.so.2 . При изменении интерфейса библиотеки её soname необходимо изменять! Любая модификация кода, приводящая к несовместимости с предыдущими релизами должна сопровождаться появлением нового soname.
Как же это все работает? Пусть для успешного исполнения некоторого приложения необходима библиотека с именем hello , пусть в системе таковая имеется, при этом имя файла библиотеки libhello.so.2.4.0.5 , а прописанное в нем soname библиотеки libhello.so.2 . На этапе компиляции приложения, линковщик, в соответствии с опцией -l hello , будет искать в системе файл с именем libhello.so . В реальной системе libhello.so это символическая ссылка на файл libhello.so.2.4.0.5 . Получив доступ к файлу библиотеки, линковщик считает прописанное в нем значение soname и наряду с прочим поместит его в исполняемый файл приложения. Когда приложение будет запущено, загрузчик динамических библиотек получит запрос на подключение библиотеки с soname, считанным из исполняемого файла, и попытается найти в системе библиотеку, имя файла которой совпадает с soname. То есть загрузчик попытается отыскать файл libhello.so.2 . Если система настроена корректно, в ней должна присутствовать символическая ссылка libhello.so.2 на файл libhello.so.2.4.0.5 , загрузчик получит доступ к требуемой библиотеки и далее не задумываясь (и ни чего более не проверяя) подключит её к приложению. Теперь представим, что мы перенесли откомпилированное таким образом приложение в другую систему, где развернута только предыдущая версия библиотеки с soname libhello.so.1 . Попытка запустить программу приведет к ошибке, так как в этой системе файла с именем libhello.so.2 нет.
Таким образом, на этапе компиляции линковщику необходимо предоставить файл библиотеки (или символическую ссылку на файл библиотеки) с именем lib name .so , на этапе исполнения загрузчику потребуется файл (или символическая ссылка) с именем lib name .so. x . y . При чем имя lib name .so. x . y должно совпадать со строкой soname использованной библиотеки.
В бинарных дистрибутивах, как правило, файл библиотеки libhello.so.2.4.0.5 и ссылка на него libhello.so.2 будут помещены в пакет libhello , а необходимая только для компиляции ссылка libhello.so , вместе с заголовочным файлом библиотеки hello.h будет упакована в пакет libhello-devel (в devel пакете окажется и файл статической версии библиотеки libhello.a , статическая библиотека может быть использована, также только на этапе компиляции). При распаковке пакета все перечисленные файлы и ссылки (кроме hello.h ) окажутся в одном каталоге.
Убедимся, что заданная строка soname действительно прописана в файле нашей библиотеки. Воспользуемся мега утилитой objdump с опцией -p :
$ objdump -p libhello.so.2.4.0.5 | grep SONAME
SONAME libhello.so.2Утилита objdump — мощный инструмент, позволяющий получить исчерпывающую информацию о внутреннем содержании (и устройстве) объектного или исполняемого файла. В man странице утилиты сказано, что objdump прежде всего будет полезен программистам, создающими средства отладки и компиляции, а не просто пишущих какие-нибудь прикладные программы 🙂 В частности с опцией -d это дизассемблер. Мы воспользовались опцией -p — вывести различную метаинформацию о объектном файле.
В приведенном примере создания библиотеки мы неотступно следовали принципам раздельной компиляции. Разумеется скомпилировать библиотеку можно было бы и вот так, одним вызовом gcc:
$ gcc -shared -Wall -fPIC -o libhello.so.2.4.0.5 -Wl,-soname,libhello.so.2 first.c second.c
Теперь попытаемся воспользоваться получившейся библиотекой:
$ gcc -Wall -c main.c
$ gcc -o main main.o -L. -lhello -Wl,-rpath,.
/usr/bin/ld: cannot find -lhello
collect2: ld returned 1 exit statusЛинковщик ругается. Вспоминаем, что было сказано выше о символических ссылках. Создаем libhello.so и повторяем попытку:
$ ln -s libhello.so.2.4.0.5 libhello.so
$ gcc -o main main.o -L. -lhello -Wl,-rpath,.Теперь все довольны. Запускаем созданный бинарник:
$ ./main
./main: error while loading shared libraries: libhello.so.2: cannot open shared object file: No such file or directoryОшибка. Ругается загрузчик, не может найти библиотеку libhello.so.2 . Убедимся, что в исполняемом файле действительно прописана ссылка на libhello.so.2 :
$ objdump -p main | grep NEEDED
NEEDED libhello.so.2
NEEDED libc.so.6Создаем соответствующую ссылку и повторно запускаем приложение:
$ ln -s libhello.so.2.4.0.5 libhello.so.2
$ ./main
First function.
Second function.
Main function.Заработало. Теперь комментарии по новым опциям gcc.
Опция -Wl,-rpath,. — уже знакомая конструкция, передать линковщику опцию -rpath с аргументом . . С помощью -rpath в исполняемый файл программы можно прописать дополнительные пути по которым загрузчик разделяемых библиотек будет производить поиск библиотечных файлов. В нашем случае прописан путь . — поиск файлов библиотек будет начинаться с текущего каталога.
$ objdump -p main | grep RPATH
RPATH .Благодаря указанной опции, при запуске программы отпала необходимость изменять переменные окружения. Понятно, что если перенести программу в другой каталог и попытаться запустить, файл библиотеки будет не найден и загрузчик выдаст сообщение об ошибке:
$ mv main ..
$ ../main
First function.
Second function.
Main function.
$ cd ..
$ ./main
./main: error while loading shared libraries: libhello.so.2: cannot open shared object file: No such file or directoryУзнать какие разделяемые библиотеки необходимы приложению можно и с помощью утилиты ldd:
$ ldd main
linux-vdso.so.1 => (0x00007fffaddff000)
libhello.so.2 => ./libhello.so.2 (0x00007f9689001000)
libc.so.6 => /lib/libc.so.6 (0x00007f9688c62000)
/lib64/ld-linux-x86-64.so.2 (0x00007f9689205000)В выводе ldd для каждой требуемой библиотеки указывается её soname и полный путь к файлу библиотеки, определённый в соответствии с настройками системы.
Сейчас самое время поговорить о том где в системе положено размещать файлы библиотек, где загрузчик пытается их найти и как этим процессом управлять.
В соответствии с соглашениями FHS (Filesystem Hierarchy Standard) в системе должны быть два (как минимум) каталога для хранения файлов библиотек:
/lib — здесь собраны основные библиотеки дистрибутива, необходимые для работы программ из /bin и /sbin ;
/usr/lib — здесь хранятся библиотеки необходимые прикладным программам из /usr/bin и /usr/sbin ;
Соответствующие библиотекам заголовочные файлы должны находиться в каталоге /usr/include .
Загрузчик по умолчанию будет искать файлы библиотек в этих каталогах.
Кроме перечисленных выше, в системе должен присутствовать каталог /usr/local/lib — здесь должны находиться библиотеки, развернутые пользователем самостоятельно, минуя систему управления пакетами (не входящие в состав дистрибутива). Например в этом каталоге по умолчанию окажутся библиотеки скомпилированные из исходников (программы установленные из исходников будут размещены в /usr/local/bin и /usr/local/sbin , разумеется речь идет о бинарных дистрибутивах). Заголовочные файлы библиотек в этом случае будут помещены в /usr/local/include .
В ряде дистрибутивов (в Ubuntu) загрузчик не настроен просматривать каталог /usr/local/lib , соответственно, если пользователь установит библиотеку из исходников, система её не увидит. Сиё авторами дистрибутива сделано специально, что бы приучить пользователя устанавливать программное обеспечение только через систему управления пакетами. Как поступить в данном случае будет рассказано ниже.
В действительности, для упрощения и ускорения процесса поиска файлов библиотек, загрузчик не просматривает при каждом обращении указанные выше каталоги, а пользуется базой данных, хранящейся в файле /etc/ld.so.cache (кэшем библиотек). Здесь собрана информация о том, где в системе находится соответствующий данному soname файл библиотеки. Загрузчик, получив список необходимых конкретному приложению библиотек (список soname библиотек, заданных в исполняемом файле программы), посредством /etc/ld.so.cache определяет путь к файлу каждой требуемой библиотеки и загружает её в память. Дополнительно, загрузчик может просмотреть каталоги перечисленные в системных переменных LD_LIBRARY_PATH , LIBRARY_PATH и в поле RPATH исполняемого файла (смотри выше).
Для управления и поддержания в актуальном состоянии кэша библиотек используется утилита ldconfig. Если запустить ldconfig без каких-либо опций, программа просмотрит каталоги заданные в командной строке, доверенные каталоги /lib и /usr/lib , каталоги перечисленные в файле /etc/ld.so.conf . Для каждого файла библиотеки, оказавшегося в указанных каталогах, будет считано soname, создана основанная на soname символическая ссылка, обновлена информация в /etc/ld.so.cache .
Убедимся в сказанном:
$ ls
hello.h libhello.so libhello.so.2.4.0.5 main.c
$ gcc -Wall -o main main.c -L. -lhello
$ ./main
./main: error while loading shared libraries: libhello.so.2: cannot open shared object file: No such file or directory
$ sudo ldconfig /полный/путь/к/катаогу/c/примером
$ ls
hello.h libhello.so libhello.so.2 libhello.so.2.4.0.5 main main.c
$ ./main
First function.
Second function.
Main function.
$ sudo ldconfig
$ ./main
./main: error while loading shared libraries: libhello.so.2: cannot open shared object file: No such file or directoryПервым вызовом ldconfig мы внесли в кэш нашу библиотеку, вторым вызовом исключили. Обратите внимание, что при компиляции main была опущена опция -Wl,-rpath,. , в результате загрузчик проводил поиск требуемых библиотек только в кэше.
Теперь должно быть понятно как поступить если после установки библиотеки из исходников система её не видит. Прежде всего необходимо внести в файл /etc/ld.so.conf полный путь к каталогу с файлами библиотеки (по умолчанию /usr/local/lib ). Формат /etc/ld.so.conf — файл содержит список разделённых двоеточием, пробелом, табуляцией или символом новой строки, каталогов, в которых производится поиск библиотек. После чего вызвать ldconfig без каких-либо опций, но с правами суперпользователя. Всё должно заработать.
Ну и в конце поговорим о том как уживаются вместе статические и динамические версии библиотек. В чем собственно вопрос? Выше, когда обсуждались принятые имена и расположение файлов библиотек было сказано, что файлы статической и динамической версий библиотеки хранятся в одном и том же каталоге. Как же gcc узнает какой тип библиотеки мы хотим использовать? По умолчанию предпочтение отдается динамической библиотеки. Если линковщик находит файл динамической библиотеки, он не задумываясь цепляет его к исполняемому файлу программы:
$ ls
hello.h libhello.a libhello.so libhello.so.2 libhello.so.2.4.0.5 main.c
$ gcc -Wall -c main.c
$ gcc -o main main.o -L. -lhello -Wl,-rpath,.
$ ldd main
linux-vdso.so.1 => (0x00007fffe1bb0000)
libhello.so.2 => ./libhello.so.2 (0x00007fd50370b000)
libc.so.6 => /lib/libc.so.6 (0x00007fd50336c000)
/lib64/ld-linux-x86-64.so.2 (0x00007fd50390f000)
$ du -h main
12K mainОбратите внимание на размер исполняемого файла программы. Он минимально возможный. Все используемые библиотеки линкуются динамически.
Существует опция gcc -static — указание линковщику использовать только статические версии всех необходимых приложению библиотек:
$ gcc -static -o main main.o -L. -lhello
$ file main
main: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.15, not stripped
$ ldd main
не является динамическим исполняемым файлом
$ du -h main
728K mainРазмер исполняемого файла в 60 раз больше, чем в предыдущем примере — в файл включены стандартные библиотеки языка C. Теперь наше приложение можно смело переносить из каталога в каталог и даже на другие машины, код библиотеки hello внутри файла, программа полностью автономна.
Как же быть если необходимо осуществить статическую линковку только части использованных библиотек? Возможный вариант решения — сделать имя статической версии библиотеки отличным от имени разделяемой, а при компиляции приложения указывать какую версию мы хотим использовать на этот раз:
$ mv libhello.a libhello_s.a
$ gcc -o main main.o -L. -lhello_s
$ ldd main
linux-vdso.so.1 => (0x00007fff021f5000)
libc.so.6 => /lib/libc.so.6 (0x00007fd0d0803000)
/lib64/ld-linux-x86-64.so.2 (0x00007fd0d0ba4000)
$ du -h main
12K mainТак как размер кода библиотеки libhello ничтожен,
$ du -h libhello_s.a
4,0K libhello.aразмер получившегося исполняемого файла практически не отличается от размера файла созданного с использованием динамической линковки.
Ну вот пожалуй и все. Большое спасибо всем, кто закончил чтение на этом месте.
GNU C
GNU C — это реализация языка программирования C, предоставляемая проектом GNU. Основной компонент этой реализации — компилятор GCC (GNU Compiler Collection), который изначально разрабатывался как компилятор для языка C, но позже был расширен для поддержки многих других языков программирования.
GNU C и GCC являются свободными и открытыми, что означает, что любой разработчик может использовать, модифицировать и распространять их в соответствии с лицензией GNU General Public License (GPL).
Кроме того, GNU C библиотека (glibc) — это библиотека C для системы GNU, которая предоставляет стандартные функции и интерфейсы, такие как системные вызовы, необходимые для выполнения программ. Она служит ключевым компонентом большинства систем на базе ядра Linux.