Пошаговое руководство. компиляция собственной программы на языке c++ из командной строкиwalkthrough: compiling a native c++ program on the command line

Интерпретаторы

Программируя на интерпретируемом языке, мы пишем программу не для выполнения в процессоре, а для выполнения программой-интерпретатором. Ее также называют виртуальной машиной.

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

При интерпретации выполнение кода происходит последовательно строка за строкой (от инструкции до инструкции). Операционная система взаимодействует с интерпретатором, а не исходным кодом.

Примеры интерпретируемых языков: PHP, JavaScript, C#, Python.

Скомпилированные программы работают быстрее, но при этом очень много времени тратится на компиляция исходного кода.

Программы же, рассчитанные на интерпретаторы, могут выполняться в любой системе, где таковой интерпретатор присутствует. Типичный пример — код JavaScript. Интерпретатором его выступает любой современный браузер. Вы можете однократно написать код на JavaScript, включив его в html-файл, и он будет одинаково выполняться в любой среде, где есть браузер

Не важно, будет ли это Safari в Mac OS, или же Internet Explorer в Windows

Виды компиляторов

  • Векторизующий. Базируется на трансляторе, транслирующем исходный код в машинный код компьютеров, оснащённых векторным процессором.
  • Гибкий. Сконструирован по модульному принципу, управляется таблицами и запрограммирован на языке высокого уровня или реализован с помощью компилятора компиляторов.
  • Диалоговый. См.: диалоговый транслятор.
  • Инкрементальный. Пересобирает программу, заново транслируя только измененные фрагменты программы без перетрансляции всей программы.
  • Интерпретирующий (пошаговый). Последовательно выполняет независимую компиляцию каждого отдельного оператора (команды) исходной программы.
  • Компилятор компиляторов. Транслятор, воспринимающий формальное описание языка программирования и генерирующий компилятор для этого языка.
  • Отладочный. Устраняет отдельные виды синтаксических ошибок.
  • Резидентный. Постоянно находится в оперативной памяти и доступен для повторного использования многими задачами.
  • Самокомпилируемый. Написан на том же языке программирования, с которого осуществляется трансляция.
  • Универсальный. Основан на формальном описании синтаксиса и семантики входного языка. Составными частями такого компилятора являются: ядро, синтаксический и семантический загрузчики.

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

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

  1. Трансляция программы — трансляция всех или только изменённых модулей исходной программы.
  2. компоновка машинно-ориентированной программы.

Структурные реализации компилятора могут быть следующими:

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

По первой схеме строились самые первые компиляторы, — для современных компиляторов такая схема построения нехарактерна.

По второй схеме построены все без исключения компиляторы с языков высокого уровня. Любой такой компилятор сам выполняет только трансляцию и далее вызывает компоновщик как внешнюю подпрограмму, который и компонует машинно-ориентированную программу. Такая схема построения легко позволяет компилятору работать и в режиме транслятора с соответствующего языка программирования. Этот обстоятельство нередко служит поводом считать компилятор разновидностью транслятора, что естественно неверно, — все современные компиляторы такого типа все же выполняют компоновку, пусть и силами вызываемого компилятором внешнего компоновщика, тогда как транслятор сам никогда не выполняет вызов внешнего компоновщика. Но это же обстоятельство позволяет компилятору с одного языка программирования на фазе компоновки включать в программу написанную на одном языке программирования функции-подпрограммы из уже оттранслированных соответствующим транслятором/компилятором, написанные на ином языке программирования. Так в программу на С/С++ можно вставлять функции написанные например на Pascal или Fortran. Аналогично и напротив написанная на С/С++ функции могут быть вставлены в Pascal- или Fortran-программу соответственно. Это было бы невозможно без поддержки многими современными компиляторами генерации кода вызова процедур (функций) в соответствии с соглашениями иных языков программирования. Например современные компиляторы с языка Pascal помимо организации вызова процедур/функций в стандарте самого Pascal поддерживают организацию вызова процедурой/функцией в соответствии с соглашениями языка С/С++. (Например чтобы на уровне машинного кода написанная на Pascal процедура/функция работала с входными параметрами в соответствии с соглашениями языка С/С++, — оператор объявления такой Pascal-процедуры/Pascal-функции должен содержать ключевое слово cdecl.)

