Компиляция и исполнение кода на C++


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



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

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

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

Плюсы и минусы компилируемости в машинный код

Плюсы:
  • эффективность: программа компилируется и оптимизируется для конкретного процессора;
  • нет необходимости устанавливать сторонние приложения, такие как интерпретатор или виртуальная машина (т.е. для запуска программы, написаной на компилируемом языке, не требуется установка компилятора).
Минусы:
  • нужно компилировать для каждой платформы (т.е. программу, написанную на языке, который компилируется в машинный код, недостаточно скомпилировать однажды чтобы её можно было запускать на любой платформе);
  • сложность внесения изменения в программу — нужно перекомпилировать заново.

Общая схема



g++ - это такая обёртка над несколькими программами:
  1. над препроцессором;
  2. над непосредственно компилятором;
  3. и над линковщиком.
g++ может сама решать, что вызывать, если не просить что-то делать специально, а просто изначально дать ей файлы, то g++ сама догадается что с ними нужно сделать.
g++ main.cpp square.cpp -o program

Этап 1: препроцессор

Язык препроцессора – это специальный язык программирования, встроенный в C++. Препроцессор работает с кодом на C++ как с текстом.

Команды языка препроцессора называют директивами, все директивы начинаются со знака #. Директива #include позволяет подключать заголовочные файлы к файлам кода.
  • #include <foo.h> — библиотечный заголовочный файл,
  • #include "bar.h" — локальный заголовочный файл.
Препроцессор заменяет директиву #include "bar.h" на содержимое файла bar.h.

Можно попросить компилятор вызвать только препроцессор и посмотреть что получится. Для компилятора g++ можно использовать ключ -E.

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

На вход компилятору поступает код на C++ после обработки препроцессором.

Каждый файл с кодом компилируется отдельно и независимо от других файлов с кодом. Компилируется только файлы с кодом (т.е. *.cpp).

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

На выходе компилятора из каждого файла с кодом получается “объектный файл” — бинарный файл со скомпилированным кодом (с расширением .o или .obj).

Если в коде C++ вы вызывает не объявленную функцию, то это ошибка этапа компиляции.

Можно "скормить" файлы с кодом непосредственно компилятору. Для компилятора g++ можно использовать ключ -c.
На выходе получается файлы с расширением .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

1 комментарий:

  1. «Я достаточно хваляю г-на Бенджамина за его помощь в получении ссуды на покупку нашего нового дома для нашей семьи. У Бенджамина было огромное количество информации, и он помог мне и моей семье понять, почему жилищный заем был лучшим вариантом в нашей конкретной ситуации. После переговоров с Бенджамином и нашим финансовым консультантом все согласились, что жилищный кредит был идеальным решением. Если вы также ищете какой-либо кредит, вы можете связаться с г-ном Бенджамином по электронной почте / Whatsappemail: 247officedept@gmail.com Whatsapp: + 1-989- 394-3740

    ОтветитьУдалить