2013-08-31 11 views
18

Wiem, że pliki nagłówkowe mają deklaracje forward różnych funkcji, struktur itp., Które są używane w pliku .c, który "wywołuje" #include, prawda? O ile mi zrozumieć, w "podziału władzy" występuje tak:Pliki nagłówkowe C i kompilacja/linkowanie

plik nagłówka: func.h

  • zawiera naprzód deklarację funkcji

    int func(int i); 
    

C plik źródłowy: func.c

  • zawiera rzeczywistego definicji funkcji

    #include "func.h" 
    
    int func(int i) { 
        return ++i ; 
    } 
    

plik źródłowy C source.c ("faktycznego" programu):

#include <stdio.h> 
#include "func.h" 

int main(void) { 
    int res = func(3); 
    printf("%i", res); 
} 

Moje pytanie brzmi: widząc, że #include jest po prostu dyrektywa kompilator, że kopie zawartość pliku w pliku #include, w jaki sposób plik .c wie, jak właściwie wykonać tę funkcję? Wszystko, co robi, to int func(int i);, więc w jaki sposób może on faktycznie wykonać tę funkcję? W jaki sposób uzyskuje dostęp do faktycznej definicji func? Czy nagłówek zawiera jakiś "wskaźnik", który mówi "to moja definicja, tam!"?

Jak to działa?

+0

To jest magia "Linkera", która rozwiązuje definicje i zapewnia, że ​​rzeczy, które według ciebie istniały podczas kompilacji, faktycznie istnieją. –

+0

