Kompilacja to proces konwersji kodu źródłowego języka C na kod maszynowy. Ponieważ C jest językiem średniego poziomu, potrzebuje kompilatora, który przekonwertuje go na kod wykonywalny, aby program mógł zostać uruchomiony na naszej maszynie.
Podczas kompilacji program C przechodzi przez następujące fazy:

Proces kompilacji w C
Jak skompilować i uruchomić program w C?
Najpierw potrzebujemy kompilatora i edytora kodu, aby skompilować i uruchomić program w C. Poniższy przykład dotyczy komputera Ubuntu z kompilatorem GCC.
Krok 1: Tworzenie pliku źródłowego C
Najpierw tworzymy program w C za pomocą edytora i zapisujemy plik jako nazwapliku.c
zastosowań systemu operacyjnego
$ vi filename.c>
Możemy napisać prosty program hello world i zapisać go.
Krok 2: Kompilacja przy użyciu kompilatora GCC
Używamy następującego polecenia w terminalu do kompilowania naszego pliku źródłowego nazwa_pliku.c
$ gcc filename.c –o filename>
Możemy przekazać wiele instrukcji do kompilatora GCC do różnych zadań, takich jak:
- Opcja -Wall włącza wszystkie komunikaty ostrzegawcze kompilatora. Ta opcja jest zalecana do generowania lepszego kodu.
- Opcja -o służy do określenia nazwy pliku wyjściowego. Jeżeli nie skorzystamy z tej opcji to generowany będzie plik wyjściowy o nazwie a.out.
Jeśli w naszym programie C nie będzie żadnych błędów, zostanie wygenerowany plik wykonywalny programu C.
Krok 3: Wykonanie programu
Po kompilacji generowany jest plik wykonywalny, który uruchamiamy za pomocą poniższego polecenia.
Java łącząca ciągi znaków
$ ./filename>
Program zostanie wykonany, a wynik zostanie wyświetlony w terminalu.
Co dzieje się w procesie kompilacji?
Kompilator konwertuje program C na plik wykonywalny. Istnieją cztery fazy, w których program C staje się plikiem wykonywalnym:
- Wstępne przetwarzanie łączenia zestawu kompilacji
Wykonując poniższe polecenie, otrzymujemy wszystkie pliki pośrednie w bieżącym katalogu wraz z plikiem wykonywalnym.
$gcc -Wall -save-temps filename.c –o filename>
Poniższy zrzut ekranu pokazuje wszystkie wygenerowane pliki pośrednie.
Pliki pośrednie
och, przesunięcie ku czerwieni
Zobaczmy po kolei, co zawierają te pliki pośrednie.
1. Wstępne przetwarzanie
Jest to pierwsza faza, przez którą przechodzi kod źródłowy. Ta faza obejmuje:
- Usuwanie komentarzy
- Rozbudowa makr
- Rozbudowa dołączonych plików.
- Kompilacja warunkowa
Wstępnie przetworzone dane wyjściowe są przechowywane w pliku nazwapliku.i . Zobaczmy, co jest w środku filename.i: using $vi nazwa pliku.i
szakal kontra wilk
W powyższym wyniku plik źródłowy jest wypełniony mnóstwem informacji, ale ostatecznie nasz kod zostaje zachowany.
- printf zawiera teraz a + b zamiast add(a, b), ponieważ makra się rozwinęły.
- Komentarze są usuwane. Brakuje #include, zamiast tego widzimy dużo kodu. Dlatego pliki nagłówkowe zostały rozszerzone i uwzględnione w naszym pliku źródłowym.
2. Kompilacja
Następnym krokiem jest skompilowanie pliku nazwa.i i utworzenie; pośredni skompilowany plik wyjściowy nazwapliku.s . Ten plik znajduje się w instrukcjach na poziomie zespołu. Przyjrzyjmy się temu plikowi za pomocą $nano nazwapliku.s polecenie terminala.
Plik kodu zespołu
Zrzut ekranu pokazuje, że jest on w języku asemblera, który jest zrozumiały dla asemblera.
3. Montaż
W tej fazie nazwa pliku.s jest pobierana jako dane wejściowe i przekształcana nazwapliku.o przez asembler. Ten plik zawiera instrukcje na poziomie komputera. Na tym etapie tylko istniejący kod jest konwertowany na język maszynowy, a wywołania funkcji, takie jak printf(), nie są rozpoznawane. Obejrzyjmy ten plik za pomocą $vi nazwa pliku.o
testowanie i typy oprogramowania
Kod binarny
4. Łączenie
Jest to ostatnia faza, w której następuje całe powiązanie wywołań funkcji z ich definicjami. Linker wie, gdzie wszystkie te funkcje są zaimplementowane. Linker również wykonuje dodatkową pracę, dodaje do naszego programu dodatkowy kod, który jest wymagany podczas uruchamiania i kończenia programu. Na przykład istnieje kod wymagany do skonfigurowania środowiska, na przykład przekazywania argumentów wiersza poleceń. Zadanie to można łatwo zweryfikować za pomocą $rozmiar nazwapliku.o I $rozmiar nazwa pliku . Dzięki tym poleceniom wiemy, w jaki sposób plik wyjściowy zmienia się z pliku obiektowego w plik wykonywalny. Dzieje się tak z powodu dodatkowego kodu, który Linker dodaje do naszego programu.
Notatka: GCC domyślnie wykonuje dynamiczne łączenie, więc printf() jest dynamicznie łączona w powyższym programie. Więcej szczegółów na temat łączenia statycznego i dynamicznego znajdziesz w this , this i this .