Что такое gcc компилятор

GNU Compiler Collection, первые шаги

Эта заметка призвана на простых примерах познакомить начинающего nix-разработчика с инструментами GNU, в частности с компилятором GCC.

С его помощью мы и создадим простейшую программу. По большому счету все, как обычно. Заводим специальную папку, в которой будет размещаться проект.
Создаем в ней файл с именем: hello.c
Открываем файл в любом текстовом редакторе и пишем простейший код:

#include
int main(void)
<
printf(«Hello world!»);
return(0);
>

Сохраняем файл и выполняем команду: gcc hello.c

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

И радуемся в связи с первой написанной программой в линуксе!

Идем далее. При запуске исполняемого файла, если мы укажем только его название, система будет искать его в каталогах /usr/bin и /usr/local/bin, и, естественно, не найдет. Первый из них предназначен для размещения стабильных версий программ, как правило, входящих в дистрибутив Linux. Второй – для программ, устанавливаемых самим пользователем (за стабильность которых никто не ручается). По умолчанию, при сборке программы, устанавливаются в каталог /usr/local/bin.

Флаги используемые при компиляции

Название получаемого файла такое же, но компилятор изменяет расширение .c на .o (но указать можно и вручную).

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

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

Источник

Использование GCC

Введение

Если имя ‘a.out’ кажется вам не очень выразительным, то вы наверняка захотите указать другое, делается это так:

Языковые стандарты

Язык C

GCC поддерживает три версии стандарта C, хотя последняя версия поддерживается лишь частично.

Язык C++

Предупреждения компилятора

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

Предупреждения компилятора это очень полезный механизм, позволивший программистам избежать многих незаметных на первый взгляд ошибок, которые могли проявиться только после запуска уже скомпилированной программы (возможно, много лет спустя после первого запуска). Его, безусловно, нужно использовать, и относиться к предупреждениям следует максимально внимательно. Многие авторитетные книги по C++, написанные в жанре сборников советов (Скотта Мейерса, Герба Саттера, Андрея Александреску, Стивена Дьюхерста), содержат совет компилировать программу на максимально строгом уровне предупреждений и добиваться того, чтобы компилятору не к чему было придраться.

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

Отладочная информация

Сложно представить себе программу, которая выполняет что-то осмысленное и которая была написана с первого раза без единой ошибки. Хуже того, иногда может оказаться, что давно и успешно работающая программа также содержит изъяны. Установить источник проблем помогает отладочная информация, которую компилятор может добавить в каждый объектный файл. Она включает в себя указание на типы данных, использованные в программе, и соответствие объектного кода строкам текста исходного файла. Отладочная информация, сгенерированная GCC, предполагается для использования в первую очередь отладчиком GDB (GNU debugger, отладчик GNU), но не только им.

Оптимизация

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

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

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

Раздельная трансляция

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

Библиотеки

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

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

Статические библиотеки

Динамические библиотеки

Источник

Национальная библиотека им. Н. Э. Баумана
Bauman National Library

Персональные инструменты

GNU Compiler Collection

Содержание

Обзор

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

Будучи официальным компилятором системы GNU, GCC также является главным компилятором для сборки ряда других операционных систем; среди них — различные варианты Linux и Berkeley Software Distribution|BSD, а также ReactOS, Mac OS X, OpenSolaris, NeXTSTEP, BeOS и Haiku.

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

Языки

В версии 4.1.1 (выпущенной 24 мая 2006 года стандартный компилятор включал в себя front-end’ы для языков:

Front end для CHILL был добавлен ранее, но из-за недостаточной поддержки был исключён из набора. До выхода версии 4.0 front-end’ом для Fortran был G77, который поддерживал лишь FORTRAN 77. В новых версиях G77 был исключён в пользу нового GFortran frontend, который поддерживает Fortran 95.

Также существуют front-end’ы для Паскаль (язык программирования)|Pascal, D, Modula-2, Modula-3, Mercury, VHDL и PL/I.

Архитектуры

Список поддерживаемых GCC (для версии 4.3) процессоров включает в себя:

Менее известные процессоры, поддерживаемые в стандартном релизе:

Дополнительные типы архитектур и процессоров, которые поддерживаются версиями GCC, но поддержкой которых занимаются сторонние организации (не Фонд свободного программного обеспечения):

Структура

Внешний интерфейс GCC является стандартом для компиляторов на платформе UNIX. Пользователь вызывает управляющую программу, которая называется gcc. Она интерпретирует аргументы командной строки, определяет и запускает для каждого входного файла свои компиляторы нужного языка, запускает, если необходимо, ассемблер и компоновщик.

Компилятор каждого языка является отдельной программой, которая получает исходный текст и порождает вывод на язык ассемблера|языке ассемблера. Все компиляторы имеют общую внутреннюю структуру: front end, который производит синтаксический разбор и порождает абстрактное синтаксическое дерево, и back end, который конвертирует дерево в Register Transfer Language (RTL), выполняет различные оптимизации, затем порождает программу на языке ассемблера, используя архитектурно-зависимое сопоставление с образцом.

Отладка программ, скомпилированных с помощью GCC

Главным инструментом для отладки программ, скомпилированных с помощью GCC, является GNU Debugger (gdb). Существуют также узкоспециализированные средства для отладки:

Лицензия

GCC версии 4.2.1 стал последним релизом, выпущенным под GNU General Public License версии 2. Все последующие версии лицензируются по [[GNU General Public License#GPL v3|GPL версии 3].

Критика

Обзор GCC 4.8

С выпуском GCC 4.8.0 разработчики набора компиляторов GNU Compiler Collection завершили переход на C++ в реализации GCC. Работа по переводу кодовой базы на C++ продолжалась c 2008 года, и теперь подошла к концу. Миграция на C++ означает, что теперь для сборки GCC из исходников обязательно требуется компилятор С++ 2003. Ричард Столлман написал первый вариант GCC в 1985 году на непереносимом диалекте языка Паскаль. В 1987 году компилятор был переписан на языке Си, и в таком виде существовал до 2013 года.В новой версии GCC 4.8 улучшена производительность, реализован новый уровень оптимизации –Og для сверхбыстрой компиляции почти без оптимизаций. Добавлены детектор ошибок в памяти Address Sanitizer от компании Google и детектор гонок данных Thread Sanitizer, который обнаруживает совместный доступ к одним и тем же данным из различных нитей многопоточного приложения. Более подробно о нововведениях см. release notes.Детектор Address Sanitizer можно использовать на платформах Linux (IA-32, x86-64, x32, PowerPC, PowerPC64) и Darwin (x86-64), при этом скорость работы программы замедляется примерно в два раза.Детектор Thread Sanitizer замедляет скорость примерно в 10 раз.Кроме того, в GCC 4.8 улучшена поддержка C++11 и появилась поддержка архитектуры AArch64(ARM64), присутствующей в процессорах с набором команд ARMv8, хотя на рынке пока нет устройств с таким набором команд.GCC — официальный компилятор системы GNU, он также является главным компилятором для ряда других операционных систем, в том числе разных вариантов Linux и BSD, Mac OS X, ReactOS, BeOS и проч. Подробнее о причинах миграции на C++ и конкретно о внесённых изменениях см. в GCC Wiki: C++ Conversion. Вкратце, причина в популярности языка C++ и более чистом коде на «плюсах».

К счастью, переход на C++ практически не отразился на производительности компиляторов GCC. [Источник 2]

Установка GCC на Debian Linux

Источник

Незамысловатый блог

03.12.2010

О GCC, компиляции и библиотеках

Основы

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

printf( «Hello World \n » );

Теперь в каталоге c hello.c отдадим команду:

Через несколько долей секунды в каталоге появиться файл a.out :

Это и есть готовый исполняемый файл нашей программы. По умолчанию gcc присваивает выходному исполняемому файлу имя a.out (когда-то очень давно это имя означало assembler output).

Запустим получившийся программный продукт:

Утилита 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 🙂

Процесс компиляции можно разбить на 4 основных этапа: обработка препроцессором, собственно компиляция, ассемблирование, линковка (связывание).

Опции gcc позволяют прервать процесс на любом из этих этапов.

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

Получить исполняемый код разумеется можно и из файла hello.s :

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

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

Компиляция с уровнем оптимизации по умолчанию:

Компиляция с максимальным уровнем оптимизации:

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

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

Собственно о компиляции все. Далее поговорим о раздельной компиляции и создании библиотек.

c c-header c-cpp-output

objective-c objective-c-header objective-c-cpp-output

objective-c++ objective-c++-header objective-c++-cpp-output

Назначение аргументов должно быть понятно из их написания (здесь cpp не имеет ни какого отношения к C++, это файл исходного кода предварительно обработанный препроцессором). Проверим:

Раздельная компиляция

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

2. Позволяет сократить время повторной компиляции проекта. Если изменения внесены в один файл нет смысла перекомпилировать весь проект, достаточно заново откомпилировать только этот изменённый файл.

3. Позволяет распределить работу над проектом между несколькими разработчиками. Каждый программист творит и отлаживает свою часть проекта, но в любой момент можно будет собрать (пересобрать) все получающиеся наработки в конечный продукт.

4. Без раздельной компиляции не существовало бы библиотек. Посредством библиотек реализовано повторное использование и распространение кода на C/C++, причем кода бинарного, что позволяет с одной стороны предоставить разработчикам простой механизм включения его в свои программы, с другой стороны скрыть от них конкретные детали реализации. Работая над проектом, всегда стоит задумываться над тем, а не понадобиться что-либо из уже сделанного когда-нибудь в будущем? Может стоит заранее выделить и оформить часть кода как библиотеку? По моему, такой подход, существенно упрощает жизнь и экономит массу времени.

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

Вот практический пример (правда весьма и весьма условный).

Набор файлов исходного кода:

#include «first.h»
#include «second.h»

printf( «Main function. \n » );

printf( «First function. \n » );

printf( «Second function. \n » );

В общем имеем вот что:

Все это хозяйство можно скомпилировать в одну команду:

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

Что мы сделали? Из каждого исходного файла (компилируя с опцией -c ) получили объектный файл. Затем объектные файлы слинковали в итоговый исполняемый. Разумеется команд gcc стало больше, но в ручную ни кто проекты не собирает, для этого есть утилиты сборщики (самая популярная make). При использовании утилит сборщиков и проявятся все из перечисленных выше преимуществ раздельной компиляции.

Просмотреть таблицу символов можно с помощью утилиты nm.

Таблица символов прописывается не только в объектный, но и в исполняемый файл:

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

Библиотеки

С точки зрения операционной системы и прикладного программного обеспечения библиотеки бывают статическими и разделяемыми (динамическими).

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

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

Парадигма разделяемых библиотек предоставляет три существенных преимущества:

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

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

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

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

Теперь немного практики.

В начале создадим и используем статическую библиотеку.

Ну а теперь, введем следующую последовательность команд:

Для создания индекса архива существует специальная утилита ranlib. Библиотеку libhello.a можно было сотворить и так:

Впрочем библиотека будет прекрасно работать и без индекса архива.

Теперь воспользуемся нашей библиотекой:

Ну теперь комментарии. Появились две новые опции gcc:

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

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

Существует альтернативный способ указания местоположения библиотек в системе. В зависимости от дистрибутива, переменная окружения LD_LIBRARY_PATH или LIBRARY_PATH может хранить список разделенных знаком двоеточия каталогов, в которых линковщик должен искать библиотеки. Как правило, по умолчанию эта переменная вообще не определена, но ни чего не мешает её создать:

Теперь создадим и используем библиотеку динамическую.

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

Что получили в результате?

Что такое gcc компилятор. Смотреть фото Что такое gcc компилятор. Смотреть картинку Что такое gcc компилятор. Картинка про Что такое gcc компилятор. Фото Что такое gcc компилятор

Убедимся, что заданная строка soname действительно прописана в файле нашей библиотеки. Воспользуемся мега утилитой objdump с опцией -p :

В приведенном примере создания библиотеки мы неотступно следовали принципам раздельной компиляции. Разумеется скомпилировать библиотеку можно было бы и вот так, одним вызовом gcc:

Теперь попытаемся воспользоваться получившейся библиотекой:

Линковщик ругается. Вспоминаем, что было сказано выше о символических ссылках. Создаем libhello.so и повторяем попытку:

Теперь все довольны. Запускаем созданный бинарник:

Создаем соответствующую ссылку и повторно запускаем приложение:

Заработало. Теперь комментарии по новым опциям gcc.

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

Узнать какие разделяемые библиотеки необходимы приложению можно и с помощью утилиты ldd:

В выводе ldd для каждой требуемой библиотеки указывается её soname и полный путь к файлу библиотеки, определённый в соответствии с настройками системы.

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

В соответствии с соглашениями FHS (Filesystem Hierarchy Standard) в системе должны быть два (как минимум) каталога для хранения файлов библиотек:

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

Убедимся в сказанном:

Ну и в конце поговорим о том как уживаются вместе статические и динамические версии библиотек. В чем собственно вопрос? Выше, когда обсуждались принятые имена и расположение файлов библиотек было сказано, что файлы статической и динамической версий библиотеки хранятся в одном и том же каталоге. Как же gcc узнает какой тип библиотеки мы хотим использовать? По умолчанию предпочтение отдается динамической библиотеки. Если линковщик находит файл динамической библиотеки, он не задумываясь цепляет его к исполняемому файлу программы:

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

Так как размер кода библиотеки libhello ничтожен,

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

Ну вот пожалуй и все. Большое спасибо всем, кто закончил чтение на этом месте.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *