Menu

Jak skompilować i uruchomić program w C (gcc krok po kroku)

Zamień plik .c w działający program: polecenie gcc, co naprawdę robią preprocesowanie, kompilacja i linkowanie, flagi, których warto używać od pierwszego dnia, i jak czytać błędy, gdy coś pójdzie nie tak.

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

C jest językiem kompilowanym: nic się nie uruchomi, dopóki kompilator nie przetłumaczy twojego kodu źródłowego na kod maszynowy dla konkretnego procesora i systemu operacyjnego. Ten krok to jedno polecenie, ale gdy wiesz, co ono robi, większość komunikatów o błędach przestaje być tajemnicza i staje się oczywista.

Dwa polecenia

Zapisz to jako hello.c:

#include <stdio.h>

int main(void) {
    printf("Czesc, C!\n");
    return 0;
}

Następnie w tym samym folderze:

gcc hello.c -o hello
./hello

Pierwsze polecenie kompiluje. Drugie uruchamia wynik. W Windows polecenie uruchomienia to hello w Wierszu polecenia albo .\hello w PowerShell: nie ma prefiksu ./, bo Windows domyślnie przeszukuje bieżący katalog, a powłoki uniksowe celowo tego nie robią.

Jeśli pominiesz -o hello, gcc nazwie plik wynikowy a.out (albo a.exe), dlatego starsze poradniki kończą się na ./a.out. Zawsze nazywaj plik wynikowy; kosztuje to kilka znaków i oszczędza zamieszania.

Oto ten sam program w edytorze w przeglądarce, który wykonuje oba kroki za ciebie:

Co naprawdę robi "kompilacja"

gcc hello.c -o hello wygląda jak jeden krok. W rzeczywistości to cztery kroki, a każdy może się nie udać z własnym rodzajem błędu.

1. Preprocesowanie. Zanim jakikolwiek kod C zostanie skompilowany, preprocesor obsługuje każdą linię zaczynającą się od #. #include <stdio.h> zostaje dosłownie zastąpione zawartością tego pliku nagłówkowego; makra #define są rozwijane; bloki #ifdef zostają zachowane albo usunięte. Wynikiem jest jeden duży plik C bez żadnych linii z #. Możesz go zobaczyć:

gcc -E hello.c

To wypisuje setki linii i prawie wszystkie to wklejony stdio.h.

2. Kompilacja. Przetworzony przez preprocesor kod C jest parsowany, sprawdzany pod kątem typów i tłumaczony na asembler dla twojego procesora. Stąd biorą się błędy składni, błędy typów i ostrzeżenia.

3. Asemblacja. Asembler staje się plikiem obiektowym: kodem maszynowym z miejscami zarezerwowanymi na wywołania funkcji z innych plików.

gcc -c hello.c   # produces hello.o, stops before linking

4. Linkowanie. Pliki obiektowe są zszywane razem z biblioteką standardową C, każde zarezerwowane miejsce zostaje wypełnione prawdziwym adresem, a wynikiem jest plik wykonywalny. Stąd biorą się błędy "undefined reference".

Praktyczny wniosek: błąd kompilacji wskazuje linię w twoim kodzie źródłowym, a błąd linkowania nie, bo linkowanie odbywa się po tym, jak każda linia została już zaakceptowana.

Flagi, których warto używać od pierwszego dnia

Gołe gcc file.c -o prog po cichu przyjmuje mnóstwo niebezpiecznego kodu. Cztery flagi to zmieniają.

gcc -std=c17 -Wall -Wextra -g hello.c -o hello
  • -Wall włącza typowe ostrzeżenia. Mimo nazwy to nie są "wszystkie" ostrzeżenia, tylko rozsądny zestaw.
  • -Wextra dodaje kolejne, w tym nieużywane parametry i niektóre błędy w porównaniach.
  • -std=c17 ustala standard języka, żeby twój kod znaczył to samo na każdej maszynie. Użyj -std=c99, jeśli korzystasz ze starszych materiałów.
  • -g zachowuje informacje do debugowania, więc gdb albo lldb pokażą twoje prawdziwe linie kodu, gdy coś się wysypie.

Dwie kolejne, których z czasem zechcesz:

  • -O2 włącza optymalizację w wersjach produkcyjnych. Podczas nauki zostaw ją wyłączoną: zoptymalizowany kod trudniej debugować, a ostrzeżenia czasem się zmieniają.
  • -fsanitize=address,undefined (gcc i clang) sprawia, że program przerywa działanie z czytelnym komunikatem w chwili, gdy czyta poza zakresem albo trafia na niezdefiniowane zachowanie. To najbardziej przydatna flaga przy nauce C.

Wypróbuj ostrzeżenia samodzielnie. Ten program kompiluje się i działa, ale ma dwa prawdziwe błędy:

