C Language
Kompilacja
Szukaj…
Wprowadzenie
Język C jest tradycyjnie językiem skompilowanym (w przeciwieństwie do tłumaczonego). Standard C definiuje fazy tłumaczenia , a produktem ich zastosowania jest obraz programu (lub program skompilowany). W c11 fazy są wymienione w §5.1.1.2.
Uwagi
Rozszerzenie nazwy pliku | Opis |
---|---|
.c | Plik źródłowy. Zwykle zawiera definicje i kod. |
.h | Plik nagłówka. Zwykle zawiera deklaracje. |
.o | Plik obiektowy. Skompilowany kod w języku maszynowym. |
.obj | Alternatywne rozszerzenie dla plików obiektowych. |
.a | Plik biblioteki. Pakiet plików obiektowych. |
.dll | Biblioteka Dynamic-Link w systemie Windows. |
.so | Obiekt współdzielony (biblioteka) w wielu systemach uniksopodobnych. |
.dylib | Biblioteka Dynamic Link na OSX (wariant Unix). |
.exe , .com | Plik wykonywalny Windows. Utworzony przez połączenie plików obiektów i plików biblioteki. W systemach uniksowych nie ma specjalnego rozszerzenia nazwy pliku wykonywalnego. |
Flagi kompilatora POSIX c99 | Opis |
---|---|
-o filename | Nazwa pliku wyjściowego, np. ( bin/program.exe , program ) |
-I directory | szukaj nagłówków w direrctory . |
-D name | zdefiniuj name makra |
-L directory | wyszukaj biblioteki w directory . |
-l name | libname biblioteki biblioteki linków. |
Kompilatory na platformach POSIX (Linux, mainframe, Mac) zwykle akceptują te opcje, nawet jeśli nie są nazywane c99
.
- Zobacz także c99 - kompiluj standardowe programy C.
Flagi GCC (kolekcja kompilatora GNU) | Opis |
---|---|
-Wall | Włącza wszystkie komunikaty ostrzegawcze, które są powszechnie akceptowane, aby były przydatne. |
-Wextra | Włącza więcej komunikatów ostrzegawczych, może być zbyt głośny. |
-pedantic | Wymuś ostrzeżenia, gdy kod narusza wybrany standard. |
-Wconversion | Włącz ostrzeżenia o niejawnej konwersji, używaj ostrożnie. |
-c | Kompiluje pliki źródłowe bez łączenia. |
-v | Wyświetla informacje o kompilacji. |
-
gcc
akceptuje flagi POSIX i wiele innych. - Wiele innych kompilatorów na platformach POSIX (
clang
, kompilatory specyficzne dla dostawców) również używa flag wymienionych powyżej. - Zobacz także Wywoływanie GCC, aby uzyskać więcej opcji.
Flagi TCC (kompilator Tiny C) | Opis |
---|---|
-Wimplicit-function-declaration | Ostrzegaj o niejawnej deklaracji funkcji. |
-Wunsupported | Ostrzegaj o nieobsługiwanych funkcjach GCC, które są ignorowane przez TCC. |
-Wwrite-strings | Ustaw stałe łańcuchowe typu const char * zamiast char *. |
-Werror | Przerwij kompilację, jeśli pojawią się ostrzeżenia. |
-Wall | Aktywuj wszystkie ostrzeżenia, z wyjątkiem -Werror , -Wunusupported i -Wwrite strings . |
Linker
Zadaniem linkera jest połączenie kilku plików obiektowych (plików .o
) w binarny plik wykonywalny. Proces łączenia polega głównie na rozwiązywaniu adresów symbolicznych na adresy numeryczne . Wynikiem procesu łączenia jest zwykle program wykonywalny.
Podczas procesu łączenia linker pobierze wszystkie moduły obiektowe określone w wierszu poleceń, doda z przodu jakiś specyficzny dla systemu kod startowy i spróbuje rozwiązać wszystkie odwołania zewnętrzne w module obiektowym za pomocą definicji zewnętrznych w innych plikach obiektowych (plikach obiektowych może być określony bezpośrednio w wierszu poleceń lub może zostać domyślnie dodany przez biblioteki). Następnie przypisze adresy ładowania do plików obiektowych, to znaczy określa, gdzie kod i dane znajdą się w przestrzeni adresowej gotowego programu. Po uzyskaniu adresów ładowania może zastąpić wszystkie adresy symboliczne w kodzie obiektowym „rzeczywistymi” numerycznymi adresami w przestrzeni adresowej celu. Program jest teraz gotowy do uruchomienia.
Obejmuje to zarówno pliki obiektowe, które kompilator utworzył z plików kodu źródłowego, jak i pliki obiektowe, które zostały wstępnie skompilowane i zebrane w pliki bibliotek. Pliki te mają nazwy, które kończą się na .a
lub .so
, i zwykle nie musisz o nich wiedzieć, ponieważ linker wie, gdzie znajduje się większość z nich, i w razie potrzeby automatycznie je połączy.
Domniemane wywołanie linkera
Podobnie jak preprocesor, linker jest osobnym programem, często nazywanym ld
(ale Linux używa na przykład collect2
). Podobnie jak preprocesor, linker jest wywoływany automatycznie podczas korzystania z kompilatora. Zatem normalny sposób korzystania z linkera jest następujący:
% gcc foo.o bar.o baz.o -o myprog
Ten wiersz informuje kompilator o połączeniu ze sobą trzech plików obiektowych ( foo.o
, bar.o
i baz.o
) w binarny plik wykonywalny o nazwie myprog
. Teraz masz plik o nazwie myprog
, który możesz uruchomić i mam nadzieję, że zrobi coś fajnego i / lub przydatnego.
Jawne wywołanie linkera
Możliwe jest bezpośrednie wywołanie linkera, ale rzadko jest to wskazane i zazwyczaj jest bardzo specyficzne dla platformy. Oznacza to, że opcje działające w systemie Linux niekoniecznie działają w systemach Solaris, AIX, macOS, Windows i podobnie w przypadku innych platform. Jeśli pracujesz z GCC, możesz użyć gcc -v
aby zobaczyć, co jest wykonywane w twoim imieniu.
Opcje dla linkera
Linker bierze również kilka argumentów, aby zmodyfikować swoje zachowanie. Następujące polecenie powiedziałoby gcc, aby foo.o
i bar.o
, ale zawierało także bibliotekę ncurses
.
% gcc foo.o bar.o -o foo -lncurses
Jest to faktycznie (mniej więcej) równoważne z
% gcc foo.o bar.o /usr/lib/libncurses.so -o foo
(chociaż libncurses.so
może być libncurses.a
, który jest tylko archiwum utworzonym za pomocą ar
). Zauważ, że powinieneś wymienić biblioteki (według nazwy ścieżki lub opcji -lname
) po plikach obiektów. W przypadku bibliotek statycznych kolejność ich określania ma znaczenie; często w przypadku bibliotek współdzielonych kolejność nie ma znaczenia.
Zauważ, że w wielu systemach, jeśli używasz funkcji matematycznych (z <math.h>
matematyki.h <math.h>
), musisz podać -lm
aby załadować bibliotekę matematyki - ale Mac OS X i macOS Sierra nie wymagają tego. Istnieją inne biblioteki, które są oddzielnymi bibliotekami w systemie Linux i innych systemach uniksowych, ale nie w macOS - wątki POSIX i POSIX w czasie rzeczywistym, a biblioteki sieciowe są przykładami. W związku z tym proces łączenia różni się w zależności od platformy.
Inne opcje kompilacji
To wszystko, co musisz wiedzieć, aby rozpocząć kompilację własnych programów C. Zasadniczo zalecamy również użycie opcji wiersza polecenia -Wall
:
% gcc -Wall -c foo.cc
Opcja -Wall
powoduje, że kompilator ostrzega o legalnych, ale wątpliwych konstrukcjach kodu i pomaga bardzo wcześnie wykryć wiele błędów.
Jeśli chcesz, aby kompilator wyświetlał w tobie więcej ostrzeżeń (w tym zmienne, które zostały zadeklarowane, ale nie zostały użyte, zapominając o zwróceniu wartości itp.), Możesz użyć tego zestawu opcji, ponieważ -Wall
, pomimo nazwy, nie zmienia się wszystkie możliwe ostrzeżenia na:
% gcc -Wall -Wextra -Wfloat-equal -Wundef -Wcast-align -Wwrite-strings -Wlogical-op \
> -Wmissing-declarations -Wredundant-decls -Wshadow …
Zauważ, że clang
ma opcję -Weverything
co naprawdę włącza wszystkie ostrzeżenia w clang
.
Typy plików
Kompilowanie programów w C wymaga pracy z pięcioma rodzajami plików:
Pliki źródłowe : Te pliki zawierają definicje funkcji i mają nazwy, które kończą się na
.c
zgodnie z konwencją. Uwaga:.cc
i.cpp
są plikami C ++; nie pliki C.
np.foo.c
Pliki nagłówkowe : pliki te zawierają prototypy funkcji i różne instrukcje preprocesora (patrz poniżej). Służą do umożliwienia plikom kodu źródłowego dostępu do funkcji zdefiniowanych zewnętrznie. Pliki nagłówkowe kończą się na
.h
zgodnie z konwencją.
np.foo.h
Pliki obiektowe : Te pliki są tworzone jako dane wyjściowe kompilatora. Składają się z definicji funkcji w formie binarnej, ale same nie są wykonywalne. Pliki obiektowe kończą się na
.o
zgodnie z konwencją, chociaż w niektórych systemach operacyjnych (np. Windows, MS-DOS) często kończą się na.obj
.
np.foo.o
foo.obj
Binarne pliki wykonywalne : są tworzone jako dane wyjściowe programu o nazwie „linker”. Linker łączy ze sobą kilka plików obiektowych, aby utworzyć plik binarny, który można wykonać bezpośrednio. Binarne pliki wykonywalne nie mają specjalnego przyrostka w systemach operacyjnych Unix, chociaż zazwyczaj kończą się
.exe
w systemie Windows.
np.foo
foo.exe
Biblioteki : Biblioteka jest skompilowanym plikiem binarnym, ale sama w sobie nie jest plikiem wykonywalnym (tzn. Nie ma funkcji
main()
w bibliotece). Biblioteka zawiera funkcje, z których może korzystać więcej niż jeden program. Biblioteka powinna być dostarczana z plikami nagłówkowymi, które zawierają prototypy dla wszystkich funkcji w bibliotece; do tych plików nagłówkowych należy się odwoływać (np.#include <library.h>
) w każdym pliku źródłowym korzystającym z biblioteki. Linker musi następnie zostać skierowany do biblioteki, aby program mógł się pomyślnie skompilować. Istnieją dwa typy bibliotek: statyczna i dynamiczna.- Biblioteka statyczna : Biblioteka statyczna (pliki
.a
dla systemów POSIX i.lib
dla Windows - nie mylić z plikami bibliotek importu DLL , które również używają rozszerzenia.lib
) jest wbudowana statycznie w program. Biblioteki statyczne mają tę zaletę, że program dokładnie wie, która wersja biblioteki jest używana. Z drugiej strony, rozmiary plików wykonywalnych są większe, ponieważ wszystkie używane funkcje biblioteki są uwzględnione.
np.libfoo.a
foo.lib
- Biblioteka dynamiczna : Biblioteka dynamiczna (pliki
.so
dla większości systemów POSIX,.dylib
dla OSX i.dll
dla Windows) jest dynamicznie łączona przez program w czasie wykonywania. Są one czasami nazywane bibliotekami współdzielonymi, ponieważ jeden obraz biblioteki może być współdzielony przez wiele programów. Biblioteki dynamiczne mają tę zaletę, że zajmują mniej miejsca na dysku, jeśli biblioteka korzysta z więcej niż jednej aplikacji. Umożliwiają także aktualizacje bibliotek (poprawki błędów) bez konieczności odbudowywania plików wykonywalnych.
np.foo.so
foo.dylib
foo.dll
- Biblioteka statyczna : Biblioteka statyczna (pliki
Preprocesor
Zanim kompilator C rozpocznie kompilację pliku kodu źródłowego, plik jest przetwarzany w fazie wstępnego przetwarzania. Ta faza może być wykonana przez osobny program lub być całkowicie zintegrowana w jednym pliku wykonywalnym. W każdym razie jest on automatycznie wywoływany przez kompilator przed rozpoczęciem właściwej kompilacji. Faza przetwarzania wstępnego przekształca kod źródłowy w inny kod źródłowy lub jednostkę tłumaczeniową poprzez zastosowanie zamienników tekstowych. Możesz myśleć o tym jako o „zmodyfikowanym” lub „rozszerzonym” kodzie źródłowym. To rozszerzone źródło może istnieć jako prawdziwy plik w systemie plików lub może być przechowywane w pamięci przez krótki czas przed dalszym przetwarzaniem.
Komendy preprocesora rozpoczynają się od znaku funta („#”). Istnieje kilka poleceń preprocesora; dwa najważniejsze to:
Definiuje :
#define
służy głównie do definiowania stałych. Na przykład,#define BIGNUM 1000000 int a = BIGNUM;
staje się
int a = 1000000;
#define
jest używane w ten sposób, aby uniknąć konieczności jawnego zapisywania stałej wartości w wielu różnych miejscach w pliku kodu źródłowego. Jest to ważne w przypadku, gdy będziesz musiał później zmienić stałą wartość; jest o wiele mniej podatny na błędy, aby go zmienić raz w#define
, niż trzeba go zmieniać w wielu miejscach rozsianych po całym kodzie.Ponieważ
#define
wykonuje po prostu wyszukiwanie zaawansowane i zamienia, możesz także deklarować makra. Na przykład:#define ISTRUE(stm) do{stm = stm ? 1 : 0;}while(0) // in the function: a = x; ISTRUE(a);
staje się:
// in the function: a = x; do { a = a ? 1 : 0; } while(0);
Przy pierwszym przybliżeniu efekt ten jest mniej więcej taki sam, jak w przypadku funkcji wstawianych, ale preprocesor nie zapewnia sprawdzania typu makr
#define
. Wiadomo, że jest to podatne na błędy, a ich stosowanie wymaga dużej ostrożności.Zwróć też uwagę, że preprocesor zastąpiłby również komentarze spacjami, jak wyjaśniono poniżej.
Obejmuje :
#include
służy do uzyskiwania dostępu do definicji funkcji zdefiniowanych poza plikiem kodu źródłowego. Na przykład:#include <stdio.h>
powoduje, że preprocesor wkleja zawartość
<stdio.h>
do pliku kodu źródłowego w miejscu instrukcji#include
, zanim zostanie skompilowana.#include
jest prawie zawsze używane do dołączania plików nagłówkowych, które są głównie plikami zawierającymi deklaracje funkcji i instrukcje#define
. W tym przypadku używamy#include
, aby móc korzystać z funkcji takich jakprintf
iscanf
, których deklaracje znajdują się w plikustdio.h
. Kompilatory C nie pozwalają na korzystanie z funkcji, chyba że została wcześniej zadeklarowana lub zdefiniowana w tym pliku; Instrukcje#include
są zatem sposobem na ponowne użycie wcześniej napisanego kodu w programach C.Operacje logiczne :
#if defined A || defined B variable = another_variable + 1; #else variable = another_variable * 2; #endif
zostanie zmieniony na:
variable = another_variable + 1;
jeśli A lub B zostały wcześniej zdefiniowane gdzieś w projekcie. Jeśli tak nie jest, oczywiście preprocesor to zrobi:
variable = another_variable * 2;
Jest to często używane w kodzie, który działa w różnych systemach lub kompiluje na różnych kompilatorach. Ponieważ istnieją definicje globalne, które są specyficzne dla kompilatora / systemu, możesz przetestować te definicje i zawsze pozwolić kompilatorowi po prostu użyć kodu, który skompiluje na pewno.
Komentarze
Preprocesor zastępuje wszystkie komentarze w pliku źródłowym pojedynczymi spacjami. Komentarze są oznaczone
//
do końca wiersza lub kombinacją nawiasów otwierających/*
i zamykających*/
komentarzy.
Kompilator
Po tym, jak preprocesor C uwzględni wszystkie pliki nagłówkowe i rozszerzy wszystkie makra, kompilator może skompilować program. Robi to, zamieniając kod źródłowy C w plik kodu obiektowego, który jest plikiem z rozszerzeniem .o
który zawiera binarną wersję kodu źródłowego. Jednak kod obiektowy nie jest bezpośrednio wykonywalny. Aby zrobić plik wykonywalny, musisz również dodać kod do wszystkich funkcji bibliotecznych, które były #include
d do pliku (to nie to samo, co #include
deklaracji, co robi #include
). To jest praca linkera .
Zasadniczo dokładna sekwencja wywoływania kompilatora C zależy w dużej mierze od używanego systemu. W tym przypadku korzystamy z kompilatora GCC, choć należy zauważyć, że istnieje wiele innych kompilatorów:
% gcc -Wall -c foo.c
%
to wiersz polecenia systemu operacyjnego. Informuje to kompilator, aby uruchomił procesor wstępny na pliku foo.c
a następnie skompilował go w pliku kodu obiektowego foo.o
Opcja -c
oznacza skompilowanie pliku kodu źródłowego do pliku obiektowego, ale nie wywoływanie programu łączącego. Ta opcja -c
jest dostępna w systemach POSIX, takich jak Linux lub macOS; inne systemy mogą używać innej składni.
Jeśli cały program znajduje się w jednym pliku kodu źródłowego, możesz zamiast tego zrobić:
% gcc -Wall foo.c -o foo
Mówi to kompilatorowi, aby uruchomił procesor wstępny na foo.c
, skompilował go, a następnie połączył go, aby utworzyć plik wykonywalny o nazwie foo
. Opcja -o
stwierdza, że następnym słowem w wierszu jest nazwa binarnego pliku wykonywalnego (programu). Jeśli nie podasz -o
, (jeśli wpiszesz gcc foo.c
), plik wykonywalny zostanie nazwany a.out
z powodów historycznych.
Zasadniczo kompilator wykonuje cztery kroki podczas konwertowania pliku .c
plik wykonywalny:
- przetwarzanie wstępne - tekstowo rozwija
#include
dyrektyw i#define
makra w pliku.c
- kompilacja - konwertuje program na asembler (możesz zatrzymać kompilator na tym etapie, dodając opcję
-S
) - wirtualny plik dziennika - konwertuje wirtualny plik dziennika na kod maszynowy
- linkage - łączy kod obiektu z bibliotekami zewnętrznymi, aby utworzyć plik wykonywalny
Zauważ też, że nazwa kompilatora, którego używamy, to GCC, co oznacza zarówno „kompilator GNU C”, jak i „zbiór kompilatorów GNU”, w zależności od kontekstu. Istnieją inne kompilatory C. W systemach operacyjnych typu Unix wiele z nich ma nazwę cc
, oznaczającą „kompilator C”, który często jest dowiązaniem symbolicznym do innego kompilatora. W systemach Linux cc
jest często aliasem dla GCC. W systemie macOS lub OS-X wskazuje na brzęk.
Standardy POSIX obecnie nakazują c99
jako nazwę kompilatora C - domyślnie obsługuje standard C99. Wcześniejsze wersje POSIX wymagały c89
jako kompilatora. POSIX nakazuje również, aby ten kompilator rozumiał opcje -c
i -o
, których użyliśmy powyżej.
Uwaga: Opcja -Wall
obecna w obu przykładach gcc
nakazuje kompilatorowi wydrukowanie ostrzeżeń o wątpliwych konstrukcjach, co jest zdecydowanie zalecane. Dobrym pomysłem jest również dodanie innych opcji ostrzegawczych , np. -Wextra
.
Fazy tłumaczenia
Począwszy od standardu C 2011, wymienionego w § 5.1.1.2 Fazy tłumaczenia , tłumaczenie kodu źródłowego na obraz programu (np. Plik wykonywalny) jest wyświetlane w 8 uporządkowanych krokach.
- Dane wejściowe pliku źródłowego są odwzorowywane na źródłowy zestaw znaków (w razie potrzeby). W tym kroku zastępowane są trygrafy.
- Linie kontynuacji (linie kończące się na
\
) są łączone z następną linią. - Kod źródłowy jest przetwarzany na białe znaki i tokeny przetwarzania wstępnego.
- Stosowany jest preprocesor, który wykonuje dyrektywy, rozwija makra i stosuje pragmy. Każdy plik źródłowy pobrany przez
#include
przechodzi fazy tłumaczenia od 1 do 4 (w razie potrzeby rekurencyjnie). Wszystkie dyrektywy związane z preprocesorem są następnie usuwane. - Wartości źródłowego zestawu znaków w stałych znaków i literałach ciągu są odwzorowywane na zestaw znaków wykonania.
- Literały łańcuchowe sąsiadujące ze sobą są konkatenowane.
- Kod źródłowy jest przetwarzany na tokeny, które zawierają jednostkę tłumaczącą.
- Odwołania zewnętrzne są rozwiązane i tworzony jest obraz programu.
Implementacja kompilatora C może łączyć ze sobą kilka kroków, ale wynikowy obraz musi nadal zachowywać się tak, jakby powyższe kroki miały miejsce osobno w powyższej kolejności.