Jak działają pliki nagłówkowe i źródłowe w C?

Przejrzałem możliwe duplikaty, jednak żadna z odpowiedzi nie tonie.

Tl; dr: w jaki sposób pliki źródłowe i nagłówkowe są powiązane w C? Czy projekty domyślnie sortują zależności deklaracji/definicji w czasie budowania?

Próbuję zrozumieć, w jaki sposób kompilator rozumie związek pomiędzy .c i .h plikami.

Biorąc pod uwagę te pliki:

Nagłówek.h :

int returnSeven(void);

Źródło.c:

int returnSeven(void){
    return 7;
}

Main.c:

#include <stdio.h>
#include <stdlib.h>
#include "header.h"
int main(void){
    printf("%d", returnSeven());
    return 0;
}

Czy ten bałagan się skompiluje? Obecnie pracuję w NetBeans 7.0 z gcc z Cygwina, który automatyzuje większość zadań budowania. Kiedy projekt jest kompilowany, czy zaangażowane pliki projektu uporządkują to Ukryte włączenie source.c na podstawie deklaracji w header.h?

Author: durron597, 2011-05-06

5 answers

Konwersja plików kodu źródłowego C do programu wykonywalnego odbywa się zwykle w dwóch krokach: kompilowanie i łączenie .

Najpierw kompilator konwertuje kod źródłowy na pliki obiektowe (*.o). Następnie linker pobiera te pliki obiektowe wraz ze statycznie powiązanymi bibliotekami i tworzy program wykonywalny.

