C/C++: Kompilacja projektów - cz. 1
Skoro czytasz ten artykuł to pewnie zdarzyło Ci się napisać, skompilować i uruchomić co najmniej kilka programów napisanych w językach C czy C++. Może Ci się więc wydawać, że jest to temat banalny. Ot, wystarczy nacisnąć przycisk kompiluj w środowisku programistycznym albo wpisać prostą komendę w wierszu poleceń.
Jest to jednak wrażenie mylące. Skompilowanie projektu złożonego z wielu źródeł, zwłaszcza w sytuacji, w której korzystamy z kodu dostarczonego przez kogoś innego, może przysporzyć wielu frustracji.
Podczas tego cyklu artykułów omówimy sobie jak kompilować złożone z wielu plików źródłowych projekty napisane w językach C i C++, jak automatyzować proces kompilacji za pomocą narzędzia GNU Make oraz jak dokładnie działa kompilator oraz linker.
Wymagania
- Zainstalowany kompilator GCC. Sposób jego instalacji w systemie Windows możesz znaleźć na naszym darmowym kursie o podstawach programowania w języku C.
- Znajomość podstaw języka C.
Prosty program
Do ćwiczeń wykorzystamy prosty program, który wczyta z wejścia tablicę pięciu liczb, a następnie ją posortuje i wypisze posortowane wartości na wyjściu. Kod źródłowy programu znajduje się w pliku app_full.c
Kompilację programu przeprowadzamy uruchamiając terminal systemu operacyjnego w folderze, w którym znajduje się plik app_full.c i wykonując komendę:
gcc -o app.exe app_full.c
Po kompilacji przetestuj działanie programu.
#include <stdio.h>
#define ARRAY_SIZE 5
/*Funkcja sortuje tablicę w miejscu za pomocą
algorytmu sortowania przez wstawianie
array - tablica do posortowania
array_size - rozmiar tablicy
*/
void sort_array(int array[], int array_size)
{
int i, j;
for (j=1; j<array_size; j++)
{
int key = array[j];
i = j - 1;
while(i>=0 && array[i]>key)
{
array[i+1]=array[i];
i--;
}
array[i+1]=key;
}
}
/*Wczytuje dane do tablicy ze standardowego wejścia
array - tablica, w której będą umieszczone dane
array_size - rozmiar tablicy*/
void read_array(int array[], int array_size)
{
for (int i=0; i<array_size; i++)
scanf("%d",&(array[i]));
}
/*Drukuje tablicę w standardowym wyjściu
array - tablica
array_size - rozmiar tablicy
*/
void print_array(int array[], int array_size)
{
for(int i=0; i<array_size; i++)
printf("%d ", array[i]);
}
int main()
{
int array[ARRAY_SIZE];
read_array(array, ARRAY_SIZE);
sort_array(array, ARRAY_SIZE);
print_array(array, ARRAY_SIZE);
return 0;
}
Podział programu na pliki
Drugim krokiem tego ćwiczenia jest podział programu na kilka plików. W naszym przypadku będą to:
- app.c – kod zawierający funkcję main programu,
- sort.c – kod funkcji sortującej tablicę,
- sort.h – plik nagłówkowy zawierający deklarację (prototyp) funkcji sortującej,
- arrayio.c – kod funkcji wczytującej i drukującej tablicę,
- arrayio.h – plik nagłówkowy zawierający deklarację funkcji do wczytywania i drukowania tablicy
Kompilację wykonujemy komendą: gcc -o app.exe app.c sort.c arrayio.c
Zwróćmy uwagę na dwie rzeczy:
- Do kompilacji pliku app.c nie musi on zawierać ciała (kodu) funkcji read_array, sort_array i print_array, ale musi zawierać ich prototypy. Prototypy te umieszczamy w pliku dodając do niego za pomocą dyrektywy #include zawartości plików nagłówkowych.
- Wywołując kompilator podajemy nazwy wszystkich plików z kodem (z rozszerzeniem .c). Pliki nagłówkowe kompilator dołączy sam.
#include "sort.h"
#include "arrayio.h"
#define ARRAY_SIZE 5
int main()
{
int array[ARRAY_SIZE];
read_array(array, ARRAY_SIZE);
sort_array(array, ARRAY_SIZE);
print_array(array, ARRAY_SIZE);
return 0;
}
#include "sort.h"
void sort_array(int array[], int array_size)
{
int i, j;
for (j=1; j<array_size; j++)
{
int key = array[j];
i = j - 1;
while(i>=0 && array[i]>key)
{
array[i+1]=array[i];
i--;
}
array[i+1]=key;
}
}
/*Funkcja sortuje tablicę w miejscu za pomocą
algorytmu sortowania przez wstawianie
array - tablica do posortowania
array_size - rozmiar tablicy
*/
void sort_array(int array[], int array_size);
#include <stdio.h>
#include "arrayio.h"
void read_array(int array[], int array_size)
{
for (int i=0; i<array_size; i++)
scanf("%d",&(array[i]));
}
void print_array(int array[], int array_size)
{
for(int i=0; i<array_size; i++)
printf("%d ", array[i]);
}
/*Wczytuje dane do tablicy ze standardowego wejścia
array - tablica, w której będą umieszczone dane
array_size - rozmiar tablicy*/
void read_array(int array[], int array_size);
/*Drukuje tablicę w standardowym wyjściu
array - tablica
array_size - rozmiar tablicy
*/
void print_array(int array[], int array_size);
Co nam to wszystko dało?
Po co dzielić kod na kilka plików? Istnieje kilka powodów;
- Jeżeli każdy z plików zawiera tylko niewielką część kodu to łatwiej go analizować i modyfikować.
- Możemy wykorzystywać pewne fragmenty kodu w różnych projektach. Np. funkcja sortująca tablicę może nam się jeszcze kiedyś przydać.
- Możemy w ten sposób korzystać też łatwo z kodu dostarczanego przez innych (np. bibliotek open source).
- Jeżeli nad projektem pracujemy w zespole, to łatwiej jest podzielić pracę między różnych programistów.
Co dalej?
W następnej części zobaczymy, że do zbudowania programu nie potrzebujemy wszystkich plików z kodem źródłowym, ale możemy wykorzystywać już skompilowane wcześniej fragment kodu.