Компиляция и сообщение компилятору

Обновлено: 14.05.2024

Изготовление исполнимых программ из исходных текстов выполняется с помощью компиляторов, переводящих исходный текст программы в эквивалентную ей результирующую программу на языке машинных команд. Основными языками программирования на высокопроизводительных вычислительных системах являются С/C++ и Фортран . Язык С создавался как язык для написания системных приложений, однако в последнее время широко применяется и для написания вычислительных программ. Язык программирования Фортран изначально разрабатывался для написания вычислительных программ. Для него разработано множество библиотек прикладных подпрограмм, в которых реализованы различные вычислительные алгоритмы. Например, библиотека LAPACK содержит широчайший набор подпрограмм для решения различных задач линейной алгебры.

Синтаксис команды компиляции имеет вид:

компилятор [опции] файлы [библиотеки]

  • Здесь компилятор - команда вызова компилятора;
  • основные опции:
  • -o - создать выходной файл с заданным именем (без опции создается a.out);
  • -c - не изготавливать исполнимый модуль (при компиляции подпрограмм);
  • -O -O1,-O2,-O3 - задание уровня оптимизации;
  • -g - выполнить компиляцию в отладочном режиме;
  • файлы - компилируемые файлы;;
  • библиотеки - подключаемые библиотеки.

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

На UNIX-подобных системах имеется множество компиляторов. Большая часть из них является коммерческими продуктами. Для систем Linux пакет GCC является неотъемлемой частью дистрибутивов, поскольку является базовым компилятором сборки ядра системы и всех ее утилит.

Пакет компиляторов GCC

В него входят компиляторы:

  • gcc - компилятор языка С;
  • g++ - компилятор языка С++;
  • gfortran - компилятор языка Фортран95.

Компиляторы GCC оптимизирующие, поддерживающие три уровня оптимизации (опции -O1, -O2, -O3). На разных программах более эффективной может оказаться та или другая опция. В большинстве случаев наиболее приемлемой бывает опция -O2, при этом ускорение программы может достигать 2-3 раз. Типичные команды компиляции:

  • gcc -O2 -o prog prog.c - для языка С;
  • gfortran -O2 -o prog prog.f - для языка Фортран.

Помимо этого, на Linux кластерах, являющихся сегодня основным видом высокопроизводительных вычислительных систем, широко используется пакет компиляторов Intel Compiler, наилучшим образом оптимизированный под платформу x86-64, являющуюся основной при построении вычислительных кластеров. Это коммерческй продукты и он приобретен Вычислительным центром СПбГУ.

Пакет компиляторов Intel

  • icc - компилятор языка С;
  • icpc - компилятор языка С++;
  • ifort - компилятор языка f77, f90, f95.

Компиляторы также поддерживают три уровня оптимизации (опции -O1, -O2, -O3, задание опции -O соответствует уровню -O2). Сочетание опций -fast -On, задает режим максимального ускорения программы на соответствующем уровне оптимизации. Для отлаженных программ включение оптимизации обязательно. В большинстве случаев ускорение работы программы может достигать 2-3 раз.

  • icc -O2 -o prog prog.c - для языка С;
  • ifort -O2 -o prog prog.f - для языка Фортран.

Рассмотрим подробнее работу с компилятором gcc.

Создадим файл с именем ex1.c с помощью команды touch. Откроем его в текстовом редакторе и наберем текст программы на языке С.

Далее следует скомпилировать программу, т.е. перевести в исполнимый код. Для этого выполним следующую команду.

Если программа написана без ошибок, то никакой выдачи информации на терминал не будет, а в рабочем каталоге появится файл с именем a.out. Это исполнимый файл, полученный в результате компиляции программы. Его можно запустить на исполнение(поэтому файлы и называются исполнимыми), набрав в командной строке:

На терминал будет напечатана строка "Hello word".

Для того чтобы поменять имя создаваемого файла c a.out на любое другое необходимо использовать опцию -o:

gcc -o ex1 ex1.c