Kiedy zajmujesz się plikami nagłówkowymi, możesz przeczytać o [include guards] (http://en.wikipedia.org/wiki/Include_guard). –

+0

Wiem o włączeniu strażników (o ile to wszystko), ale pominąłem je dla zwięzłości. – Aristides

Odpowiedz

20

Uchia Itachi udzielił odpowiedzi. To jest linker linker.

Korzystanie GNU C compiler gcc chcesz skompilować program jednego pliku jak

gcc hello.c -o hello # generating the executable hello 

Ale kompilacji programu dwa (lub więcej) plików w sposób opisany w przykładzie, trzeba by wykonać następujące czynności:

gcc -c func.C# generates the object file func.o 
gcc -c main.C# generates the object file main.o 
gcc func.o main.o -o main # generates the executable main 

Każdy plik obiektowy ma zewnętrzne symbole (możesz myśleć o nim jako o członkach publicznych). Funkcje są domyślnie zewnętrzne, natomiast zmienne globalne są domyślnie wewnętrzne. Można to zmienić poprzez zdefiniowanie

static int func(int i) { # static linkage 
    return ++i ; 
} 

lub

/* global variable accessible from other modules (object files) */ 
extern int global_variable = 10; 

Gdy napotyka wywołanie funkcji, a nie zdefiniowane w module głównym, łącznik wyszukuje wszystkie pliki obiektu (i bibliotek) dostarczane wejście dla modułu, w którym zdefiniowana jest wywoływana funkcja. Domyślnie prawdopodobnie masz kilka bibliotek powiązanych z twoim programem, tak możesz użyć printf, jest już skompilowany do biblioteki.

Jeśli jesteś naprawdę zainteresowany, wypróbuj programowanie zespołów. Te nazwy są odpowiednikiem etykiet w kodzie zespołu.

+0

Tak więc z GCC wzór jest następujący: 1. Użyj flagi -c z każdym .c (z definicjami) i .h (z prototypami func), aby utworzyć każdy .o 2. Użyj opcji -o i każdego pliku .o, aby utworzyć końcowy exe? – Aristides

+0

Tak. opcja "-c" służy do "kompilacji", więc po prostu skompiluj kod obiektowy do plików obiektowych. gcc bez -c rozpoznaje, że dane wejściowe są plikami obiektowymi, więc po prostu łączy je razem za pomocą łącznika. I na koniec flaga -o jest opcjonalna, służy do określenia nazwy pliku wyjściowego pliku wykonywalnego. –

+2

To naprawdę dobra odpowiedź, dziękuję. –

14

Jest to linker, który obsługuje to wszystko. Kompilator właśnie emituje specjalną sekwencję w pliku obiektowym, mówiąc "Mam ten zewnętrzny symbol func, proszę go rozwiązać" dla linkera. Następnie linker widzi to i przeszukuje wszystkie inne pliki obiektów i biblioteki dla symbolu.

+0

Czy to oznacza, że ​​przeszukany zostanie cały plik '.c' w projekcie? –

+0

@LidongGuo Jeśli skompilujesz wszystkie pliki źródłowe razem w wierszu poleceń, lub jeśli utworzysz pliki obiektów ze wszystkich źródeł i połączysz je wszystkie razem, to tak, zostaną przeszukane. Nie dzieje się to jednak automatycznie, musisz powiedzieć linkerowi, które pliki obiektów chcesz połączyć, i tylko te będą przeszukiwane. –

2

Nagłówek zapewnia dostęp nie tylko do innych plików .c w tym samym programie, ale także do bibliotek, które mogą być dystrybuowane w formie binarnej. Relacja jednego pliku .c do innego jest dokładnie taka sama jak biblioteka zależna od innej.

Ponieważ interfejs programowania musi być w formie tekstowej, niezależnie od formatu implementacji, pliki nagłówkowe mają sens jako oddzielenie problemów.

Jak wspomnieli inni, program, który rozwiązuje wywołania funkcji i dostępu między bibliotekami i źródłami (jednostkami tłumaczeniowymi), nazywany jest łącznikiem.

Łącznik nie działa z nagłówkami. Po prostu tworzy dużą tabelę wszystkich nazw, które są zdefiniowane we wszystkich jednostkach tłumaczeniowych i bibliotekach, a następnie łączy te nazwy z liniami kodu, które mają do nich dostęp. Archaiczne użycie C pozwala nawet na wywołanie funkcji bez deklaracji implementacji; założono, że każdy niezdefiniowany typ to int.

3

Deklaracja symbolu bez definicji w tej samej jednostce kompilacji nakazuje kompilatorowi skompilowanie z symbolem zastępczym adresu tego symbolu w pliku obiektowym.

Łącznik zobaczy, że wymagana jest definicja symbolu i będzie szukać zewnętrznych definicji symbolu w bibliotekach i innych plikach obiektowych.

Jeśli linker znajdzie definicję, symbol zastępczy w oryginalnym pliku obiektowym zostanie zastąpiony znalezionym adresem w ostatecznym pliku wykonywalnym.

1

Generalnie podczas kompilowania pliku takiego:

gcc -o program program.c 

Naprawdę wzywają program sterownika, który wykonuje następujące czynności:

  • przerób (jeśli poprosiliśmy o to być indywidualne krok) przy użyciu cpp.
  • kompilacji (może być zintegrowany z wyprzedzającym) stosując cc1
  • montaż za pomocą as (gaz, GNU Assembler).
  • łączenie za pomocą collect2, która również używa ld (łącznik GNU).

Zwykle w ciągu pierwszych 3 etapach, należy stworzyć prosty plik obiektowy (.o przedłużacza), który zostaje utworzony przez skompilowanie jednostki kompilacji (jest to plik .c, z #include oraz innych dyrektyw zastąpiony przez preprocesor).

Czwarty etap to ten, który tworzy ostateczny plik wykonywalny. Po kompilacji jednostki kompilator zaznacza kilka kawałków kodu jako referencje, które muszą zostać rozwiązane przez linker. Zadaniem łącznika jest wyszukiwanie wśród wielu jednostek kompilacji i rozwiązywanie odniesień do zewnętrznych jednostek kompilacji.

Powiązane problemy