Menu

Kompilacja C++: od kodu źródłowego do pliku wykonywalnego z g++

Jak C++ zamienia kod źródłowy .cpp w natywny plik wykonywalny: kompilacja przez g++ (albo clang++/MSVC), uruchomienie programu i czytanie błędów kompilatora, gdy coś pójdzie nie tak.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

Skompiluj, potem uruchom

Masz już zainstalowany kompilator, więc uruchomienie programu w C++ to cykl w dwóch krokach. W przeciwieństwie do języka skryptowego, w którym interpreter czyta plik linia po linii, C++ najpierw kompiluje kod źródłowy do natywnego pliku wykonywalnego (samodzielnego pliku binarnego z kodem maszynowym), a potem uruchamiasz ten plik bezpośrednio. W czasie działania między twoim programem a procesorem nie stoi żaden interpreter.

Ten podział to powód, dla którego C++ jest szybki, a także dlaczego błąd składni zatrzymuje cię już przy kompilacji, a nie w połowie działania programu. Kompilator sprawdza cały plik, zanim cokolwiek wytworzy.

Każdy samodzielny przykład możesz uruchomić tutaj, na stronie: edytor poniżej kompiluje go i uruchamia za ciebie. Warto jednak wiedzieć, co dzieje się na twoim komputerze, bo tam żyją prawdziwe projekty.

Cykl kompilacji i uruchamiania na twoim komputerze

Załóżmy, że zapisano to jako main.cpp:

#include <iostream>
using namespace std;

int main() {
    cout << "Hello from the terminal" << endl;
    return 0;
}

Otwórz terminal w folderze z plikiem i wywołaj kompilator. Najpopularniejszy kompilator to g++ (część GCC); clang++ działa tu identycznie:

g++ main.cpp -o main

Jeśli kod skompiluje się bez problemów, g++ nic nie wypisze i utworzy obok źródła nowy plik: plik wykonywalny o nazwie main (albo main.exe na Windowsie). Ten plik to kod maszynowy: nie da się go czytać jak tekstu i jest związany z twoim systemem operacyjnym i procesorem. Teraz go uruchom:

./main
Hello from the terminal

Na Windowsie uruchamiasz go jako main.exe (albo po prostu main) z tego samego terminala. ./ na macOS i Linuksie mówi powłoce "program jest tutaj, w tym folderze, a nie gdzieś w PATH". Początkujący zapominają o tym kroku, a potem dziwią się, że main zwraca command not found.

Co robi -o (i a.out)

Flaga -o nadaje nazwę wynikowi. Pomiń ją, a g++ nadal skompiluje program, ale zapisze plik wykonywalny pod domyślną nazwą: a.out na macOS/Linuksie, a.exe na Windowsie.

g++ main.cpp        # produces a.out, not main
./a.out

To ciągle myli ludzi: kompilują, nie widzą błędów, uruchamiają ./main i dostają No such file or directory, bo plik binarny nazywa się w rzeczywistości a.out. Zawsze podawaj -o, aby dokładnie wiedzieć, co uruchamiasz.

Kompilujesz raz po każdej zmianie źródła. Potem plik wykonywalny jest niezależny: możesz uruchomić ./main sto razy bez ponownej kompilacji. g++ wywołujesz ponownie dopiero po edycji pliku .cpp.

Wybór standardu C++

C++ ma wersje: C++11, C++14, C++17, C++20, C++23, a każda dodaje nowe funkcje języka. Haczyk: kompilator wybiera domyślny standard, który może być starszy, niż się spodziewasz, więc nowoczesny kod może się nie kompilować bez oczywistego powodu. Ustaw standard jawnie flagą -std:

g++ -std=c++17 main.cpp -o main
g++ -std=c++20 main.cpp -o main

Oto kod, który używa funkcji z C++17 (structured bindings). W edytorze kompiluje się bez problemu, ale na twoim komputerze wymaga -std=c++17 lub nowszego:

Jeśli zobaczysz błąd w rodzaju 'structured bindings' only available with '-std=c++17', poprawka nie leży w twoim kodzie: wystarczy dodać właściwą flagę -std. W całym tym kursie zakładamy C++17 lub nowszy.

Włącz ostrzeżenia

Program w C++ może się bez problemu skompilować i nadal być błędny. Kompilator, jeśli go poprosisz, wskaże podejrzany kod, zanim ten ugryzie cię w czasie działania. Dodaj -Wall -Wextra:

