Jak poprawnie używać słowa kluczowego extern w C.


235

Moje pytanie dotyczy tego, kiedy do funkcji należy odwoływać się za pomocą externsłowa kluczowego w C.

Nie widzę, kiedy należy to wykorzystać w praktyce. Gdy piszę program, wszystkie funkcje, których używam, są udostępniane za pośrednictwem dołączonych plików nagłówkowych. Dlaczego więc warto externuzyskać dostęp do czegoś, co nie zostało ujawnione w pliku nagłówkowym?

Mógłbym myśleć o tym, jak externdziała niepoprawnie, a jeśli tak, proszę mnie poprawić.

Edycja: Czy powinieneś externcoś zrobić, gdy jest to domyślna deklaracja bez słowa kluczowego w pliku nagłówkowym?


Odpowiedzi:


290

extern” zmienia powiązanie. W przypadku słowa kluczowego zakłada się, że funkcja / zmienna jest dostępna gdzie indziej, a rozstrzyganie jest odraczane do linkera.

Istnieje różnica między „zewnętrznymi” funkcjami a zmiennymi: w zmiennych nie tworzy instancji samej zmiennej, tj. Nie przydziela żadnej pamięci. Trzeba to zrobić gdzie indziej. Dlatego ważne jest, jeśli chcesz zaimportować zmienną z innego miejsca. W przypadku funkcji informuje to tylko kompilator, że połączenie jest zewnętrzne. Ponieważ jest to ustawienie domyślne (używasz słowa kluczowego „statyczny”, aby wskazać, że funkcja nie jest powiązana za pomocą powiązania zewnętrznego), nie musisz jej używać jawnie.


1
dlaczego ta sama zewnętrzna cecha
rsjethani

K&R nie zauważa, że ​​domyślnie deklaruje się funkcję jako „zewnętrzną”, jednak ta odpowiedź rozwiązuje moje zamieszanie!
acgtyrant

@rsjethani Myślę, że celem jest uściślenie i sformatowanie dokumentu.
acgtyrant

Może głupie pytanie, ale jak to porównać do deklaracji forward?
weberc2

196

extern informuje kompilator, że te dane są gdzieś zdefiniowane i zostaną połączone z konsolidatorem.

Z pomocą tutaj odpowiedzi i rozmowy z kilkoma przyjaciółmi tutaj jest praktyczny przykład zastosowania zewnętrznego .

Przykład 1 - aby pokazać pułapkę:

File stdio.h:

int errno;
/* other stuff...*/

myCFile1.c:
#include <stdio.h>

Code...

myCFile2.c:
#include <stdio.h>

Code...

Jeśli pliki myCFile1.o i myCFile2.o są połączone, każdy z plików c ma osobne kopie errno . Jest to problem, ponieważ to samo errno powinno być dostępne we wszystkich połączonych plikach.

Przykład 2 - Poprawka.

File stdio.h:

extern int errno;
/* other stuff...*/

File stdio.c

int errno;

myCFile1.c:
#include <stdio.h>

Code...

myCFile2.c:
#include <stdio.h>

Code...

Teraz, jeśli oba myCFile1.o i MyCFile2.o są połączone przez linker, oba wskażą to samo errno . Zatem rozwiązanie implementacji z zewnętrzem .


70
Problemem nie jest to, że moduły myCFile1 i myCFile2 mają osobną kopię errno, tylko to, że oba pokazują symbol o nazwie „errno”. Kiedy linker to zobaczy, nie wie, które „errno” wybrać, więc wyskoczy z komunikatem o błędzie.
cwick

