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
-Wallwłącza typowe ostrzeżenia. Mimo nazwy to nie są "wszystkie" ostrzeżenia, tylko rozsądny zestaw.-Wextradodaje kolejne, w tym nieużywane parametry i niektóre błędy w porównaniach.-std=c17ustala 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.-gzachowuje informacje do debugowania, więcgdbalbolldbpokażą twoje prawdziwe linie kodu, gdy coś się wysypie.
Dwie kolejne, których z czasem zechcesz:
-O2włą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).