В результате будет создан исполнимый файл с именем ex1.

Приведем несколько важных опций компилятора gcc (они справедливы и для icc)

  • -o файл - Поместить вывод в файл 'файл'. Эта опция применяется вне зависимости от вида порождаемого файла, является ли это выполнимый файл, объектный файл, ассемблерный файл или препроцессированный C код. Если '-o' не указано, по умолчанию выполнимый файл помещается в 'a.out', объектный файл для 'исходный.суффикс' - в 'исходный.o', его ассемблерный код в 'исходный.s' и все препроцессированные C файлы - в стандартный вывод.
  • -c - Компилировать или ассемблировать исходные файлы, но не линковать. Стадия ликовки просто не выполняется. Конечный вывод происходит в форме объектного файла для каждого исходного файла.
  • -g - Порождает отладочную информацию.
  • -O,-O1,-O2,-O3 - Задание уровня оптимизации оптимизации
  • -Iдиректория - Добавляет каталог 'директория' в начало списка каталогов, используемых для поиска заголовочных файлов. Ее можно использовать для подмены системных заголовочных файлов, подставляя ваши собственные версии, поскольку эти директории просматриваются до директорий системных заголовочных файлов. Если используется более чем одна опция '-I', директории просматриваются в порядке слева на право; стандартные системные директории просматриваются последними.
  • -Lдиректория - Добавляет каталог 'директория' в начало списка каталогов, используемых для поиска библиотек
  • -lбиблиотека - Подключает библиотеку с именем lib'библиотека'.so

Рассмотрим назначение опций более подробно на примерах.

-Iдиректория,
где директория - путь к каталогу, в котором расположен данный файл.

Если используется стандартный заголовочный файл, то опцию -I для его поиска в командной строке компиляции программы указывать необязательно. Существует специальный каталог, где располагаются стандартные заголовочные файлы. Препроцессор автоматически просматривает его при поиске заголовочных файлов. Все сказанное в полной мере относится и к компилятору с языка Фортран. Отличие состоит в синтаксисе подключения include файла:

include 'файл.h'

Если в команде компиляции не указана опция -c, то компилятор автоматически выполняет операцию компоновки, т.е. изготовление исполнимой программы. В примере для вывода строки "Hello word" применялась стандартная функция printf, следовательно, код этой функции должен быть вставлен в программу. Операцию объедения кода программы и кода внешних функций выполняет компоновщик. Компоновщик (или линковщик - linker) - программа, которая производит компоновку, принимает на вход один или несколько объектных модулей и собирает из них исполняемый модуль. Объектный модуль (или объектный файл - object file) - это файл с промежуточным представлением отдельного модуля программы, полученный в результате обработки исходного кода компилятором. Объектный файл содержит в себе особым образом подготовленный код (часто называемый бинарным), который может быть объединён с другими объектными файлами при помощи редактора связей (линковщика) для получения готового исполняемого модуля либо библиотеки.

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

Попробуем скомпилировать программу командой:

gcc -o ex2 ex2.c

/tmp/ccgSk9AB.o(.text+0x49): In function `main':
ex2.c: undefined reference to `pow'
collect2: ld returned 1 exit status

gcc -o ex2 ex2.c -lm

В результате будет создана программа с именем ex2, которая при запуске напечатает:

Подключение библиотеки было выполнено с помощью опции -lm. Файл этой библиотеки находится в каталоге /usr/lib. Полное его название libm, имена файлов библиотек подпрограмм всегда начинаются с префикса lib, за которым идет название библиотеки. При подключении библиотеки к программе в строке компилятора префикс lib заменяется на -l. Таким образом, подключение библиотеки libm осуществляется опцией -lm. Поскольку библиотека стандартная, находится в специальном каталоге, то нет необходимости указывать путь поиска файла библиотеки математических подпрограмм с помощью опции -L. Компилятор сам найдет его в директории /usr/lib. Работа с библиотеками имеет ряд аспектов, которые нуждаются в более подробном рассмотрении.