Наконец по третьей схеме построены компиляторы, представляющие собой целые системы, включающие в себя трансляторы с разных языков программирования и компоновщики. Также любой такой компилятор может использовать в качестве транслятора любой способный работать в режиме транслятора компилятор с конкретного языка высокого уровня. Естественно такой компилятор может компилировать программу, разные части исходного текста которой написаны на разных языках программирования. Нередко такие компиляторы управляются встроенным интерпретатором того или иного командного языка. Яркий пример таких компиляторов — имеющийся во всех UNIX-системах (в частности в Linux) компилятор make.

Трансляция программы как неотъемлемая составляющая компиляции включает в себя:

  1. Лексический анализ. На этом этапе последовательность символов исходного файла преобразуется в последовательность лексем.
  2. Синтаксический (грамматический) анализ. Последовательность лексем преобразуется в древо разбора.
  3. Семантический анализ. На этой фазе древо разбора обрабатывается с целью установления его семантики (смысла) — например, привязка идентификаторов к их объявлениям, типам данных, проверка совместимости, определение типов выражений и т. д. Результат обычно называется «промежуточным представлением/кодом», и может быть дополненным древом разбора, новым деревом, абстрактным набором команд или чем-то ещё, удобным для дальнейшей обработки.
  4. Оптимизация. Выполняется удаление излишних конструкций и упрощение кода с сохранением его смысла. Оптимизация может быть на разных уровнях и этапах — например, над промежуточным кодом или над конечным машинным кодом.
  5. Генерация кода. Из промежуточного представления порождается код на целевом машинно-ориентированном языке.

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

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

  1. Трансляция программы — трансляция всех или только изменённых модулей исходной программы.
  2. компоновка машинно-ориентированной программы.

В первом случае компилятор представляет собой пакет программ, включающий в себя трансляторы с разных языков программирования и компоновщики. Такой компилятор может компилировать программу, разные части исходно текста которой написаны на разных языках программирования. Нередко такие компиляторы управляются встроенным интерпретатором того или иного командного языка. Яркий пример таких компиляторов — имеющийся во всех UNIX-системах (в частности в Linux) компилятор make.

Во втором случае компилятор де-факто выполняет только трансляцию и далее вызывает компоновщик как внешнюю подпрограмму, который и компонует машинно-ориентированную программу. Этот факт нередко служит поводом считать компилятор разновидностью транслятора, что естественно неверно, — все современные компиляторы такого типа поддерживают организацию импорта программой процедуры (функции) из уже оттранслированого программного модуля, написанного на другом языке программирования. Так в программу на С/С++ можно импортировать функцию написанную например Pascal или Fortran. Аналогично и напротив написанная на С/С++ функция может быть импортирована в Pascal- или Fortran-программу соотвественно. Это как правило было бы невозможно без поддержки многими современными компиляторами организации обработки входных данных в процедуру (функций) в соответствии с соглашениями других языков программирования. Например современные компиляторы с языка Pascal помимо соглашения самого Pascal поддерживает организацию обработки процедурая/функцией входных в соответствии с соглашениями языка С/С++. (Чтобы на уровне машинного кода написанная на Pascal процедура/функция работала с входными параметрами в соответствии с соглашениями языка С/С++, — оператор объявления такой Pascal-процедуры/Pascal-функции должен содержать ключевое слово cdecl.) Примерами таких компиляторов являются компиляторы со всех без исключения языков программирования, используемые непосредственно.

Трансляция программы как неотъемлемая составляющая компиляции включает в себя:

  1. Лексический анализ. На этом этапе последовательность символов исходного файла преобразуется в последовательность лексем.
  2. Синтаксический (грамматический) анализ. Последовательность лексем преобразуется в дерево разбора.
  3. Семантический анализ. Дерево разбора обрабатывается с целью установления его семантики (смысла) — например, привязка идентификаторов к их декларациям, типам, проверка совместимости, определение типов выражений и т. д. Результат обычно называется «промежуточным представлением/кодом», и может быть дополненным деревом разбора, новым деревом, абстрактным набором команд или чем-то ещё, удобным для дальнейшей обработки.
  4. Оптимизация. Выполняется удаление излишних конструкций и упрощение кода с сохранением его смысла. Оптимизация может быть на разных уровнях и этапах — например, над промежуточным кодом или над конечным машинным кодом.
  5. Генерация кода. Из промежуточного представления порождается код на целевом машинно-ориентированном языке.

Генерация кода

Генерация машинного кода

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

Результат компиляции — исполнимый программный модуль — обладает максимально возможной производительностью, однако привязан к конкретной операционной системе (семейству или подсемейству ОС) и процессору (семейству процессоров) и не будет работать на других.

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

