Одним із інструментів створення програм є компілятором GCC. Спочатку ця абревіатура розшифровується,
пяк GNU C Compiler. Зараз вона означає - GNU Compiler Collection.
пРассмотрим приклад створення програми «Hello world!» - «Привіт Світ!».
Файли з текстами програм, які ми будемо створювати, це звичайні текстові файли, та
створювати їх можна за допомогою будь-якого текстового редактора (наприклад GEdit KWrite, Kate, а також більш традиційні для користувачів Linux
- vi і emacs). Крім текстових редакторів, існують спеціалізовані середовища розробки зі своїми вбудованими редакторами.
пОдним з таких засобів є KDevelop. Цікаво, що в ньому є вбудований редактор і вбудована консоль,
прасположенная прямо під редактором. Так що можна прямо в одній програмі, не перемикаючись між вікнами, та редагувати код і давати
пконсольные команди.
пДля проби можете створити окремий каталог hello і в ньому - текстовий файл hello.c з наступним текстом:
#include <stdio.h>
int void main ()
{
printf("Hello world!\n");
return(0);
}
Потім в консолі зайдіть в каталог проекту. Наберіть команду
gcc hello.c
У каталозі з'явився новий файл a.out. Це і є виконуваний файл. Запустимо його. Наберіть в консолі:
/.a.out
Програма повинна запуститися, тобто повинен з'явитися текст:
Hello world!
Компілятор gcc за замовчуванням присвоює всім створеним виконуваним файлів ім'я a.out.
Якщо хочете назвати його по-іншому, потрібно до команди на компіляцію додати прапор-o та ім'я, яким ви хочете його назвати.
gcc hello.c-o hello
У каталозі з'явиться виконуваний файл з назвою hello. Для його запуску наберіть в консолі:
/.hello
Прапор-o є лише одним з численних прапорів компілятора gcc. Деякі інші прапори були наведені раніше, деякі ми розглянемо
пізніше.
Щоб переглянути всі можливі прапори, можна скористатися довідковою системою man. Введіть у командному рядку:
man gcc
Перед вами постане довідкова система по цій програмі. Вихід з довідкової системи здійснюється за допомогою клавіші q.
пТочку і слеш перед назвою виконуваного файлу означає шлях до файлу, тобто файл знаходиться в поточному каталозі.
пнатисніть запустити програму, яка знаходиться в іншому місці, треба прописати повний шлях до неї, наприклад так:
/home/user/projects/hello/hello
Або інший варіант: прописати шлях відносно поточного каталогу, в якому ви в даній момент знаходитесь в консолі.
При цьому одна точка поточний каталог, дві точки - батьківський. Наприклад, команда ./hello запускає програму hello,
що знаходиться в поточному каталозі, команда ../hello - програму hello, що знаходиться в батьківському каталозі.
пякщо наберать тільки назва виконуваного файлу, операційна система буде
пискать його в каталогах /usr/bin /usr/local/bin, і, природно, не знайде.
пКаталоги /usr/bin /usr/local/bin - системні каталоги розміщення виконуваних програм.
Перший з них призначений для розміщення стабільних версій програм, як правило, входять у дистрибутив Linux.
Другий - для програм, встановлених самим користувачем (за стабільність яких ніхто не ручається).
Така розбиття потрібно,щоб відділити їх один від одного. За замовчуванням при складанні програми встановлюються в каталог /usr/local/bin.
Вкрай небажано поміщати що-або зайве в /usr/bin або видаляти що-то звідти вручну, тому що це може призвести до краху системи.
Там повинні розміщуватися програми, за стабільність яких відповідають розробники дистрибутиву.
пТакже є можливість додавати в список системних шляхів шляху до своїх програм.
пДля цього треба додати новий шлях в системну змінну оточення PATH.
Розглянемо, що ж робить програма gcc
Робота gcc включає три етапи: обробка препроцессором, компіляція та компонування (або лінкування).
Препроцесор включає в основний файл вміст всіх заголовних файлів, вказаних у директивах #include.
пВ заголовних файлах зазвичай знаходяться оголошення функцій, що використовуються у програмі, але не визначених у тексті програми.
піх визначення знаходяться десь в іншому місці або в інших файлах з вихідним кодом або в бінарних бібліотеках.
пДля того, щоб подивитися, що на цьому етапі робиться, скористаємося опцією-E. Ця опція зупиняє виконання програми на етапі
побработки препроцессором. У результаті виходить файл вихідного коду з включеним у нього вмістом заголовних файлів.
пВ прикладі hello.c підключається один файл заголовків на мові - stdio.h - колекція стандартних функцій введення-виведення.
Введіть наступну команду:
gcc-E hello.c-o hello.cpp
пПолученному файлу ми дали ім'я hello.cpp. Відкривши його, ви побачите, що він досить довгий. Це тому, що в нього ввійшов увесь
пкод файлу заголовків stdio.h. Крім того, препроцесор сюди додав деякі теги, які вказують компілятору спосіб зв'язку
пс оголошеними функціями. Основний текст нашої програми видно тільки в самому низу.
пви можете заодно подивитися, які ще функції оголошені в заголовочном файлі stdio.h. Якщо вам захочеться отримати інформацію про яку-небудь
пфункции, можна поцікавитися про неї у вбудованому керівництві man.
пНапример, якщо вам раптом захочеться дізнатися, що ж робить функція fopen, можна набрати:
man fopen
info fopen
Друга стадія - компіляція. Вона полягає в перетворенні тексту програми на мові C/C++ в набір машинних команд.
пРезультат зберігається в об'єктному файлі. Зрозуміло, на машинах з різною архітектурою процесора двійкові файли виходять в різних
пформатах, і на одній машині неможливо запустити бінарник, зібраний на іншій машині (хіба тільки, якщо у них однакова архітектура
ппроцессора і однакові операційні системи). Ось чому програми для UNIX-подібних систем поширюються у вигляді вихідних кодів:
поні повинні бути доступні всім користувачам, незалежно від того, у кого який процесор і яка операційна система.
пОбъектный файл являє собою «dostac» переклад нашого програмного коду на машинний мову,
ппока без зв'язку викликаних функцій з їх визначеннями. Для формування об'єктного файлу служить опція-c.r
gcc-c hello.c
Назва одержуваного об'єктного файлу можна не вказувати, так як компілятор просто бере назва початкового і змінює розширення .c на .o
(вказати можна, якщо нам захочеться назвати його по-іншому).
пякщо ми створюємо об'єктний файл з джерела, вже обробленого препроцессором (наприклад, такого, який ми отримали вище),
пто ми повинні обов'язково вказати очевидно, що компільований файл є файлом вихідного коду, оброблений препроцессором,
пі має теги препроцесора. В іншому випадку він буде оброблятися, як звичайний файл C, без урахування тегів препроцесора,
а значить зв'язок з оголошеними функціями не буде встановлюватися.
Для явного вказування на формат та мову оброблюваного файлу служить опція-x. Файл C, оброблений препроцессором позначається cpp-output.
gcc-x cpp-output-c hello.cpp
Остання стадія - компонування. Вона полягає у зв'язуванні всіх об'єктних файлів проекту в один, зв'язуванні викликів функцій
пс їх визначеннями, і приєднанням до бібліотечних файлів, що містять функції, які викликаються, але не визначені в проекті.
пВ результаті формується виконуваний файл - наша кінцева мета. Якщо якась функція програми використовується,
пно компонувальник не знайде місце, де ця функція визначена, він видасть повідомлення про помилку, і відмовиться створювати виконуваний файл.
пДля отримання з об'єктного файлу виконуваного використовується опція-o:
gcc hello.o-o helo
Отриманий виконуваний файл можна запускати:
/.hello
Ви запитаєте: «Навіщо вся ця метушня з проміжними етапами?
пНе чи краще просто один раз скомандувати
gcc kalkul.c-o kalkul?
Справа в тому, що ці програми дуже рідко складаються з одного файлу.
Як правило вихідних файлів кілька, і вони об'єднані в проект. І в деяких виняткових випадках програму доводиться
компонувати з декількох частин, написаних на різних мови. В цьому випадку доводиться запускати компілятори різних мов,
щоб кожен отримав об'єктний файл зі свого джерела, а потім вже ці отримані об'єктні файли компонувати в виконувану програму.
Приклад проекту з декількох файлів
Розглянемо програму, що складається з двох вихідних файлів та одного заголовків.
пДля цього візьмемо як приклад примитивнй калькулятор, здатний додавати, віднімати, множити і ділити.
пякщо запуску він буде запитувати два числа, над якими слід провести дію, та
пзнак арифметичного дії. Це можуть бути дії: «+», «-», «*», «/», pow, sqrt, sin, cos, tan.
після цього програма виводить результат і зупиняється (повертає нас в операційну систему, а точніше - в командний інтерпретатор,
з якого ми програму і викликали).
При цьому після введення першого числа треба відразу вводити в дію. Якщо дія оперує тільки з одним числом
(як у випадку синуса, косінуса, тангенса, квадратного кореня), результат відразу буде виведено.
пякщо знадобиться друге число, воно буде спеціально запитуватися
пСоздадим каталог проекту kalkul2. У ньому створимо три файлу: calculate.h, calculate.c, main.c.
Файл calculate.h:
///////////////////////////////////////
// calculate.h
#ifndef CALCULATE_H_
#define CALCULATE_H_
float Calculate(float Numeral, char Operation[4]);
#endif /*CALCULATE_H_*/
Файл calculate.c:
////////////////////////////////////
// calculate.c
#include <stdio.h>
#include <math.h>
#include <string.h>
#include "calculate.h"
float Calculate(float Numeral, char Operation[4])
{
float SecondNumeral;
if(strncmp(Operation, "+", 1) == 0)
{
printf("Другий доданок: ");
scanf("%f",&SecondNumeral);
return(Numeral + SecondNumeral);
}
else if(strncmp(Operation, "-", 1) == 0)
{
printf("що віднімається: ");
scanf("%f",&SecondNumeral);
return(Numeral - SecondNumeral);
}
else if(strncmp(Operation, "*", 1) == 0)
{
printf("Множник: ");
scanf("%f",&SecondNumeral);
return(Numeral * SecondNumeral);
}
else if(strncmp(Operation, "/", 1) == 0)
{
printf("Дільник: ");
scanf("%f",&SecondNumeral);
if(SecondNumeral == 0)
{
printf("Помилка: ділення на нуль! ");
return(HUGE_VAL);
}
else
return(Numeral / SecondNumeral);
}
else if(strncmp(Operation, "pow", 3) == 0)
{
printf("Ступінь: ");
scanf("%f",&SecondNumeral);
return(pow(Numeral, SecondNumeral));
}
else if(strncmp(Operation, "sqrt", 4) == 0)
return(sqrt(Numeral));
else if(strncmp(Operation, "sin", 3) == 0)
return(sin(Numeral));
else if(strncmp(Operation, "cos", 3) == 0)
return(cos(Numeral));
else if(strncmp(Operation, "tan", 3) == 0)
return(tan(Numeral));
else
{
printf("Неправильно введено в дію ");
return(HUGE_VAL);
}
}
Файл main.c:
////////////////////////////////////////
// main.c
#include <stdio.h>
#include "calculate.h"
int void main ()
{
float Numeral;
char Operation[4];
float Result;
printf("Число: ");
scanf("%f",&Numeral);
printf("Арифметичне дію (+,-,*,/,pow,sqrt,sin,cos,tan): ");
scanf("%s",&Operation);
Calculate Result = (Numeral, Operation);
printf("%6.2f\n",Result);
return 0;
}
пУ нас є два файлу вихідного коду (c-файли) і один заголовний (h-файл). Заголовний включається в обидва-c файлу.
пСкомпилируем calculate.c.
gcc-c calculate.c
пПолучили calculate.o. Потім main.c.
gcc-c main.c
пі ось він main.o перед нами! Тепер, як ви вже, напевно, підказує інтуїція, треба з цих двох об'єктних файлів зробити виконуваний.
gcc calculate.o main.o-o kalkul
Упс... і не вийшло... Замість настільки бажаного запускається файлу, в консолі з'явилася якась лайку:
calculate.o(.text+0x1b5): In function `Calculate":
calculate.c: undefined reference to `pow"
calculate.o(.text+0x21e):calculate.c: undefined reference to `sqrt"
calculate.o(.text+0x274):calculate.c: undefined reference to `sin"
calculate.o(.text+0x2c4):calculate.c: undefined reference to `cos"
calculate.o(.text+0x311):calculate.c: undefined reference to `tan"
collect2: ld returned 1 exit status
Давайте розберемося, за що нас так вилаяли. Undefined reference означає посилання на функцію, яка не визначена.
В даному випадку gcc не знайшов визначення функцій pow, sqrt, sin, cos, tan. Де ж їх знайти?
Як вже говорилося раніше, визначення функцій можуть перебувати в бібліотеках.
Це скомпільовані двійкові файли, що містять колекції однотипних операцій, які часто викликаються з багатьох програм,
а тому немає сенсу багаторазово писати їх код в програмах. Стандартне розташування файлів бібліотек - каталоги /usr/lib і
/usr/local/lib (при бажанні можна додати шлях). Якщо бібліотечний файл має розширення .a, то це статична бібліотека,
тобто при компонуванні весь її двійковий код включається у виконуваний файл. Якщо розширення .so, то це динамічна бібліотека.
Це значить у виконуваний файл програми поміщається тільки посилання на бібліотечний файл, а вже з нього і запускається функція.
пКогда ми писали програму hello, ми використовували функцію printf для виведення текстової рядка. Однак, як ви пам'ятаєте, ми ніде не
пписали визначення цієї функції. Звідки ж тоді вона викликається?
пПросто при компонуванні будь-якої програми компілятор gcc за замовчуванням включає в запускається файл бібліотеки libc.
це стандартна бібліотека мови C. Вона містить стандартні функції, необхідні абсолютно у всіх програмах,
пнаписанных на C, у тому числі і функцію printf. Оскільки бібліотека libc потрібна у всіх програмах, вона включена за замовчуванням,
пбез необхідності давати окреме вказівка на її включення.
пОстальные бібліотеки треба вимагати включати явно. Адже не можна ж у всі програми поміщати абсолютно всі бібліотеки.
пТогда виконуваний файл роздується до немислимо великих розмірів. Одним програмами потрібні одні функції, іншим - інші.
пЗачем ж засмічувати їх непотрібним кодом! Нехай залишається тільки те, що реально необхідно.
пНам в даному випадку потрібна бібліотека libm. Саме вона містить всі основні математичні функції.
пОна вимагає включення в текст програми файлу заголовків math.h.
пПомимо цього дистрибутиви Linux містять і інші бібліотеки, наприклад:
libGL Висновок тривимірної графіки в стандарті OpenGL. Потрібно файл заголовків на <GL/gl.h>.r
libcrypt Криптографічні функції. Потрібно файл заголовків на <crypt.h>.
libcurses Псевдографика у символьному режимі. Потрібно файл заголовків на <curses.h>.r
libform Створення екранних форм у текстовому режимі. Потрібно файл заголовків на <form.h>.r
libgthread Підтримка багатопоточного режиму. Потрібно файл заголовків на <glib.h>.r
libgtk Графічна бібліотека в режимі X Window. Потрібно файл заголовків на <gtk/gtk.h>.r
libhistory Роботи з журналами. Потрібно файл заголовків на <readline/readline.h>.r
libjpeg Робота з зображень у форматі JPEG. Потрібно файл заголовків на <jpeglib.h>.r
libncurses Робота з псевдографикой у символьному режимі. Потрібно файл заголовків на <ncurses.h>.r
libpng Робота з графікою у форматі PNG. Потрібно файл заголовків на <png.h>.r
libpthread Багатопотокова бібліотека POSIX. Стандартна багатопотокова бібліотека для Linux.
Потрібно файл заголовків на <pthread.h>.r
libreadline Робота з командним рядком. Потрібно файл заголовків на <readline/readline.h>.r
libtiff Робота з графікою у форматі TIFF. Потрібно файл заголовків на <tiffio.h>.r
libvga Низькорівневий робота з VGA і SVGA. Потрібно файл заголовків на <vga.h>.
пА також багато-багато інших.
пОбратите увагу, що назви всіх цих бібліотек починаються з буквосполучення lib-.
пДля їх явного включення у виконуваний файл, потрібно додати до команді gcc опцію-l, до якої
пслитно додати назва бібліотеки без lib-.
пНапример, щоб включити бібліотеку libvga треба вказати параметр-lvga.
пНам потрібні математичні функції pow, sqrt, sin, cos, tan. Вони, як вже було сказано, знаходяться
пв математичної бібліотеці libm.
пСледовательно, щоб підключити цю бібліотеку, ми повинні вказати параметр-lm.
gcc calculate.o main.o-o kalkul-lm
Ура! Виконуваний файл створений! Запустимо його:
.kalkul