В рассмотренном ранее примере было упомянуто, что стандартная математическая библиотека находится в системном каталоге /usr/lib. Однако если перейти в каталог /usr/lib, и попробовать найти там файл с именем libm, то такого файла там нет. Зато есть два файла с именами libm.a и libm.so. Почему два и с разными расширениями? Потому что большинство UNIX-подобных систем поддерживают два типа компоновки - статическую и динамическую.

error while loading shared libraries: libxxx.so: cannot open shared object file: No such file or directory

Статические библиотеки в виде пакетов объектных файлов, присоединяются (линкуются) к исполнимой программе на этапе компиляции (в Windows такие файлы имеют расширение .lib, а в UNIX-подобных .a). В результате этого программа включает в себя все необходимы функции, что делает её автономной, хорошо переносимой, но увеличивает размер.

Статическая библиотека создается специальной командой:

ar rc libимя.a список_объектных_файлов

Объектные файлы создаются компиляцией функций с опцией -c. Рекомендуется каждую функцию (или подпрограмму в Фортране) оформлять в отдельном файле.

Динамическая библиотека создаются компилятором:

gcc -shared -o libимя.so список_объектных_файлов

Для создания объектных файлов компиляция выполняется с опциями -fPIC -c. Опция -fPIC (PIC - Position Independent Code) означает создание позиционно-независимого кода.

Все библиотеки обычно хранятся в каталоге lib. Если с одним и тем же именем имеется две библиотеки и статическая и динамическая, то по умолчанию линковщик будет использовать динамическую библиотеку. Предположим, что в домашнем каталоге пользователя имеется подкаталог lib и в нем находятся два библиотечных файла: libmy.a и libmy.so. Подкаталог includeсодержит заголовочный файл. Тогда команда компиляции
gcc -o prog_shared prog.c -I~/include -L~/lib -lmy
будет использовать динамическую библиотеку.

Для создания исполнимого файла со статической библиотекой потребуется команда:

gcc -static -o prog_static prog.c -I~/include -L~/lib -lmy

Мы создали две версии программы: с использованием динамической и статической библиотек. Во втором случае использовалась опция -static, чтобы компилятор использовал статическую библиотеку libmy.a. Если бы динамической версии библиотеки не было, то эту опцию можно было бы не указывать. Компилятор, не найдя динамической библиотеки автоматически подключает статическую библиотеку. Опция -I~/include заставляет искать заголовочные файлы в пользовательском подкаталоге include. Заметим, что в Фортране использование заголовочных файлов не требуется, и include файлы используются для других целей - определения констант и параметров. Опция -L~/lib указывает компилятору, что при сборке программы, помимо стандартных путей, следует искать библиотеки и в директории lib домашнего каталога пользователя.

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

  • команда
  • ./prog_static - выполнится без проблем;
  • а при запуске
  • ./prog_shared - программа завершится с ошибкой:
  • prog_shared: error while loading shared libraries: libmy.so: cannot open shared object file: No such file or directory

Дело в том, что в момент загрузки программы, система ищет необходимые для запуска программы разделяемые библиотеки, чтобы собрать исполнимую программу. Поиск идет по заранее установленному списку директорий. Имена директорий перечислены в системном файле /etc/ld.so.conf. Очевидно, что в этот файл невозможно занести все индивидуальные каталоги пользователей. В этой ситуации на помощь приходят переменные окружения. Как уже говорилось ранее, в UNIX системах существует специальная переменная LD_LIBRARY_PATH, в которой каждый пользователь может перечислить директории для поиска разделяемых библиотек. Добавим к переменной LD_LIBRARY_PATH путь к директории lib, где находится библиотека libmy.so. Делается это командой

export LD_LIBRARY_PATH=$:~/lib (bash)
setenv LD_LIBRARY_PATH $:~/lib (tcsh)

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