Некоторые компиляторы переводят программу с языка высокого уровня не прямо в машинный код, а на язык ассемблера. (Пример: PureBasic, транслирующий бейсик-код в ассемблер FASM.) Это делается для упрощения части компилятора, отвечающей за генерацию кода, и повышения его переносимости (задача окончательной генерации кода и привязки его к требуемой целевой платформе перекладывается на ассемблер), либо для возможности контроля и исправления результата компиляции (в т. ч. ручной оптимизации) программистом.

Генерация байт-кода

Результатом работы компилятора может быть программа на специально созданном низкоуровневом языке двоично-кодовых команд, выполняемых виртуальной машиной. Такой язык называется псевдокодом или байт-кодом. Как правило, он не есть машинный код какого-либо компьютера и программы на нём могут исполняться на различных архитектурах, где имеется соответствующая виртуальная машина, но в некоторых случаях создаются аппаратные платформы, напрямую выполняющие псевдокод какого-либо языка. Например, псевдокод языка Java называется байт-кодом Java и выполняется в Java Virtual Machine, для его прямого исполнения была создана спецификация процессора picoJava. Для платформы .NET Framework псевдокод называется Common Intermediate Language (CIL), а среда исполнения — Common Language Runtime (CLR).

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

Динамическая компиляция

Основная статья: Динамическая компиляция  (англ.)

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

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

CIL-код также компилируется в код целевой машины JIT-компилятором, а библиотеки .NET Framework компилируются заранее.

Трасляция байт-кода в машинный код

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

Декомпиляция

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

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

Раздельная компиляция (англ. separate compilation) — трансляция частей программы по отдельности с последующим объединением их компоновщиком в единый загрузочный модуль.

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

Появление раздельной компиляции и выделение компоновки как отдельной стадии произошло значительно позже создания компиляторов. В связи с этим вместо термина «компилятор» иногда используют термин «транслятор» как его синоним: либо в старой литературе, либо когда хотят подчеркнуть его способность переводить программу в машинный код (и наоборот, используют термин «компилятор» для подчёркивания способности собирать из многих файлов один). Вот только использование в таком контексте терминов «компилятор» и «транслятор» неправильно. Даже если компилятор выполняет трансляцию программы самостоятельно, поручая компоновку вызываемой внешней программе-компоновщику, такой компилятор не может считаться разновидностью транслятора, — транслятор выполняет трансляцию исходной программы и только. И уж тем более не являются трансляторами компиляторы вроде системной утилиты-компилятра make, имеющейся во всех UNIX-системах. Утилита

Собственно утилита make — яркий пример довольно удачной реализации раздельной компиляции. Работа утилиты make управляется сценарием на интерпретируемым утилитой входном языке, известном как makefile, содержащемся в задаваемом при запуске утилиты входном текстовом файле. Сама утилита не выполняет ни трансляцию ни компоновку, — де-факто утилита make функционирует как диспетчер процесса компиляции, организующий компиляцию программы в соответствии с заданным сценарием. В частности в ходе компиляции целевой программы утилита make вызывает трансляторы с языков программирования транслирующие разные части исходной программы в объектный код, и уже после этого вызывается тот или иной компоновщик, компонующий конечный исполняемый программный или библиотечный программный модуль. При этом разные части программы, оформляемые в виде отдельных файлов исходно текста, могут быть написаны как на одном языке программирования так и на разных языках программирования. В процессе перекомпиляции программы транслируются только измененные части-файлы исходного текста программы, в следствие чего длительность перекомпиляции программы значительно (порой на порядок) сокращается.

Виды компиляции[2]

  • Пакетная. Компиляция нескольких исходных модулей в одном задании.
  • Построчная. Машинный код порождается и затем исполняется для каждой завершённой грамматической конструкции языка. Внешне воспринимается как интерпретация, но устройство имеет иное.
  • Условная. Компиляция, при которой транслируемый текст зависит от условий, заданных в исходной программе директивами компилятора. (Яркий пример — работа препроцессора языка С и производных от него.) Так, в зависимости от значения некой константы некая заданная часть исходного текста программы транслируется или не транслируется.

Добавление ссылок на внешние сборки

Давайте посмотрим, как скомпилировать приложение, в котором используются
типы, определенные в отдельной сборке .NET. Если осталось неясным, каким образом компилятору C# удалось понять ссылку на тип System.Console, вспомните,
что во время процесса компиляции происходит автоматическое добавление ссылки на mscorlib.dll (если по какой-то необычной причине нужно отключить эту функцию,
следует передать компилятору csc.exe параметр /nostdlib).

Модифицируем приложение TestApplication так, чтобы в нем открывалось окно сообщения Windows Forms. Для этого откройте файл TestApplication.cs и измените его следующим
образом:

Далее в командной строке нужно проинформировать компилятор
csc.exe о том, в какой сборке содержатся используемые пространства имен. Поскольку применялся класс MessageBox из пространства имен System.Windows.Forms, значит, нужно указать компилятору на сборку System.Windows.Forms.dll, что делается с помощью флага /reference (или его сокращенной версии /r):

Если теперь снова попробовать запустить приложение, то помимо консольного вывода в нем должно появиться еще и окно с сообщением:

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

    csc /r:System.Windows.Forms.dll;System.Drawing.dll *.cs

Виды компиляторов

  • Векторизующий. Базируется на трансляторе, транслирующем исходный код в машинный код компьютеров, оснащённых векторным процессором.
  • Гибкий. Сконструирован по модульному принципу, управляется таблицами и запрограммирован на языке высокого уровня или реализован с помощью компилятора компиляторов.
  • Диалоговый. См.: диалоговый транслятор.
  • Инкрементальный. Пересобирает программу, заново транслируя только измененные фрагменты программы без перетрансляции всей программы.
  • Интерпретирующий (пошаговый). Последовательно выполняет независимую компиляцию каждого отдельного оператора (команды) исходной программы.
  • Компилятор компиляторов. Транслятор, воспринимающий формальное описание языка программирования и генерирующий компилятор для этого языка.
  • Отладочный. Устраняет отдельные виды синтаксических ошибок.
  • Резидентный. Постоянно находится в оперативной памяти и доступен для повторного использования многими задачами.
  • Самокомпилируемый. Написан на том же языке программирования, с которого осуществляется трансляция.
  • Универсальный. Основан на формальном описании синтаксиса и семантики входного языка. Составными частями такого компилятора являются: ядро, синтаксический и семантический загрузчики.

Rust 1.45.0: стабилизация функциональных процедурных макросов, исправление дефектов преобразования

Перевод

Команда Rust рада сообщить о выпуске новой версии, 1.45.0. Rust — это язык программирования, позволяющий каждому создавать надёжное и эффективное программное обеспечение.

Если вы установили предыдущую версию Rust средствами , то для обновления до версии 1.45.0 вам достаточно выполнить следующую команду:

Что вошло в стабильную версию 1.45.0

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

Кросс-курс 160 Рублей (Россия) к другим валютам

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

В процессе выполнения компиляции, реализуются следующие этапы:

  1. Анализ лексики. Выполняется преобразование символики исходной программы в набор лексем (абстрактных единиц).
  2. Анализ синтаксиса (грамматики). Выполняется преобразование набора лексем в дерево разбора.
  3. Анализ семантики. Выполняется обработка дерева разбора для определения его смысловых значений (семантики). Это может быть процесс привязки идентификаторов к их видам, определение совместимости т так далее. В итоге формируется промежуточное представление (код), и возможно формирование дополненного дерева разбора или даже нового дерева. Возможно так же формирование абстрактного набора команд, удобного для последующего применения.
  4. Повышение оптимальности. Удаляются лишние образования и формируется упрощённый код без смысловых потерь. Процесс оптимизации возможно осуществлять на различных этапах. К примеру, можно оптимизировать промежуточный или окончательный машинный код.
  5. Процесс генерации кода. Выполняется трансформация промежуточных представлений в коды конечного языка.

Замечание 1

Для разных типов компиляторов возможно подразделение или совмещение данных этапов в различных вариациях.

Теория

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

Во-вторых, есть разные типы проектов. При создании нового проекта, вам нужно будет выбрать его тип. Все проекты, которые мы будем создавать на данных уроках, будут консольного типа. Это означает, что они запускаются в консоли (аналог командной строки). По умолчанию, консольные приложения не имеют графического интерфейса пользователя — GUI (сокр. от «Graphical User Interface») и компилируются в автономные исполняемые файлы. Это идеальный вариант для изучения языка C++, так как он сводит всю сложность к минимуму.

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

Традиционно, первой программой на новом языке программирования является всеми известная программа «Hello, world!». Мы не будем нарушать традиции

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

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

Adblock
detector