W C nie można mieć definicji / implementacji funkcji w pliku nagłówkowym. Jednak w C ++ możesz mieć pełną implementację metody w pliku nagłówkowym. Dlaczego zachowanie jest inne?
W C nie można mieć definicji / implementacji funkcji w pliku nagłówkowym. Jednak w C ++ możesz mieć pełną implementację metody w pliku nagłówkowym. Dlaczego zachowanie jest inne?
Odpowiedzi:
W C, jeśli zdefiniujesz funkcję w pliku nagłówka, wówczas funkcja ta pojawi się w każdym skompilowanym module zawierającym ten plik nagłówka, a dla funkcji zostanie wyeksportowany symbol publiczny. Jeśli więc dodatek funkcji jest zdefiniowany w nagłówku.h, a zarówno foo.c, jak i bar.c zawierają nagłówek.h, wówczas zarówno foo.o, jak i bar.o będą zawierać kopie dodatku.
Kiedy przejdziesz do połączenia tych dwóch plików obiektowych razem, linker zobaczy, że sumowanie symboli jest zdefiniowane więcej niż raz i nie pozwoli na to.
Jeśli zadeklarujesz funkcję jako statyczną, żaden symbol nie zostanie wyeksportowany. Pliki obiektowe foo.o i bar.o nadal będą zawierać osobne kopie kodu dla funkcji i będą mogły z nich korzystać, ale linker nie będzie w stanie zobaczyć żadnej kopii funkcji, więc nie będę narzekać. Oczywiście żaden inny moduł również nie będzie w stanie zobaczyć tej funkcji. A twój program będzie rozdęty dwiema identycznymi kopiami tej samej funkcji.
Jeśli zadeklarujesz funkcję tylko w pliku nagłówkowym, ale nie zdefiniujesz jej, a następnie zdefiniujesz tylko w jednym module, linker zobaczy jedną kopię funkcji, a każdy moduł w twoim programie będzie mógł ją zobaczyć i Użyj tego. Twój skompilowany program będzie zawierał tylko jedną kopię funkcji.
Tak więc możesz mieć definicję funkcji w pliku nagłówkowym w C, jest to po prostu zły styl, zła forma i ogólnie zły pomysł.
(Przez „zadeklaruj” mam na myśli zapewnienie prototypu funkcji bez ciała; przez „zdefiniowanie” mam na myśli faktyczny kod ciała funkcji; jest to standardowa terminologia w języku C.)
#ifndef HEADER_H
powinien zapobiec?
C i C ++ zachowują się bardzo podobnie pod tym względem - możesz mieć inline
funkcje w nagłówkach. W C ++ każda metoda, której treść znajduje się w definicji klasy, jest niejawna inline
. Jeśli chcesz zrobić to samo w C, zadeklaruj funkcje static inline
.
static inline
” ... a nadal będziesz mieć wiele kopii funkcji w każdej jednostce tłumaczeniowej, która z niej korzysta. W C ++ z static
inline
niefunkcją będziesz miał tylko jedną kopię. Aby faktycznie mieć implementację w nagłówku w C, musisz 1) oznaczyć implementację jako inline
(np. inline void func(){do_something();}
) I 2) faktycznie powiedzieć, że ta funkcja będzie w określonej jednostce tłumaczeniowej (np void func();
.).
Pojęcie pliku nagłówkowego wymaga małego wyjaśnienia:
Albo podasz plik w wierszu poleceń kompilatora, albo zrobisz „#include”. Większość kompilatorów akceptuje plik poleceń z rozszerzeniem c, C, cpp, c ++ itp. Jako plik źródłowy. Zazwyczaj zawierają one jednak opcję wiersza polecenia, aby umożliwić użycie dowolnego dowolnego rozszerzenia do pliku źródłowego.
Zasadniczo plik podany w wierszu poleceń nosi nazwę „Źródło”, a dołączony plik nazywa się „Nagłówek”.
Krok preprocesora faktycznie bierze je wszystkie i sprawia, że wszystko wydaje się kompilatorowi jak jeden duży plik. To, co było w nagłówku lub w źródle, nie jest w tym momencie istotne. Zwykle istnieje opcja kompilatora, który może wyświetlić dane wyjściowe tego etapu.
Tak więc dla każdego pliku, który został podany w linii poleceń kompilatora, kompilator otrzymuje ogromny plik. Może zawierać kod / dane, które zajmują pamięć i / lub tworzą symbol, do którego można się odwoływać z innych plików. Teraz każdy z nich wygeneruje obraz „obiektu”. Linker może nadać „duplikatowi symbol”, jeśli ten sam symbol zostanie znaleziony w więcej niż dwóch plikach obiektowych, które są ze sobą połączone. Być może to jest powód; nie zaleca się umieszczania kodu w pliku nagłówkowym, który może tworzyć symbole w pliku obiektowym.
„Inline” są zwykle wstawiane. Ale podczas debugowania mogą nie być wstawiane. Dlaczego więc linker nie podaje wielokrotnie zdefiniowanych błędów? Proste ... Są to „słabe” symbole i tak długo, jak wszystkie dane / kod słabego symbolu ze wszystkich obiektów mają ten sam rozmiar i treść, połączony zachowa jedną kopię i upuści kopię z innych obiektów. To działa.
C ++ standardowe cytaty
C ++ 17 N4659 standardowy projekt 10.1.6 „specyfikatorem inline” mówi, że metody są niejawnie inline:
4 Funkcja zdefiniowana w definicji klasy jest funkcją wbudowaną.
a następnie w dalszej części widzimy, że metody wbudowane nie tylko mogą, ale muszą być zdefiniowane we wszystkich jednostkach tłumaczeniowych:
6 Funkcja wbudowana lub zmienna powinna być zdefiniowana w każdej jednostce tłumaczenia, w której jest używana odr i powinna mieć dokładnie taką samą definicję w każdym przypadku (6.2).
Jest to również wyraźnie wspomniane w nocie 12.2.1 „Funkcje członkowskie”:
1 Funkcja składowa może być zdefiniowana (11.4) w definicji klasy, w którym to przypadku jest to wbudowana funkcja składowa (10.1.6) [...]
3 [Uwaga: W programie może istnieć co najwyżej jedna definicja nieliniowej funkcji składowej. W programie może znajdować się więcej niż jedna definicja wbudowanej funkcji składowej. Patrz 6.2 i 10.1.6. - uwaga końcowa]
Implementacja GCC 8.3
main.cpp
struct MyClass {
void myMethod() {}
};
int main() {
MyClass().myMethod();
}
Kompiluj i przeglądaj symbole:
g++ -c main.cpp
nm -C main.o
wynik:
U _GLOBAL_OFFSET_TABLE_
0000000000000000 W MyClass::myMethod()
U __stack_chk_fail
0000000000000000 T main
następnie widzimy, man nm
że MyClass::myMethod
symbol jest oznaczony jako słaby w plikach obiektowych ELF, co oznacza, że może pojawić się w plikach wielu obiektów:
„W” „w” Symbol jest słabym symbolem, który nie został specjalnie oznaczony jako symbol słabego obiektu. Kiedy słaby zdefiniowany symbol jest połączony z normalnie zdefiniowanym symbolem, normalnie zdefiniowany symbol jest używany bez błędu. Kiedy słaby niezdefiniowany symbol jest połączony, a symbol nie jest zdefiniowany, wartość symbolu jest określana w sposób specyficzny dla systemu bez błędu. W niektórych systemach wielkie litery wskazują, że określono wartość domyślną.
Prawdopodobnie z tego samego powodu, dla którego musisz umieścić pełną implementację metody w definicji klasy w Javie.
Mogą wyglądać podobnie, z nawiasami kwadratowymi i wieloma takimi samymi słowami kluczowymi, ale są to różne języki.