g++ -std=c++17 -Wall -Wextra main.cpp -o main

Spójrz na ten program. Kompiluje się bez -Wall, ale ma prawdziwy błąd: odczytuje zmienną, której nigdy nie nadano wartości, a to niezdefiniowane zachowanie:

#include <iostream>
using namespace std;

int main() {
    int count;                 // never initialized
    cout << count << endl;     // reads garbage - undefined behavior
    return 0;
}

Z włączonymi ostrzeżeniami kompilator to wychwytuje:

warning: 'count' is used uninitialized [-Wuninitialized]

Wyrób w sobie nawyk kompilowania z -Wall -Wextra od pierwszego dnia. Ostrzeżenia to darmowy przegląd kodu wykonany przez kompilator; ignorowanie ich to sposób, w jaki przeżywają subtelne błędy. Poprawka jest tu prosta: int count = 0;.

Czytanie błędów kompilatora

Gdy g++ odrzuca twój kod, podaje plik, linię i to, co poszło nie tak. Umiejętność czytania tych komunikatów to połowa sukcesu w wychodzeniu z impasu. Oto klasyk, czyli brakujący średnik:

#include <iostream>
using namespace std;

int main() {
    cout << "Oops"      // no semicolon
    return 0;
}
main.cpp:5:5: error: expected ';' before 'return'
    5 |     return 0;
      |     ^~~~~~

Zwróć uwagę na kilka rzeczy. Błąd wskazuje linię 5, ale pomyłka jest w linii 4: kompilator orientuje się, że brakuje średnika, dopiero gdy dochodzi do następnego tokenu. Gdy błąd wskazuje linię, która wygląda dobrze, sprawdź linię przed nią. main.cpp:5:5 to plik, linia, a potem kolumna. Popraw tę jedną wskazaną rzecz i skompiluj ponownie, zamiast zgadywać.

Błędy kompilatora oznaczają, że jeszcze nic się nie wykonało. To coś innego niż błędy czasu wykonania, gdy program wystartował, a potem się wysypał. Wyłapywanie pomyłek podczas kompilacji, zanim program w ogóle ruszy, to jedna z największych zalet C++.

Program kontrolny

Uruchom go w edytorze albo zapisz jako main.cpp, wykonaj g++ -std=c++17 -Wall main.cpp -o main, a potem ./main na swoim komputerze. Jeśli pojawią się wszystkie trzy linie, twoje narzędzia działają od początku do końca:

Pojawiają się tu trzy rzeczy, które wkrótce poznasz dokładniej: zmienna int, vector (tablica o zmiennym rozmiarze w C++) i cout do wypisywania. Na razie wystarczy, że program się kompiluje i wypisuje wszystkie trzy linie po kolei.

Dalej: składnia C++

Masz za sobą kompilację i uruchomienie kilku programów, ale do tej pory pomijaliśmy interpunkcję: linie #include, klamry, średniki, int main() i to, dlaczego każda linia wygląda tak, a nie inaczej. Następna strona rozkłada składnię C++ na części, aby struktura przestała wyglądać jak szablonowy kod i zaczęła mieć sens.

Najczęściej zadawane pytania

Jak skompilować i uruchomić program w C++?

Zapisz kod jako main.cpp, otwórz terminal w tym folderze i wykonaj g++ main.cpp -o main, aby utworzyć plik wykonywalny. Następnie uruchom go przez ./main na macOS/Linuksie albo main.exe na Windowsie. Kompilujesz raz; powstały plik możesz uruchamiać dowolnie wiele razy.

Co robi flaga -o w g++?

-o nadaje nazwę plikowi wyjściowemu. g++ main.cpp -o hello tworzy plik wykonywalny o nazwie hello. Bez -o g++ domyślnie używa nazwy a.out (albo a.exe na Windowsie), i dlatego początkujący często uruchamiają plik, o którego istnieniu nie wiedzieli.

Jak skompilować C++ w konkretnym standardzie, np. C++17 lub C++20?

Przekaż flagę -std: g++ -std=c++17 main.cpp -o main albo -std=c++20. Bez niej kompilator używa własnego ustawienia domyślnego, które może być starsze, niż się spodziewasz, więc nowsze funkcje, takie jak structured bindings czy <ranges>, mogą się nie kompilować, dopóki nie ustawisz standardu jawnie.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