Предпочтительное использование динамических библиотек обусловлено тем, что размеры исполнимых модулей в десятки раз меньше, чем у статических. Все системные утилиты собираются с использованием динамических библиотек. А поскольку в системе их несколько тысяч, то экономятся гигантские объемы дискового пространства. Кроме того, исполнимые файлы с использованием динамических библиотек более мобильны. В качестве примера рассмотрим типичную ситуацию. В организации имеется два кластера с различной коммуникационной средой - Ethernet и Infiniband. Если использовать статические MPI библиотеки, то для каждого кластера нужно иметь свою версию программы, а если использовать динамические библиотеки, то программы становится совместимыми. При запуске программы на каждом кластере будет вызываться своя версия коммуникационной библиотеки. Еще одно преимущество динамических библиотек состоит в том, что при обновлении системной библиотеки не потребуется пересборка всех системных утилит и программ пользователей.

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

  • mpif77 -O -o progname progname.f - на языке Фортран
  • mpicc -O -o progname progname.c - на языке С
  • mpicxx -O -o progname progname.cc - на языке С++

Здесь mpif77, mpicc, mpicxx - командные скрипты, вызывающие стандартные компиляторы с настройкой путей к необходимым include-файлам и подключением всех необходимых коммуникационных библиотек библиотек. Использование таких скриптов, в свою очередь, порождает некоторые проблемы. Дело в том, что практически на любом вычислительном кластере имеется множество версий коммуникационных библиотек. Это, во-первых, связано с необходимостью обновления установленных версий, а, во-вторых, с тем, что поставщики коммуникационного программного обеспечения, как правило, предоставляют множество реализаций коммуникационных библиотек. Например, в состав коммуникационного пакета OFED входят три различных реализации MPI (MVAPICH, MVAPICH2, OpenMPI), которые к тому же собираются всеми имеющимися в системе компиляторами. К сожалению, все эти версии не совместимы друг с другом, и поэтому очень важно при работе с MPI программами соблюсти синхронность в использовании коммуникационных библиотек. Это означает, что если программа откомпилирована с использованием некоторой версии MPI, то нужно быть уверенным, что при запуске программы на выполнение будет использована та же самая версия MPI, т.е. будет использована команда mpirun из этой же версии пакета, и будут подключены нужные версии динамических библиотек. Это достигается соответствующими настройками переменных окружения PATH и LD_LIBRARY_PATH.

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

Все действия будут производиться на Ubuntu версии 16.04.
Используя компилятор g++ версии:

Состав компилятора g++

Мы не будем вызывать данные компоненты напрямую, так как для того, чтобы работать с C++ кодом, требуются дополнительные библиотеки, позволив все необходимые подгрузки делать основному компоненту компилятора — g++.

Зачем нужно компилировать исходные файлы?

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

Этапы компиляции:

Перед тем, как приступать, давайте создадим исходный .cpp файл, с которым и будем работать в дальнейшем.

driver.cpp:

1) Препроцессинг

Самая первая стадия компиляции программы.

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

Взглянув на тело функции main в новом сгенерированном файле, можно заметить, что макрос RETURN был заменен:

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

2) Компиляция

На данном шаге g++ выполняет свою главную задачу — компилирует, то есть преобразует полученный на прошлом шаге код без директив в ассемблерный код. Это промежуточный шаг между высокоуровневым языком и машинным (бинарным) кодом.

Ассемблерный код — это доступное для понимания человеком представление машинного кода.

Используя флаг -S, который сообщает компилятору остановиться после стадии компиляции, получим ассемблерный код в выходном файле driver.s:

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

3) Ассемблирование

Так как x86 процессоры исполняют команды на бинарном коде, необходимо перевести ассемблерный код в машинный с помощью ассемблера.

Ассемблер преобразовывает ассемблерный код в машинный код, сохраняя его в объектном файле.

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

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

Получим машинный код с помощью ассемблера (as) в выходной объектный файл driver.o:

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

4) Компоновка

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

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

Получим исполняемый файл driver:

5) Загрузка

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

Запустим нашу программу:

Заключение

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

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

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

Зачем нужен компилятор?