W pierwszym kroku kompilator pobiera jednostkę kompilacji, która zwykle jest wstępnie przetworzonym plikiem źródłowym (czyli plikiem źródłowym z zawartość wszystkich nagłówków, które #includes) i konwertuje je do pliku obiektowego.

W każdej jednostce kompilacji wszystkie użyte funkcje muszą być zadeklarowane , aby kompilator wiedział, że funkcja istnieje i jakie są jej argumenty. W twoim przykładzie deklaracja funkcji returnSeven znajduje się w pliku nagłówkowym header.h. Kiedy kompilujesz main.c, dołączasz nagłówek z deklaracją, aby kompilator wiedział, że returnSeven istnieje, gdy kompiluje main.c.

Kiedy linker wykonuje swoje zadanie, musi znaleźć definicję każdej funkcji. Każda funkcja musi być zdefiniowana dokładnie raz w jednym z plików obiektowych - jeśli istnieje wiele plików obiektowych, które zawierają definicję tej samej funkcji, linker zatrzyma się z błędem.

Twoja funkcja returnSeven jest zdefiniowana w source.c (a main funkcja jest zdefiniowana w main.c).

Podsumowując, masz dwie jednostki kompilacji: source.c i main.c (z pliki nagłówkowe, które zawiera). Kompilujesz je do dwóch plików obiektowych: source.o i main.o. Pierwsza z nich będzie zawierać definicję returnSeven, druga definicję main. Następnie linker sklejy te dwa Razem w programie wykonywalnym.

O linkowaniu:

Istnieje zewnętrzne połączenie i wewnętrzne połączenie . Domyślnie funkcje mają zewnętrzne powiązania, co oznacza, że kompilator sprawia, że funkcje te są widoczne dla linkera. Jeśli zrobisz funkcja static, posiada wewnętrzne powiązanie - jest widoczna tylko wewnątrz jednostki kompilacji, w której jest zdefiniowana(linker nie będzie wiedział, że istnieje). Może to być przydatne dla funkcji, które robią coś wewnętrznie w pliku źródłowym i które chcesz ukryć przed resztą programu.

 63
Author: Jesper,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-05-05 22:16:34

Język C nie ma pojęcia o plikach źródłowych i plikach nagłówkowych (podobnie jak kompilator). Jest to tylko konwencja; pamiętaj, że plik nagłówkowy jest zawsze #included w pliku źródłowym; preprocesor dosłownie po prostu kopiuje-wkleja zawartość, zanim rozpocznie się właściwa kompilacja.

Twój przykład powinien skompilować (bez względu na głupie błędy składniowe). Na przykład używając GCC, możesz najpierw wykonać:

gcc -c -o source.o source.c
gcc -c -o main.o main.c

Kompiluje każdy plik źródłowy osobno, tworzenie niezależnych plików obiektowych. Na tym etapie returnSeven() nie został rozwiązany wewnątrz main.c; kompilator tylko oznaczył plik obiektowy w sposób, który stwierdza, że musi zostać rozwiązany w przyszłości. Więc na tym etapie nie jest problemem, że main.c nie widzi definicji z returnSeven(). (Uwaga: jest to odmienne od faktu, że main.c musi być w stanie zobaczyć deklarację z returnSeven() w celu kompilacji; musi wiedzieć, że rzeczywiście jest to funkcja i jaki jest jej prototyp. Że dlatego musisz #include "source.h" W main.c.)

Potem robisz:

gcc -o my_prog source.o main.o

To łączy dwa pliki obiektowe razem w wykonywalny plik binarny i wykonuje rozdzielczość symboli. W naszym przykładzie jest to możliwe, ponieważ main.o wymaga returnSeven(), a to jest ujawnione przez source.o. W przypadkach, gdy wszystko się nie zgadza, spowodowałby błąd łącznika.

 25
Author: Oliver Charlesworth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-05-05 22:08:15

Nie ma nic magicznego w kompilacji. Ani automatyczne!

Pliki nagłówkowe w zasadzie dostarczają informacji kompilatorowi, prawie nigdy nie kodują.
Sama ta informacja zwykle nie wystarcza do stworzenia pełnego programu.

Rozważ program "hello world" (z prostszą funkcją puts):

#include <stdio.h>
int main(void) {
    puts("Hello, World!");
    return 0;
}

Bez nagłówka kompilator nie wie, jak radzić sobie z puts() (nie jest to słowo kluczowe C). Nagłówek pozwala kompilatorowi wiedzieć, jak zarządzać argumentami i zwracana wartość.

Sposób działania funkcji nie jest jednak nigdzie określony w tym prostym kodzie. Ktoś inny napisał kod puts() i włączył skompilowany kod do biblioteki. Kod w tej bibliotece jest dołączany do skompilowanego kodu źródłowego w ramach procesu kompilacji.

Teraz Uznaj, że chciałeś mieć własną wersję puts()

int main(void) {
    myputs("Hello, World!");
    return 0;
}

Kompilowanie tylko tego kodu powoduje błąd, ponieważ kompilator nie ma informacji o funkcji. Ty może dostarczyć te informacje

int myputs(const char *line);
int main(void) {
    myputs("Hello, World!");
    return 0;
}

A kod teraz kompiluje - - - ale nie linkuje, ie nie tworzy pliku wykonywalnego, ponieważ nie ma kodu dla myputs(). Więc piszesz kod dla myputs() w pliku o nazwie " myputs.c "

#include <stdio.h>
int myputs(const char *line) {
    while (*line) putchar(*line++);
    return 0;
}

I musisz pamiętać, aby skompilować zarówno swój pierwszy plik źródłowy, jak i " myputs.c " razem.

Po chwili twoje " myputs.c " plik rozszerzył się do ręki pełnej funkcji i trzeba zawierać informacje o wszystkich funkcje (ich prototypy) w plikach źródłowych, które chcą z nich korzystać.
Wygodniej jest zapisać wszystkie prototypy w jednym pliku i #include tego pliku. Dzięki włączeniu nie ryzykujesz popełnienia błędu podczas pisania prototypu.

Nadal musisz skompilować i połączyć wszystkie pliki kodu razem.


Gdy rosną jeszcze bardziej, umieszczasz cały skompilowany kod w bibliotece ... i to już inna historia:)

 11
Author: pmg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-05-05 22:25:17

Pliki nagłówkowe są używane do oddzielania deklaracji interfejsu, które odpowiadają implementacjom w plikach źródłowych. Są maltretowani w inny sposób, ale jest to powszechny przypadek. To nie jest dla kompilatora, tylko dla ludzi piszących kod.

Większość kompilatorów nie widzi tych dwóch plików osobno, są one połączone przez preprocesor.

 4
Author: Hack Saw,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-05-05 22:01:08

Sam kompilator nie ma specyficznej "wiedzy" o relacjach między plikami źródłowymi a plikami nagłówkowymi. Te typy relacji są zazwyczaj definiowane przez pliki projektu (np. makefile, solution, itp.).

Podany przykład wygląda tak, jakby skompilował się poprawnie. Trzeba by skompilować oba pliki źródłowe, a następnie linker będzie potrzebował obu plików obiektowych do wytworzenia pliku wykonywalnego.

 2
Author: Mark Wilkins,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-05-05 21:58:52