2
co tak naprawdę oznacza „linkowany przez linker”? wszyscy używają tego terminu, nie znajduję żadnej definicji :(
Marcel Falliere

7
@MarcelFalliere Wiki ~ Kompilator samodzielnie kompiluje każdy plik źródłowy i tworzy plik obiektowy dla każdego pliku źródłowego. Linker łączy te pliki obiektów do 1 pliku wykonywalnego.
Bitterblue,

1
@cwick gcc nie wyświetla błędu ani ostrzeżenia nawet po użyciu -Walli -pedantic. Czemu ? i jak ?
b-ak

6
Czy ochroniarz nie chroni przed tym dokładnie?
obskyr

32

Stwierdzono już, że externsłowo kluczowe jest zbędne dla funkcji.

Jeśli chodzi o zmienne współużytkowane przez jednostki kompilacyjne, należy zadeklarować je w pliku nagłówkowym za pomocą słowa kluczowego extern, a następnie zdefiniować je w jednym pliku źródłowym, bez słowa kluczowego extern. Najlepszym rozwiązaniem powinien być ten sam plik źródłowy, który ma wspólną nazwę pliku nagłówkowego.


@ aib „nadmiarowe dla funkcji”, sprawdź mój komentarz w odpowiedzi bluebrother.
rsjethani,

Co zrobić, jeśli nie chcesz udostępniać żadnej funkcji w pliku nagłówkowym? Czy nie byłoby lepiej zadeklarować zmienną w jednym pliku C i uzyskać do niej dostęp przez zewnętrzny w innym pliku; pozwól linkerowi rozwiązać problem i ukryć resztę nagłówka.
ste3e

16

Wiele lat później odkrywam to pytanie. Po przeczytaniu każdej odpowiedzi i komentarza pomyślałem, że mogę wyjaśnić kilka szczegółów ... Może to być przydatne dla osób, które docierają tutaj za pomocą wyszukiwarki Google.

Pytanie dotyczy konkretnie użycia funkcji „extern”, więc zignoruję użycie „extern” ze zmiennymi globalnymi.

Zdefiniujmy 3 prototypy funkcji:

//--------------------------------------
//Filename: "my_project.H"
extern int function_1(void);
static int function_2(void);
       int function_3(void);

Plik nagłówka może być używany przez główny kod źródłowy w następujący sposób:

//--------------------------------------
//Filename: "my_project.C"
#include "my_project.H"

void main(void){
    int v1 = function_1();
    int v2 = function_2();
    int v3 = function_3();
}

int function_2(void) return 1234;

Aby skompilować i połączyć, musimy zdefiniować „function_2” w tym samym pliku kodu źródłowego, w którym wywołujemy tę funkcję. Dwie pozostałe funkcje mogą być zdefiniowane w innym kodzie źródłowym „ .C” lub mogą znajdować się w dowolnym pliku binarnym ( .OBJ, * .LIB, * .DLL), dla którego możemy nie mieć kodu źródłowego.

Dołączmy ponownie nagłówek „my_project.H” do innego pliku „* .C”, aby lepiej zrozumieć różnicę. W tym samym projekcie dodajemy następujący plik:

//--------------------------------------
//Filename: "my_big_project_splitted.C"
#include "my_project.H"

void old_main_test(void){
    int v1 = function_1();
    int v2 = function_2();
    int v3 = function_3();
}

int function_2(void) return 5678;

int function_1(void) return 12;
int function_3(void) return 34;

Ważne funkcje, na które należy zwrócić uwagę:

  • Gdy funkcja jest zdefiniowana jako „statyczna” w pliku nagłówkowym, kompilator / linker musi znaleźć instancję funkcji o tej nazwie w każdym module, który używa tego pliku.

  • Funkcję, która jest częścią biblioteki C, można zastąpić tylko w jednym module, ponownie definiując prototyp na „statyczny” tylko w tym module. Na przykład zamień każde wywołanie na „malloc” i „free”, aby dodać funkcję wykrywania wycieków pamięci.

  • Specyfikator „extern” nie jest tak naprawdę potrzebny dla funkcji. Gdy „statyczny” nie zostanie znaleziony, zawsze przyjmuje się, że funkcja jest „zewnętrzna”.

  • Jednak „zewnętrzny” nie jest domyślny dla zmiennych. Zwykle każdy plik nagłówkowy, który definiuje zmienne widoczne w wielu modułach, musi używać „extern”. Jedynym wyjątkiem byłby przypadek, gdyby plik nagłówkowy był gwarantowany z jednego i tylko jednego modułu.

    Wielu menedżerów projektów wymagałoby wtedy umieszczenia takiej zmiennej na początku modułu, a nie w żadnym pliku nagłówka. Niektóre duże projekty, takie jak emulator gier wideo „Mame”, wymagają nawet, aby taka zmienna pojawiała się tylko nad pierwszą funkcją, która ich używa.


Dlaczego więc dokładnie funkcja statyczna potrzebuje definicji w porównaniu z zewnętrznymi? (Wiem, że to 2 lata za późno, ale tak naprawdę jest to bardzo pomocne dla zrozumienia)
SubLock69,

2
Definicja jest potrzebna, jeśli wywołujesz funkcję w linii 100 i instytujesz ją w linii 500. Linia 100 zadeklaruje niezdefiniowany prototyp. Więc dodajesz prototyp u góry.
Christian Gingras

15

W C „extern” jest implikowany dla prototypów funkcji, ponieważ prototyp deklaruje funkcję zdefiniowaną gdzie indziej. Innymi słowy, prototyp funkcji ma domyślnie zewnętrzne połączenie; użycie „extern” jest w porządku, ale jest zbędne.

(Jeśli wymagane jest połączenie statyczne, funkcja musi być zadeklarowana jako „statyczna” zarówno w swoim prototypie, jak i nagłówku funkcji, i zwykle oba powinny znajdować się w tym samym pliku .c).


8

Bardzo dobry artykuł na temat externsłowa kluczowego, wraz z przykładami: http://www.geeksforgeeks.org/understanding-extern-keyword-in-c/

Chociaż nie zgadzam się, że używanie externdeklaracji funkcji jest zbędne. To powinno być ustawienie kompilatora. Dlatego zalecam używanie externdeklaracji funkcji w razie potrzeby.


3
Przeczytałem artykuł geeksforgeeks.org, zanim tu przybyłem, ale okazało się, że jest dość źle napisany. Poza niedociągnięciami gramatycznymi i składniowymi używa wielu słów, aby kilkakrotnie wskazać ten sam punkt, a następnie przegląda krytyczne informacje. Na przykład w przykładzie 4 nagle dołączono „somefile.h”, ale nie mówi się o tym inaczej niż: „Załóżmy, że somefile.h ma definicję var”. Cóż, informacje, które „przypuszczamy”, są po prostu informacjami, których szukam. Niestety, żadne odpowiedzi na tej stronie nie są znacznie lepsze.
Elise van Looij

6

Jeśli każdy plik w twoim programie jest najpierw kompilowany do pliku obiektowego, to pliki obiektowe są ze sobą połączone, potrzebujesz extern. Mówi kompilatorowi: „Ta funkcja istnieje, ale jej kod jest gdzie indziej. Nie panikuj”.


Hmm, tak zwykle wykonuje się tłumaczenie: pliki źródłowe kompilują się do plików obiektowych, a następnie są łączone. Kiedy w takim przypadku nie potrzebujesz zewnętrznej? Nie użyłbyś też #include, aby uzyskać funkcje, ale raczej prototypy funkcji. Nie rozumiem o czym mówisz.
David Thornley,

Wydaje mi się, że ostatnio mam ten problem z błędnym odczytaniem. Przepraszam za to. Kiedy byłem nowy w C, #include „file.c”, aby po prostu włączyć funkcje w jednym pliku bezpośrednio do drugiego pliku. Potem wymyśliłem, jak używać „zewnętrznego”. Myślałem, że popełnia ten sam błąd, co ja.
Chris Lutz

4

Wszystkie deklaracje funkcji i zmiennych w plikach nagłówkowych powinny być extern.

Wyjątkami od tej reguły są funkcje wbudowane zdefiniowane w nagłówku i zmienne, które - choć zdefiniowane w nagłówku - będą musiały być lokalne dla jednostki tłumaczącej (plik źródłowy, do którego dołącza się nagłówek): powinny być static.

W plikach źródłowych externnie należy używać do funkcji i zmiennych zdefiniowanych w pliku. Po prostu poprzedź lokalne definicje statici nie rób nic dla wspólnych definicji - domyślnie będą to symbole zewnętrzne.

Jedynym powodem, aby externw ogóle korzystać z pliku źródłowego, jest deklarowanie funkcji i zmiennych zdefiniowanych w innych plikach źródłowych, dla których nie podano pliku nagłówkowego.


Deklarowanie prototypów funkcji externjest w rzeczywistości niepotrzebne. Niektórym się to nie podoba, ponieważ będzie to po prostu marnować przestrzeń, a deklaracje funkcji mają już tendencję do przekraczania limitów linii. Inni to lubią, ponieważ w ten sposób funkcje i zmienne można traktować w ten sam sposób.


Czy możesz podać powód, dla którego „wszystkie deklaracje funkcji i zmiennych w plikach nagłówkowych powinny być zewnętrzne”? Z pozostałych odpowiedzi wynika, że ​​domyślnie są one zewnętrzne.
lillq

@Lane: externjest opcjonalny dla deklaracji funkcji, ale lubię traktować zmienne i funkcje w ten sam sposób - przynajmniej to najbardziej rozsądna rzecz, jaką mogłem wymyślić, ponieważ nie pamiętam dokładnie, dlaczego to zrobiłem;)
Christoph

Czy nie jest lepszym pomysłem, aby zawsze dołączać zmienne globalne do pliku C, aby nie były widoczne przez inne losowe pliki C zawierające nagłówek. I zawsze używać zewnętrznego na każdym globu, z wyjątkiem zainicjowanego prawdziwego ujścia, dla jasności; jeśli ma prefiks zewnętrzny, to jest zdefiniowany gdzie indziej.
ste3e

3

Funkcje faktycznie zdefiniowane w innych plikach źródłowych powinny być deklarowane tylko w nagłówkach. W takim przypadku powinieneś użyć extern podczas deklarowania prototypu w nagłówku.

Przez większość czasu twoje funkcje będą jedną z następujących (bardziej jak najlepsza praktyka):

  • statyczne (normalne funkcje, które nie są widoczne poza tym plikiem .c)
  • static inline (inline z plików .c lub .h)
  • extern (deklaracja w nagłówkach następnego rodzaju (patrz poniżej))
  • [bez słowa kluczowego] (normalne funkcje mają być dostępne za pomocą deklaracji zewnętrznych)

Dlaczego miałbyś się denerwować, deklarując prototyp, jeśli jest to domyślny?
lillq

@Lane: Może być trochę stronniczy, ale każdy rozsądny projekt, nad którym pracowałem, stosuje następującą konwencję: w nagłówkach deklaruj prototypy tylko dla funkcji zewnętrznych (stąd extern). W plikach .c można użyć zwykłych prototypów, aby uniknąć konieczności specjalnego zamawiania, ale nie należy ich umieszczać w nagłówkach.
Eduard - Gabriel Munteanu

1

Gdy masz tę funkcję zdefiniowaną w innej bibliotece dll lub lib, aby kompilator odroczył linker, aby ją znaleźć. Typowym przypadkiem jest wywoływanie funkcji z interfejsu API systemu operacyjnego.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.