Процессор — самая важная часть компьютера. Он обрабатывает информацию, выполняет команды пользователя и следит за работой всех подключенных устройств. Но процессор может разобрать только машинный код — набор 0 и 1, которые записаны в определённом порядке.

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

Программы для первых компьютеров выглядели как огромные наборы 0 и 1. Чтобы записать такую программу, инженеры пользовались гибкими картонными карточками — перфокартами. Цифры на перфокарте записывались поочередно, в несколько строк. Чтобы записать 1, программист делал отверстие в карте. Места без отверстия обозначали 0.

Изображение перфокарты

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

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

Как работает компилятор?

Преобразование программного кода в машинный называется компиляцией. Компиляция только преобразует код. Она не запускает его на исполнение. В этот момент он “статически” (то есть без запуска) транслируется в машинный код. Это сложный процесс, в котором сначала текст программы разбирается на части и анализируется, а затем генерируется код, понятный процессору.

Этапы компиляции

Разберём этапы компиляции на примере вычисления периметра прямоугольника:

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

Затем компилятор читает список и ищет токен-операторы. Это могут быть оператор присваивания( = ), арифметические операторы( + , - , * , / ), оператор вывода( printf() ) и другие операторы языка программирования. Такие операторы работают с числами, текстом и переменными.

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

Так операция P = 2*(a + b) будет преобразована в логическое дерево:

Дерево разбора

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

  • Взять переменную a , взять переменную b , сложить их
  • Взять результат сложения, взять число 2 и найти их произведение
  • Результат произведения присвоить (записать) в переменную P

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

На чем написан компилятор?

В 1950-е годы группа разработчиков IBM под руководством Джона Бэкуса разработала первый высокоуровневый язык программирования Fortran, который позволил писать программы на понятном человеку языке. Помимо языка, инженеры работали и над компилятором. Он представлял собой программу с набором исполняемых команд, которая могла компилировать другие программы на Fortran, в том числе и улучшенную версию себя.

Этапы создания компилятора

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

Какие бывают компиляторы?

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

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

Если программа будет работать на нескольких операционных системах, то нужен кросс-компилятор — компилятор, который преобразует универсальный машинный код. Например, GNU Compiler Collection(сокращенно GCC) поддерживает C++, Objective-C, Java, Фортран, Ada, Go и поддерживает разную архитектуру процессоров.

Начинающие программисты даже не знают о наличии компилятора на компьютере. Они пишут программы в интегрированной среде разработки, в которую встроен компилятор, а иногда и не один. В этом случае, выбор компилятора делает среда, а не программист. Например, MS Visual Studio поддерживает компиляторы для операционных систем Windows, Linux, Android. Выбирая тип проекта, Visual Studio определяет процессор и операционную систему компьютера, и после этого выбирает подходящий компилятор.

Какие ошибки может определить компилятор?

  • ошибки объявления переменных или отсутствие их начальных значений
  • ошибки несоответствия типов
  • ошибки неправильной записи операторов и функций

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

Выводы и рекомендации

Компилятор — переводчик между программистом и процессором. Он преобразует текст программы в машинный код, определяет ряд ошибок в программе и оптимизирует ее работу. Выбирая, где компилировать программу, важно помнить о том, что машинный код для процессоров и операционных систем будет разным, и подобрать правильный компилятор. Чем точнее компилятор определит команды, тем корректнее и быстрее будет работать программа. Для этого следуйте простым рекомендациям:

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

Частые вопросы

Чем компилятор отличается от интерпретатора?

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

Большинство компиляторов переводит программу с некоторого высокоуровневого языка программирования в машинный код, который может быть непосредственно выполнен центральным процессором. Как правило, этот код также ориентирован на исполнение в среде конкретной операционной системы, поскольку использует предоставляемые ею возможности (системные вызовы, библиотеки функций). Архитектура (набор программно-аппаратных средств), для которой производится компиляция, называется целевой машиной.

Для каждой целевой машины (Apple и т. д.) и каждой операционной системы или семейства операционных систем, работающих на целевой машине, требуется написание своего компилятора. Существуют также так называемые кросс-компиляторы, позволяющие на одной машине и в среде одной ОС получать код, предназначенный для выполнения на другой целевой машине и/или в среде другой ОС. Кроме того, компиляторы могут быть оптимизированы под разные типы процессоров из одного семейства (путём использования специфичных для этих процессоров инструкций). Например, код, скомпилированный под процессоры семейства MMX, SSE2.

Также существуют компиляторы, переводящие программу с языка высокого уровня на язык ассемблера.

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

Структура компилятора

Процесс компиляции состоит из следующих этапов:

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

Трансляция и компоновка

Интересные факты

Примечания

  1. ↑ГОСТ 19781-83 // Вычислительная техника. Терминология: Справочное пособие. Выпуск 1 / Рецензент канд. техн. наук Ю. П. Селиванов. — М.: Издательство стандартов, 1989. — 168 с. — 55 000 экз. — ISBN 5-7050-0155-X
  2. 1234567Першиков В. И., Савинков В. М. Толковый словарь по информатике / Рецензенты: канд. физ.-мат. наук А. С. Марков и д-р физ.-мат. наук И. В. Поттосин. — М.: Финансы и статистика, 1991. — 543 с. — 50 000 экз. — ISBN 5-279-00367-0
  3. 123 СТ ИСО 2382/7-77 // Вычислительная техника. Терминология. Указ. соч.
  4. Борковский А. Б. Англо-русский словарь по программированию и информатике (с толкованиями). — М.: Русский язык, 1990. — 335 с. — 50 050 (доп.) экз. — ISBN 5-200-01169-3
  5. ↑ Толковый словарь по вычислительным системам = Dictionary of Computing / Под ред. В. Иллингуорта и др.: Пер. с англ. А. К. Белоцкого и др.; Под ред. Е. К. Масловского. — М.: Машиностроение, 1990. — 560 с. — 70 000 (доп.) экз. — ISBN 5-217-00617-X (СССР), ISBN 0-19-853913-4 (Великобритания)
  6. Н. А. Криницкий, Г. А. Миронов, Г. Д. Фролов. Программирование / Под ред. М. Р. Шура-Бура. — М.: Государственное издательство физико-математической литературы, 1963.

См. также

Литература

Wikimedia Foundation . 2010 .

Полезное

Смотреть что такое "Компиляция (программирование)" в других словарях:

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

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

Сравнение языков программирования — Эту статью следует викифицировать. Пожалуйста, оформите её согласно правилам оформления статей. Условные обозначения … Википедия

Пайтон — Python Класс языка: функциональный, объектно ориентированный, императивный, аспектно ориентированный Тип исполнения: интерпретация байт кода, компиляция в MSIL, компиляция в байт код Java Появился в: 1990 г … Википедия

ГОСТ 19781-90: Обеспечение систем обработки информации программное. Термины и определения — Терминология ГОСТ 19781 90: Обеспечение систем обработки информации программное. Термины и определения оригинал документа: 9. Абсолютная программа Non relocatable program Программа на машинном языке, выполнение которой зависит от ее… … Словарь-справочник терминов нормативно-технической документации

Паскаль (язык) — Pascal Семантика: процедурный Тип исполнения: компилятор Появился в: 1970 г. Автор(ы): Никлаус Вирт Паскаль (англ. Pascal) высокоуровневый язык программирования общего назначения. Один из наиболее известных языков программирования, широко… … Википедия

Паскаль (язык программирования) — Эта статья или раздел нуждается в переработке. В Паскале нет модулей, ООП и прочих новомодных веяний. Описание расширений должно присутствовать только в статьях о соответ … Википедия

D (язык программирования) — У этого термина существуют и другие значения, см. D. D Семантика: мультипарадигменный: императивное, объектно ориентированное, обобщённое программирование Тип исполнения: компилятор Появился в: 1999 Автор(ы) … Википедия

Читайте также: