8. Разобрать /
Информационные технологии /
Разработка ПО /
Язык программирования C++ /
Жизненный цикл программы на C++
Обязательно содержит три этапа:
- кодирование программы;
- сборка программы;
- запуск программы.
Необязательными этапами могут быть:
- отладка программы;
- тестирование программы;
- упаковка программы;
- документирование программы.
1. Кодирование программы
Это
- обязательное написание исходного кода программы на языке C++ в редакторе или IDE1,
- который сохранятся в текстовых файлах с расширением
*.cpp
.
Необязательным результатом кодирования могут является
- заголовочные текстовые файлы
*.h
, - текстовые файлы других расширений необходимые для сборки программы,
- файлы ресурсов, например изображения, необходимые для работы программы.
Пример исходного кода программы на C++ в файле program.cpp
.
#include <iostream>
int main() {
std::cout << "Hello, World!" << std::endl;
return 0;
}
2. Сборка программы
Это
- последовательное преобразование исходного кода программы в машинный код, который может быть
- исполняемым/запускаемым файлом (binary) с расширением
*.exe
или без расширения, - статической библиотекой (static library) с расширением
*.a
(архив которой можно просмотреть командойar -t
) или*.lib
подключаемой на этапе компоновки (linking) программы - или динамической библиотекой (shared library) с расширением
*.so
или*.dll
подключаемой на этапе выполнения (runtime) программы.
- исполняемым/запускаемым файлом (binary) с расширением
Состав компилятора g++
cpp
- препроцессор (preprocessor);as
- ассемблер (assembler);g++
- компилятор (compiler);ld
- компоновщик (linker).
Эти программы можно использовать явно, но рекомендуются вызывать неявно через g++
.
Зарезервированные расширения файлов
*.cpp
,*.cxx
- исходный код программы на языке C++;*.h
,*.hxx
- заголовочный файл программы на языке C++;*.ii
- исходный код программы после препроцессинга или не нуждающийся в препроцессинге на языке C++;*.s
- исходный код программы на языке Ассемблер;*.o
,*.obj
- машинный код (объектный файл).
Преобразование исходного кода в машинный
Зачастую преобразования можно выполнять как конкретными программами (cpp
, as
, ld
) так и при помощи обёртки - g++
. Далее, в двух колонках таблицы указываются альтернативные варианты.
Некоторые полезные параметры g++
:
-o file
- указывает название файлаfile
для сохранения результата;-v
- выводит дополнительную информацию во время работы;-Wall
- показывает все предупреждения по исходному коду;-Wextra
- показывает дополнительные предупреждения по исходному коду;-Wpedantic
- предупреждает о несоответствии исходного кода стандарту ISO C++;-std=c++20
- позволяет использовать возможности C++20;-O0
,-O1
,-O2
,-O3
,-Os
,-Ofast
,-Og
,-Oz
- включает различные уровни оптимизации программы, например,-Os
создаёт исполняемый файл минимального размера;-x=c++
- явно требует использовать тот или иной язык программирования и его особенности, некоторые полезные значения:c++
,c++-header
,c++-system-header
иc++-user-header
;-g
- добавляет информацию для последующей отладки, например в программе GNU Debugger;--strip-all
- исключает всю таблицу символов из исполняемого файла;-D MACRO=VALUE
- определяет макросMACRO
со значениемVALUE
;-I path/to/include
- указывает путь к директорииinclude
с заголовочными файлами;-L path/to/lib
- указывает путь к директорииlib
с внешними библиотеками;-l library
- указывает названиеlibrary
подключаемой внешней библиотеки;-fmodules-ts
- включает поддержку модулей C++20.
Обычно, для сборки программы из исходного кода в исполняемый достаточно одной команды.
g++ -o program program.cpp
Но мы в учебных целях разберём каждый шаг. Всего их четыре:
- Препроцессинг;
- Компиляция;
- Ассемблирование;
- Компоновка.
2.1 Препроцессинг
Шаг предварительной обработки заменяет в исходном коде макросы вида #include
, #define
и прочие #...
на конкретные значения. Например, вместо #include <iostream>
вставляется исходный код библиотеки для работы с вводом и выводом.
cpp |
g++ |
---|---|
$ cpp program.cpp > program.ii |
$ g++ -E -o program.ii program.cpp |
Где,
-E
- останавливает обработку после препроцессинга.
Пример program.ii
.
# 0 "program.cpp"
# 0 "<built-in>"
# 0 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 0 "<command-line>" 2
# 1 "program.cpp"
# 1 "/usr/include/c++/12/iostream" 1 3
# 36 "/usr/include/c++/12/iostream" 3
...
# 3 "program.cpp"
int main() {
std::cout << "Hello, World!" << std::endl;
return 0;
}
2.2 Компиляция
Шаг компиляции преобразует исходный код на языке C++ в исходный код на языке Ассемблер.
g++ |
---|
$ g++ -S -o program.s program.ii |
Где,
-S
- останавливает обработку после преобразования в исходный код на языке Ассемблер.
Пример program.s
.
.file "program.cpp"
.text
.local _ZStL8__ioinit
.comm _ZStL8__ioinit,1,1
.section .rodata
.LC0:
.string "Hello, World!"
.text
.globl main
.type main, @function
main:
.LFB1761:
.cfi_startproc
pushq %rbp
...
2.3 Ассемблирование
Шаг ассемблирования преобразует исходный код на языке Ассемблер в машинный код (объектный файл).
as |
g++ |
---|---|
$ as -o program.o program.s |
$ g++ -c -o program.o program.s |
Где,
-c
- останавливает обработку после преобразования в машинный код.
Пример машинного кода после дизассемблирования файла program.o
можно посмотреть командой objdump --disassemble
.
$ objdump --disassemble program.o
...
Disassembly of section .text:
0000000000000000 <main>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 48 8d 05 00 00 00 00 lea 0x0(%rip),%rax # b <main+0xb>
b: 48 89 c6 mov %rax,%rsi
e: 48 8d 05 00 00 00 00 lea 0x0(%rip),%rax # 15 <main+0x15>
15: 48 89 c7 mov %rax,%rdi
18: e8 00 00 00 00 call 1d <main+0x1d>
1d: 48 8b 15 00 00 00 00 mov 0x0(%rip),%rdx # 24 <main+0x24>
24: 48 89 d6 mov %rdx,%rsi
27: 48 89 c7 mov %rax,%rdi
2a: e8 00 00 00 00 call 2f <main+0x2f>
2f: b8 00 00 00 00 mov $0x0,%eax
34: 5d pop %rbp
35: c3 ret
...
Таблицу символов в объектном файле можно посмотреть командой nm
.
$ nm program.o
U __cxa_atexit
U __dso_handle
U _GLOBAL_OFFSET_TABLE_
0000000000000088 t _GLOBAL__sub_I_main
0000000000000000 T main
0000000000000036 t _Z41__static_initialization_and_destruction_0ii
U _ZNSolsEPFRSoS_E
U _ZNSt8ios_base4InitC1Ev
U _ZNSt8ios_base4InitD1Ev
U _ZSt4cout
U _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
0000000000000000 b _ZStL8__ioinit
U _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
Все эти странные символы вокруг имён методов описывают их сигнатуру в специальном формате.
2.4 Компоновка
Шаг компоновки связывает несколько файлов машинного кода в единую программу.
ld |
g++ |
---|---|
См. ниже | $ g++ -o program program.s |
ld \
--dynamic-linker=/lib64/ld-linux-x86-64.so.2 \
-o program \
program.o \
-L /usr/lib/gcc/x86_64-linux-gnu/12 \
-l stdc++ -l c \
/usr/lib/x86_64-linux-gnu/Scrt1.o \
/usr/lib/x86_64-linux-gnu/crti.o \
/usr/lib/gcc/x86_64-linux-gnu/12/crtbeginS.o \
/usr/lib/gcc/x86_64-linux-gnu/12/crtendS.o \
/usr/lib/x86_64-linux-gnu/crtn.o
Обратим внимание, что компоновка при помощи g++
намного лаконичнее, чем ld
. Дело в том, что g++
скрывает от пользователя внутреннюю кухню связанную с подключением стандартной библиотеки.
Как же я узнал, как параметризовать программу ld
? Для этого собрал исполняемый файл командой g++ -v -o program program.cpp
с параметром -v
и там подсмотрел параметры связывания.
Командой ldd
можно посмотреть динамически подключаемые библиотеки, от которых зависит программа.
$ ldd program
linux-vdso.so.1 (0x00007ffc33ea7000)
libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007ff1e3800000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff1e361f000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007ff1e353f000)
/lib64/ld-linux-x86-64.so.2 (0x00007ff1e3a53000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007ff1e351f000)
3. Запуск программы
Это загрузка скомпонованного машинного кода (исполняемого файла) программы в память компьютера и его запуск.
Пример запуска программы program
.
$ ./program
Hello, World!
Ресурсы
- Препроцессор C и C++ на ru.ruwiki.ru;
- Компилятор на ru.ruwiki.ru;
- Язык ассемблера на ru.ruwiki.ru;
- Компоновщик на ru.ruwiki.ru;
- Загрузчик программ на ru.ruwiki.ru;
- Процесс компиляции программ на C++ на habr.com;
- Сборка проектов Си и Си++: от простого к сложному. Часть I. Библиотеки на habr.com;
- Сборка проектов Си и Си++: от простого к сложному. Часть II. Сборщики на habr.com;
- Separate Compilation на hackingcpp.com;
- GCC and Make: Compiling, Linking and Building C/C++ Applications на www3.ntu.edu.sg.
IDE - интегрированная среда разработки, которая упрощает решение задач разработки↩︎