Что такое компиляция?
Компиляция — преобразование одностороннее, нельзя восстановить исходный код.
Для того, чтобы скомпилировать программу на C++ для некоторой архитектуры X, необязательно устанавливать компилятор С++ на компьютер с архитектурой X.
Не каждая программа, написанная на компилируемом языке, переносима. Т.е. не любая программа, написанная на компилируемом языке, будет работать везде одинаково.
Для того, чтобы скомпилировать программу на C++ для некоторой архитектуры X, необязательно устанавливать компилятор С++ на компьютер с архитектурой X.
Не каждая программа, написанная на компилируемом языке, переносима. Т.е. не любая программа, написанная на компилируемом языке, будет работать везде одинаково.
Плюсы и минусы компилируемости в машинный код
Плюсы:
- эффективность: программа компилируется и оптимизируется для конкретного процессора;
- нет необходимости устанавливать сторонние приложения, такие как интерпретатор или виртуальная машина (т.е. для запуска программы, написаной на компилируемом языке, не требуется установка компилятора).
Минусы:
- нужно компилировать для каждой платформы (т.е. программу, написанную на языке, который компилируется в машинный код, недостаточно скомпилировать однажды чтобы её можно было запускать на любой платформе);
- сложность внесения изменения в программу — нужно перекомпилировать заново.
Общая схема
g++ - это такая обёртка над несколькими программами:
- над препроцессором;
- над непосредственно компилятором;
- и над линковщиком.
g++ main.cpp square.cpp -o program
Этап 1: препроцессор
Язык препроцессора – это специальный язык программирования, встроенный в C++. Препроцессор работает с кодом на C++ как с текстом.
Команды языка препроцессора называют директивами, все директивы начинаются со знака #. Директива #include позволяет подключать заголовочные файлы к файлам кода.
Можно попросить компилятор вызвать только препроцессор и посмотреть что получится. Для компилятора g++ можно использовать ключ -E.
Каждый файл с кодом компилируется отдельно и независимо от других файлов с кодом. Компилируется только файлы с кодом (т.е. *.cpp).
Заголовочные файлы сами по себе ни во что не компилируются, только в составе файлов с кодом.
На выходе компилятора из каждого файла с кодом получается “объектный файл” — бинарный файл со скомпилированным кодом (с расширением .o или .obj).
Если в коде C++ вы вызывает не объявленную функцию, то это ошибка этапа компиляции.
Можно "скормить" файлы с кодом непосредственно компилятору. Для компилятора g++ можно использовать ключ -c.
Можно попросить компилятор g++ показать некоторое содержимое объектных файлов, но сам по себе смотреть объектный файл не интересно (там бинарная информация), но можно посмотреть его ассемблированный вид.
g++ -S square.cpp
Получится файл square.s.
По каждому объектному файлу строится таблица всех функций, которые в нём определены.
На этапе компоновки важно, что каждая функция имеет уникальное имя. В C++ может быть две функции с одним именем, но разными параметрами. Имена функций искажаются (mangle) таким образом, что в их имени кодируются их параметры.
Например, компилятор GCC превратит имя функции foo
в _Z3fooid. Компилятор g++ также предоставляет возможность обратного преобразования.
c++filt -n _Z3fooid
foo(int, double)
Аналогично функциям в линковке нуждаются глобальные переменные.
Точка входа — функция, вызываемая при запуске программы. По умолчанию — это функция main:
или
Даже для программы, состоящей всего из одного файла и из одной пустой функции int main() { return 0; } все равно требуется ликовка.
Если в коде C++ вы вызываете функцию, которая была объявлена, но не была определена, то это ошибка этапа линковки.
Для того чтобы собрать объектные файлы в один файл их нужно "скормить" компилятору и указать имя исполняемого файла:
g++ square.o main.o -o program
Команды языка препроцессора называют директивами, все директивы начинаются со знака #. Директива #include позволяет подключать заголовочные файлы к файлам кода.
- #include <foo.h> — библиотечный заголовочный файл,
- #include "bar.h" — локальный заголовочный файл.
Можно попросить компилятор вызвать только препроцессор и посмотреть что получится. Для компилятора g++ можно использовать ключ -E.
- g++ -E square.cpp -o square_preprocessed.cpp
- g++ -E main.cpp -o main_preprocessed.cpp
Этап 2: компиляция
На вход компилятору поступает код на C++ после обработки препроцессором.Каждый файл с кодом компилируется отдельно и независимо от других файлов с кодом. Компилируется только файлы с кодом (т.е. *.cpp).
Заголовочные файлы сами по себе ни во что не компилируются, только в составе файлов с кодом.
На выходе компилятора из каждого файла с кодом получается “объектный файл” — бинарный файл со скомпилированным кодом (с расширением .o или .obj).
Если в коде C++ вы вызывает не объявленную функцию, то это ошибка этапа компиляции.
Можно "скормить" файлы с кодом непосредственно компилятору. Для компилятора g++ можно использовать ключ -c.
- g++ -c main.cpp
- g++ -c square.cpp
На выходе получается файлы с расширением .o - это объектные файлы.
- main.o
- square.o
Можно попросить компилятор g++ показать некоторое содержимое объектных файлов, но сам по себе смотреть объектный файл не интересно (там бинарная информация), но можно посмотреть его ассемблированный вид.
g++ -S square.cpp
Получится файл square.s.
Этап 3: линковка (компоновка)
На этом этапе все объектные файлы объединяются в один исполняемый (или библиотечный) файл. При этом происходит подстановка адресов функций в места их вызова.void foo() { bar(); }
void bar() { }
По каждому объектному файлу строится таблица всех функций, которые в нём определены.
На этапе компоновки важно, что каждая функция имеет уникальное имя. В C++ может быть две функции с одним именем, но разными параметрами. Имена функций искажаются (mangle) таким образом, что в их имени кодируются их параметры.
Например, компилятор GCC превратит имя функции foo
void foo(int, double) {}
в _Z3fooid. Компилятор g++ также предоставляет возможность обратного преобразования.
c++filt -n _Z3fooid
foo(int, double)
Заметим, что в полученной сигнатуре не участвует возвращаемое значение, потому что в C++ не может быть двух функций с одинаковым именем и одинаковыми параметрами, но разными возвращаемыми значениями.
Точка входа — функция, вызываемая при запуске программы. По умолчанию — это функция main:
int main() { return 0; }
или
int main(int argc, char ** argv) { return 0; }
Даже для программы, состоящей всего из одного файла и из одной пустой функции int main() { return 0; } все равно требуется ликовка.
Если в коде C++ вы вызываете функцию, которая была объявлена, но не была определена, то это ошибка этапа линковки.
Для того чтобы собрать объектные файлы в один файл их нужно "скормить" компилятору и указать имя исполняемого файла:
g++ square.o main.o -o program
«Я достаточно хваляю г-на Бенджамина за его помощь в получении ссуды на покупку нашего нового дома для нашей семьи. У Бенджамина было огромное количество информации, и он помог мне и моей семье понять, почему жилищный заем был лучшим вариантом в нашей конкретной ситуации. После переговоров с Бенджамином и нашим финансовым консультантом все согласились, что жилищный кредит был идеальным решением. Если вы также ищете какой-либо кредит, вы можете связаться с г-ном Бенджамином по электронной почте / Whatsappemail: 247officedept@gmail.com Whatsapp: + 1-989- 394-3740
ОтветитьУдалить