Dlaczego możesz mieć definicję metody w pliku nagłówkowym w C ++, skoro w C nie możesz?


23

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:


28

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.)


2
To nie jest taki zły pomysł - tego rodzaju rzeczy można znaleźć nawet w nagłówkach GNU Libc.
— SK-logic

ale co z idiomatycznym zawijaniem pliku nagłówkowego w warunkowej dyrektywie kompilacji? Następnie, nawet z funkcją zadeklarowaną AND w nagłówku, zostanie załadowana tylko raz. Jestem nowy w C, więc mogę się nieporozumieć.
— user305964,

2
@papiro Problem polega na tym, że owijanie chroni tylko podczas jednego uruchomienia kompilatora. Więc jeśli foo.c jest skompilowany do foo.o w jednym uruchomieniu, bar.c do bar.o w innym, a foo.o i bar.o są połączone w a.out w trzecim (jak to typowe), to opakowanie nie zapobiega wielu jego wystąpieniom, po jednym w każdym pliku obiektowym.
— David Conrad

Czy opisany tutaj problem nie #ifndef HEADER_Hpowinien zapobiec?
— Robert Harvey,

27

C i C ++ zachowują się bardzo podobnie pod tym względem - możesz mieć inlinefunkcje 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.


„ zadeklaruj funkcjestatic inline ” ... a nadal będziesz mieć wiele kopii funkcji w każdej jednostce tłumaczeniowej, która z niej korzysta. W C ++ z static inlineniefunkcją 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();.).
— Ruslan

6

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.


3

Możesz to zrobić w C99: inlinefunkcje są zapewnione gdzie indziej, więc jeśli funkcja nie została wstawiona, jej definicja jest tłumaczona na deklarację (tzn. Implementacja jest odrzucana). I oczywiście możesz użyć static.


1

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::myMethodsymbol 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ą.


-4

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.


1
Nie, naprawdę jest prawdziwa odpowiedź, a jednym z głównych celów C ++ była zgodność wsteczna z C.
— Ed S.

4
Nie, został zaprojektowany tak, aby miał „wysoki stopień kompatybilności z C” i „brak darmowych niezgodności z C”. (oba z Stroustrup). Zgadzam się, że można udzielić bardziej szczegółowej odpowiedzi, aby podkreślić, dlaczego ta szczególna niezgodność nie jest nieuzasadniona. Zapraszam do dostarczenia jednego.
— Paul Butcher,

Zrobiłbym to, ale Simon Richter już to zrobił, kiedy to opublikowałem. Możemy spierać się o różnicę między „kompatybilnością wsteczną” a „zgodnością wysokiego stopnia C”, ale faktem jest, że ta odpowiedź jest nieprawidłowa. Ostatnia instrukcja byłaby poprawna, gdybyśmy porównali C # i C ++, ale nie tak bardzo z C i C ++.
— Ed S.
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.