Сразу оговорюсь, это не учебник и т.п. !!!
Необходимы базовые знания языка c++
В данном гайде будут лишь некоторые примеры работы с языком C и C++ в unix подобных системах
Установка компилятора для centos\debian\freebsd:
# yum -y install gcc gcc-c++
# apt-get install gcc gcc++
# pkg install gcc
Проверка версии:
# gcc -v
================================================================================================ Классическая первая программа: hello.cpp:
# g++ hello.cpp -o hello
# ./hello Hello, World!
В программе использовалась библиотека стандартного вывода <iostream> namespace std - избавляет от необходимости указания префикса имен, который соответствует стандартной библиотеке
Т.е. в строке cout << "Hello, World!\n"; необходимо было бы указать std::cout << "Hello, World!\n";
================================================================================================ Азы языка, циклы while и do while, условие if, выход из цикла break:
cycles.cpp:
# g++ cycles.cpp -o cycles
# ./cycles while: 5 while: 10 while: 20 in if: 20 do while: 5
================================================================================================ Программа, состоящая из нескольких файлов:
Создадим 3 файла: main.cpp, body.cpp, body.hpp main.cpp - точка входа, вызывает функцию из файла body.cpp body.cpp - описывает функцию с логикой body.hpp - объявляет интерфейс, функцию из файла body.cpp, подключается в main.cpp
Скомпилируем их и запустим свою первую программу
main.cpp:
body.cpp:
body.hpp:
# g++ body.cpp main.cpp -o utility
# ./utility 6
================================================================================================ Компиляция, линковка, запуск программы g++ - обертка над предпроцессором, компилятором и линковщиком
1й этап компиляции:
Пример компиляции файлов с исходным кодом в файлы предпроцессора:
# g++ -E body.cpp -o body_preprocessed.cpp
# g++ -E main.cpp -o main_preprocessed.cpp
2й этап компиляции:
Пример компиляции файлов с исходным кодом в файлы objective (в бинарном виде)
Скомпилируем файлы с исходным кодом:
# g++ -c body.cpp -o body.o
# g++ -c main.cpp -o main.o
3й этап компиляции (с линковкой в единый исполняемый модуль):
Создадим программу из бинарных файлов и запустим
# g++ body.o main.o -o utility
# ./utility 6
Показать файл в виде ассемблера:
# g++ -S body.cpp && cat body.s
# g++ -S main.cpp && cat main.s
В выводе мы увидим закодированное название нашей функцмм func:
call _Z4funci _Z - префикс 4 - количество символов в названии функции func - имя i - первая буква от параметра integer
Вывести начальную сигнатуру функции можно так:
# c++filt -n _Z4funci func(int)
================================================================================================ Создание динамической библиотеки пример 1 (gcc для языка С)
Динамическая библиотека - подключаеется к программе в момент выполнения
При создании библиотеки производится не только ее компиляция, но и линковка с нужными ей библиотеками
Имеем немного переделанный код нашей предыдущей программы
body.h:
body.c:
main.c:
Наименование состоит из lib + имя библиотеки + .so
На примере нашей разделенной программы создадим библиотеку
-shared - указывает создать динамическую (разделяемую) библиотеку
Для х32:
# gcc -o libBody.so -shared body.c
Для х64:
# gcc -o libBody.so -shared -fPIC body.c
Скомпилируем утилиту, ссылающуюся на созданную нами библиотеку
Для х32:
# gcc main.c -L. -lBody -o utility
Для х64:
# gcc main.c -fPIC -L. -lBody -o utility
Где: -L. - путь, где искать библиотеки (т.е. текущий каталог) -lbody - ключ + имя библиотеки исключая префикс и расширение
Если мы запустим нашу утилиту - ./utility, то она выдаст ошибку: Shared object "libBody.so" not found, required by "utility"
Это происходит потому, что сервис поиска библиотек ld не знает пути к нашему каталогу
Можно поместить библиотеку в стандартный каталог для библиотек (/lib или /usr/lib)
Или добавить наш каталог в переменную окружения:
# bash
# export LD_LIBRARY_PATH=.
# ./utility Hello from func!
Используем полученный core дамп-файл:
# gdb utility utility.core GNU gdb 6.1.1 [FreeBSD] Copyright 2004 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "amd64-marcel-freebsd"...(no debugging symbols found)... Core was generated by `./utility'. Program terminated with signal 11, Segmentation fault. Reading symbols from ./libBody.so...(no debugging symbols found)...done. Loaded symbols for ./libBody.so Reading symbols from /lib/libc.so.7...(no debugging symbols found)...done. Loaded symbols for /lib/libc.so.7 Reading symbols from /libexec/ld-elf.so.1...(no debugging symbols found)...done. Loaded symbols for /libexec/ld-elf.so.1 #0 0x0000000800b7b1bf in strlen () from /lib/libc.so.7 (gdb)
Какие имена обращения доступны в нашей библиотеке:
# nm libBody.so
Видим имя функции "func" для обращения:
... 00000000000004e5 T func
...
Формат вывода можно вывести так:
# man nm
Где - T - тип "текст программы"
Какие библиотеки использует программа:
# ldd ./utility ./utility: libBody.so => ./libBody.so (0x800821000) libc.so.7 => /lib/libc.so.7 (0x800a22000)
================================================================================================ Создание динамической библиотеки пример 2 (g++ для языка С++)
Утилита make читает специальный файл, в котором описаны зависимости
В фале описывается:
Зависимый файл или объект, далее :, далее имена объектов, от которых он зависит
Следующая строка - табуляция и команда, которая из списка зависимостей делает файл из левой части
Пример make файла по нашей схеме зависимостей
Makefile:
Т.е.
Цель all зависит от bin lib
Цель bin зависит от body.hmain.c lib, собирает исполняемый файл
Цель lib зависит от body.h body.cpp, собирает файл библиотеки
Цель clean не имеет зависимостей, очищает указанные файлы, - заставляет не учитывать результат команды
Файлы изначально:
# ls Makefile body.cpp body.h main.c
По умолчанию отрабатывает цель all:
# make g++ -fPIC -c body.cpp -o body.o && g++ -shared -o libBody.so -fPIC body.o g++ main.c -fPIC -L. -lBody -o utility
Проверим цель clean:
# make clean rm utility libBody.so *.o 2>/dev/null
Файлы после очистки:
# ls Makefile body.cpp body.h main.c
================================================================================================ Работа с файлами
Типы работы с файлами: ifstream - чтение файла ofstream - запись в файл fstream - запись и чтение
Для операций выше необходимо открыть файл с помощью функции open ( open(path) или open(path, flags) ): int open(const char *path, int flags, ...));
Где: path - имя или полный путь до файла flags - флаги
Возвращает файловый дескриптор (неотрицательное целое число), который необходим для работы с файлом
Возможные режимы открытия файла: ios::in: - ввод (чтение) ( только для объектов ifstream или fstream ) ios::out: - вывод (запись) ( старые данные удаляются, только для объектов ofstream или fstream ) ios::app: - дозапись ( старые данные не удаляются ) ios::ate: - указатель переходит в конец файла ios::trunc: - файл усекается при открытии, может быть установлен с режимом out ios::binary: - откроется в бинарном режиме
По дефолту предполагаются режимы: ifstream - ios::in ofstream - ios::out fstream - ios::out и ios::in
Проверить, окрыт ли файл - is_open(), если открыт - вернет true: std::ifstream in1; in1.open("file.txt"); if ( in1.is_open() ) { }
Примеры
Поток для записи, окрытие файла для записи: std::ofstream out1; out.open("file.txt");
Поток для записи, окрытие файла для дозаписи: std::ofstream out2; out2.open("file.txt", std::ios::app);
Вариант с помощью вызова конструктора с записью: std::ofstream out3("file.txt");
Поток для чтения, окрытие файла для чтения: std::ifstream in1; in.open("file.txt");
Вариант с помощью вызова конструктора с чтением: std::ifstream in2("file.txt");
Вариант с помощью вызова конструктора с дозаписью: std::fstream fs("file.txt", std::ios::app);
По окончанию работы необходимо закрыть файл с помощью функции close()
Напишем программу с примерами записи и чтения файлов
file.cpp:
Скомпилируем и запустим:
# g++ file.cpp -o file
# ./file Line in the sand Double trouble Line in the sand Double trouble
================================================================================================ Что на самом деле происходит при работе программы
Есть пара утилит, которые могут детально паказать вызовы и подноготную работы программ
В Linux можно воспользоваться утилитами:
Для трассировки библиотечных вызовов
ltrace
Для перехвата и записи системных вызовов, выполненяемых процессом
strace
В FreeBSD можно воспользоваться утилитами:
Для трассировки библиотечных вызовов
pkg install ltrace
Отобразить системные вызовы команды, запущенного процесса
truss
Пример запуска утилит: hello.cpp:
# g++ hello.cpp -o hello
# ./hello Hello, World!
Некоторые опции ltrace
-c
Время графа и призывы к каждой библиотеке называют и сообщают о резюме на выходе программы
-C, - demangle
Расшифруйте (demangle) имена символа низкого уровня на имена уровня пользователя
Помимо удаления любой начальной буквы подчеркивают префикс, используемый системой, делает имена функции C удобочитаемыми
-c
Не показывает отдельные системные вызовы или сигналы
Вместо этого перед переходом напечатает резюме, содержащее для каждого системного вызова:
Полное системное используемое время, вызовы, и количество вызовов с возвратом ошибки
# truss ./hello
# truss -c ./hello
================================================================================================ Что на самом деле происходит при компиляции
Отладочную информацию gcc при компиляции можно вывести с помощью уровней от 0 до 3 Примеры работы с gcc для программ на с:
# gcc -g3 hello.c -o hello
# gcc -ggdb hello.c -o hello
С core файлом мы встречались ранее, чтоб его создавать, нужно применить уже знакомую команду:
# ulimit -c unlimited
Сломаем нашу программу hello.cpp:
Пару слов о printf: %d - Десятичное число целого типа со знаком
Компилируем:
# g++ hello.cpp -o hello
Получаем ошибку при выводе:
# ./hello Floating point exception (core dumped)
Появился файл дампа - hello.core
Проведем дебаг (Видим Arithmetic exception в print_arg ()):
# gdb ./hello hello.core GNU gdb 6.1.1 [FreeBSD] Copyright 2004 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "amd64-marcel-freebsd"...(no debugging symbols found)... Core was generated by `./hello'. Program terminated with signal 8, Arithmetic exception. Reading symbols from /usr/local/lib/gcc8/libstdc++.so.6...(no debugging symbols found)...done. Loaded symbols for /usr/local/lib/gcc8/libstdc++.so.6 Reading symbols from /lib/libm.so.5...(no debugging symbols found)...done. Loaded symbols for /lib/libm.so.5 Reading symbols from /lib/libgcc_s.so.1...(no debugging symbols found)...done. Loaded symbols for /lib/libgcc_s.so.1 Reading symbols from /lib/libc.so.7...(no debugging symbols found)...done. Loaded symbols for /lib/libc.so.7 Reading symbols from /libexec/ld-elf.so.1...(no debugging symbols found)...done. Loaded symbols for /libexec/ld-elf.so.1 #0 0x000000000040080a in print_arg ()
Пример дебага с точкой останова в функции:
# gdb ./hello GNU gdb 6.1.1 [FreeBSD] Copyright 2004 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "amd64-marcel-freebsd"...(no debugging symbols found)...
Делаем точку останова в бажной функции с именем print_arg:
# (gdb) b print_arg Breakpoint 1 at 0x4007f9
# (gdb) info breakpoints Num Type Disp Enb Address What 1 breakpoint keep y 0x00000000004007f9 <print_arg(int)+4>
Выполним - r (т.е. - run):
# (gdb) r Starting program: /root/test/hello (no debugging symbols found)...(no debugging symbols found)...(no debugging symbols found)... Breakpoint 1, 0x00000000004007f9 in print_arg ()
Если выйдет ошибка - Dwarf Error: wrong version in compilation unit header (is 4, should be 2) Дайте команду (определенная версия DWARF 2):
# g++ -g -gdwarf-2 hello.cpp -o hello
# gdb ./hello
# (gdb) b print_arg
# (gdb) r Starting program: /root/test/hello