count nigdy nie dostaje wartości, więc program wypisuje bajty, które akurat tam były. Z -Wall kompilator o tym mówi: 'count' is used uninitialized. Ostrzeżenia w C prawie nigdy nie są szumem: traktuj je jak błędy, które jeszcze się nie ujawniły.

Kompilacja więcej niż jednego pliku

Prawdziwe programy są podzielone na pliki. Przekaż je wszystkie do gcc:

gcc -std=c17 -Wall main.c utils.c -o myprog

Albo skompiluj każdy osobno i zlinkuj na końcu. Tak robią systemy budowania, żeby zmiana jednego pliku nie przebudowywała wszystkiego:

gcc -c main.c      # -> main.o
gcc -c utils.c     # -> utils.o
gcc main.o utils.o -o myprog

Niektóre biblioteki wymagają jawnej flagi linkowania. Biblioteka matematyczna to ta, na którą trafia każdy początkujący:

gcc calc.c -o calc -lm

Bez -lm użycie sqrt z math.h kompiluje się bez problemu, a potem wywraca się przy linkowaniu z komunikatem undefined reference to sqrt: deklaracja była w nagłówku, ale kod był w bibliotece, o którą nikt nie poprosił.

Czytanie błędów

Błędy w C są zwięzłe, ale spójne. Trzy przykłady obejmują większość tego, co zobaczysz na początku.

Brakujący średnik jest zgłaszany w następnej linii, bo kompilator czytał dalej:

hello.c:5:5: error: expected ';' before 'return'

Poprawka należy do linii 4, a nie 5. Zawsze gdy błąd wskazuje linię, która wygląda dobrze, sprawdź linię przed nią.

Brakujący nagłówek wygląda jak zagadka dotycząca funkcji, której nazwa jest ewidentnie napisana poprawnie:

hello.c:4:5: warning: implicit declaration of function 'printf'

To znaczy, że kompilator nigdy nie widział deklaracji printf, czyli w pliku brakuje #include <stdio.h>. Od C99 to błąd, a nie tylko ostrzeżenie.

Błąd linkowania nie ma w ogóle numeru linii:

/usr/bin/ld: main.o: in function `main':
main.c:(.text+0x1a): undefined reference to `helper'

Kompilator uwierzył ci, że helper gdzieś istnieje; linker poszukał i go nie znalazł. Albo definicja nigdy nie powstała, albo nazwa jest zapisana inaczej, albo plik, który ją zawiera, nie został przekazany do gcc.

Zawsze naprawiaj pierwszy błąd. Błędy w C pociągają za sobą kolejne: jedna zła linia potrafi wygenerować dwadzieścia komunikatów, a dziewiętnaście z nich znika, gdy naprawisz pierwszy.

Kody wyjścia

return 0 z main to nie ozdobnik. To kod wyjścia programu, a powłoka potrafi go odczytać:

Po uruchomieniu echo $? w macOS i Linuksie (albo echo %errorlevel% w Windows) wypisuje tę liczbę. Skrypty i narzędzia do budowania używają jej, żeby zdecydować, czy kontynuować. Konwencja jest bezwzględna: 0 to sukces, a wartość niezerowa to wybrany przez ciebie kod błędu. stdlib.h definiuje EXIT_SUCCESS i EXIT_FAILURE, jeśli wolisz nazwy.

Od C99 dojście do końca main bez return jest traktowane jak return 0, ale jawne napisanie tego jest czytelniejsze, a w każdej innej funkcji jest wymagane.

Najczęściej zadawane pytania

Jak skompilować i uruchomić program w C?

Zapisz kod jako hello.c, potem uruchom gcc hello.c -o hello, żeby go zbudować, i ./hello, żeby go uruchomić (w Windows samo hello). Jeśli gcc nie zostanie znalezione, najpierw musisz zainstalować kompilator.

Co robi gcc -o?

-o nadaje nazwę plikowi wyjściowemu. gcc hello.c -o hello tworzy plik wykonywalny o nazwie hello. Bez -o gcc zapisuje wynik do a.out (albo a.exe w Windows), dlatego tak wiele starych poradników uruchamia ./a.out.

Jakich flag gcc używać zawsze?

gcc -std=c17 -Wall -Wextra -g yourfile.c -o yourprog. -Wall -Wextra włączają ostrzeżenia, które wyłapują prawdziwe błędy, -std=c17 ustala wersję języka, a -g zachowuje symbole debugowania, żeby debugger mógł pokazać twój kod źródłowy. Dodaj -O2, gdy w wersji produkcyjnej zależy ci na szybkości.

Co oznacza "undefined reference to" w C?

To błąd linkera: kompilator zaakceptował wywołanie funkcji, ale nie znaleziono jej definicji. Częste przyczyny to literówka w nazwie, zapomnienie o skompilowaniu drugiego pliku .c albo użycie funkcji matematycznej bez dolinkowania biblioteki matematycznej (-lm).

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