Czy „inline” bez „static” lub „extern” jest kiedykolwiek przydatne w C99?

96

Kiedy próbuję zbudować ten kod

inline void f() {}

int main()
{
    f();
}

za pomocą wiersza poleceń

gcc -std=c99 -o a a.c

Otrzymuję błąd konsolidatora (niezdefiniowane odniesienie do f). Błąd znika, jeśli używam static inlinelub extern inlinezamiast po prostu inline, lub jeśli kompiluję z -O(więc funkcja jest faktycznie wbudowana).

Wydaje się, że zachowanie to jest zdefiniowane w pkt 6.7.4 (6) normy C99:

Jeśli wszystkie deklaracje zakresu plików dla funkcji w jednostce translacji zawierają specyfikator inlinefunkcji bez extern, to definicja w tej jednostce translacji jest definicją wbudowaną. Definicja wbudowana nie zapewnia zewnętrznej definicji funkcji i nie zabrania umieszczania zewnętrznej definicji w innej jednostce tłumaczeniowej. Definicja wbudowana stanowi alternatywę dla definicji zewnętrznej, której tłumacz może użyć do zaimplementowania dowolnego wywołania funkcji w tej samej jednostce tłumaczeniowej. Nie jest określone, czy wywołanie funkcji używa definicji wbudowanej, czy definicji zewnętrznej.

Jeśli wszystko dobrze rozumiem, jednostka kompilacji z funkcją zdefiniowaną inlinejak w powyższym przykładzie kompiluje się konsekwentnie tylko wtedy, gdy istnieje również funkcja zewnętrzna o tej samej nazwie i nigdy nie wiem, czy wywoływana jest moja własna funkcja, czy funkcja zewnętrzna.

Czy to zachowanie nie jest całkowicie głupie? Czy kiedykolwiek warto zdefiniować funkcję inlinebez staticlub externw C99? Czy coś mi brakuje?

Podsumowanie odpowiedzi

Oczywiście czegoś mi brakowało, a zachowanie nie jest głupie. :)

Jak wyjaśnia Nemo , chodzi o to, aby umieścić definicję funkcji

inline void f() {}

w pliku nagłówkowym i tylko deklaracja

extern inline void f();

w odpowiednim pliku c. Tylko externdeklaracja wyzwala generowanie widocznego na zewnątrz kodu binarnego. I rzeczywiście nie ma zastosowania inlinew pliku .c - jest użyteczne tylko w nagłówkach.

Jak wyjaśnia uzasadnienie komitetu C99 przytoczone w odpowiedzi Jonathana , inlinechodzi o optymalizacje kompilatora, które wymagają, aby definicja funkcji była widoczna w miejscu wywołania. Można to osiągnąć tylko poprzez umieszczenie definicji w nagłówku i oczywiście definicja w nagłówku nie może emitować kodu za każdym razem, gdy jest widziany przez kompilator. Ale ponieważ kompilator nie jest zmuszony do faktycznego wstawiania funkcji, musi gdzieś istnieć zewnętrzna definicja.

Sven Marnach
źródło
borderline duplicate of stackoverflow.com/questions/5369888/…
Earlz,
@Earlz: Dzięki za link. Moje pytanie dotyczy uzasadnienia cytowanego paragrafu standardu i czy kiedykolwiek są przypadki użycia inlinebez statici extern, chociaż. Niestety, żadna z tych kwestii nie została poruszona w tym pytaniu.
Sven Marnach
@Nemo: Przeczytałem to pytanie i odpowiedzi, zanim opublikowałem moje. Znowu nie pytam „jak to się zachowuje?” ale raczej „jaka jest idea tego zachowania”? Jestem prawie pewien, że czegoś tu brakuje.
Sven Marnach
1
tak, dlatego powiedziałem „borderline”. Osobiście uważam, że to jest zupełnie inne pytanie
Earlz,

Odpowiedzi:

40

Właściwie ta doskonała odpowiedź również odpowiada na twoje pytanie, myślę:

Co robi extern inline?

Pomysł jest taki, że „inline” może być użyte w pliku nagłówkowym, a następnie „extern inline” w pliku .c. „extern inline” to po prostu sposób, w jaki instruujesz kompilator, który plik obiektowy powinien zawierać (widoczny z zewnątrz) wygenerowany kod.

[aktualizacja, rozwinięcie]

Wydaje mi się, że w pliku .c nie ma pożytku z wyrażenia „inline” (bez „static” lub „extern”). Ale w pliku nagłówkowym ma to sens i wymaga odpowiedniej deklaracji „extern inline” w jakimś pliku .c, aby faktycznie wygenerować samodzielny kod.

Nemo
źródło
1
Dzięki, zaczynam rozumieć!
Sven Marnach
8
Tak, sam tego nigdy nie rozumiałem, aż do teraz, więc dziękuję, że pytasz :-). Dziwne jest to, że definicja „inline” niebędąca extern umieszczana jest w nagłówku (ale niekoniecznie skutkuje w ogóle generowaniem kodu), podczas gdy deklaracja „extern inline” trafia do pliku .c i faktycznie powoduje, że kod być generowane.
Nemo
3
Czy to oznacza, że ​​piszę inline void f() {}w nagłówku i extern inline void f();w pliku .c? Czyli rzeczywista definicja funkcji jest umieszczana w nagłówku, a plik .c zawiera w tym przypadku zwykłą deklarację, w odwrotnej kolejności?
Sven Marnach
1
@endolith: Nie rozumiem twojego pytania. Dyskutujemy inline bez static lub extern. Oczywiście static inlinejest w porządku, ale nie o to chodzi w tym pytaniu i odpowiedzi.
Nemo
2
@MatthieuMoy Musisz użyć -std=c99zamiast -std=gnu89.
a3f
27

Z samej normy (ISO / IEC 9899: 1999):

Dodatek J.2 Niezdefiniowane zachowanie

  • ...
  • Funkcja z połączeniem zewnętrznym jest deklarowana za pomocą inlinespecyfikatora funkcji, ale nie jest również definiowana w tej samej jednostce tłumaczeniowej (6.7.4).
  • ...

Komitet C99 napisał uzasadnienie i mówi:

6.7.4 Specyfikatory funkcji

Nowością C99:inline słów kluczowych, zaadaptowany z C ++, to funkcja-specifier , które mogą być wykorzystane tylko w deklaracji funkcji. Jest to przydatne w przypadku optymalizacji programów, które wymagają, aby definicja funkcji była widoczna w miejscu wywołania. (Należy zwrócić uwagę, że standard nie podejmuje próby określenia charakteru tych optymalizacji).

Widoczność jest zapewniona, jeśli funkcja ma powiązanie wewnętrzne lub jeśli ma powiązanie zewnętrzne, a wywołanie jest w tej samej jednostce tłumaczeniowej co definicja zewnętrzna. W takich przypadkach obecność inlinesłowa kluczowego w deklaracji lub definicji funkcji nie ma żadnego skutku poza wskazaniem preferencji, że wywołania tej funkcji powinny być optymalizowane w stosunku do wywołań innych funkcji zadeklarowanych bez inlinesłowa kluczowego.

Widoczność jest problemem w przypadku wywołania funkcji z łączem zewnętrznym, gdy wywołanie jest w innej jednostce tłumaczeniowej niż definicja funkcji. W tym przypadku inlinesłowo kluczowe pozwala jednostce tłumaczeniowej zawierającej wywołanie zawierać również lokalną lub wbudowaną definicję funkcji.

Program może zawierać jednostkę tłumaczeniową z definicją zewnętrzną, jednostkę tłumaczeniową z definicją wbudowaną oraz jednostkę tłumaczeniową z deklaracją, ale bez definicji funkcji. Połączenia w drugiej jednostce tłumaczeniowej będą normalnie używać definicji zewnętrznej.

Uznaje się, że wbudowana definicja funkcji jest inną definicją niż definicja zewnętrzna. Jeśli wywołanie jakiejś funkcji funcz łączeniem zewnętrznym występuje, gdy widoczna jest definicja wbudowana, zachowanie jest takie samo, jak w przypadku wywołania innej funkcji, powiedzmy __func, z połączeniem wewnętrznym. Program zgodny nie może zależeć od wywoływanej funkcji. To jest model inline w standardzie.

Program zgodny nie może polegać na implementacji używającej definicji wbudowanej ani nie może polegać na implementacji korzystającej z definicji zewnętrznej. Adres funkcji jest zawsze adresem odpowiadającym definicji zewnętrznej, ale gdy ten adres jest używany do wywołania funkcji, można użyć definicji wbudowanej. Dlatego poniższy przykład może nie zachowywać się zgodnie z oczekiwaniami.

inline const char *saddr(void)
{
    static const char name[] = "saddr";
    return name;
}
int compare_name(void)
{
    return saddr() == saddr(); // unspecified behavior
}

Ponieważ implementacja może używać definicji wbudowanej dla jednego z wywołań saddri używać definicji zewnętrznej dla drugiego, operacja równości nie jest gwarantowana do wartości 1 (prawda). Pokazuje to, że obiekty statyczne zdefiniowane w definicji wbudowanej różnią się od odpowiadających im obiektów w definicji zewnętrznej. To zmotywowało ograniczenie nawet do zdefiniowania constobiektu niebędącego przedmiotem tego typu.

Inlining został dodany do Standardu w taki sposób, że można go zaimplementować z istniejącą technologią konsolidatora, a podzbiór inliningu C99 jest kompatybilny z C ++. Osiągnięto to poprzez wymaganie, aby dokładnie jedna jednostka translacyjna zawierająca definicję funkcji wbudowanej była określona jako ta, która zapewnia zewnętrzną definicję funkcji. Ponieważ ta specyfikacja składa się po prostu z deklaracji, która albo nie zawiera inlinesłowa kluczowego, albo zawiera oba inlinei extern, zostanie również zaakceptowana przez tłumacza C ++.

Inlining w C99 rozszerza specyfikację C ++ na dwa sposoby. Po pierwsze, jeśli funkcja jest zadeklarowana inlinew jednej jednostce tłumaczeniowej, nie musi być deklarowana inlinew każdej innej jednostce tłumaczeniowej. Pozwala to, na przykład, na funkcję biblioteczną, która ma być wbudowana w bibliotekę, ale dostępna tylko poprzez zewnętrzną definicję w innym miejscu. Alternatywa użycia funkcji opakowującej dla funkcji zewnętrznej wymaga dodatkowej nazwy; może to również negatywnie wpłynąć na wydajność, jeśli tłumacz w rzeczywistości nie wykonuje zastępowania w tekście.

Po drugie, wymóg, aby wszystkie definicje funkcji wbudowanej były „dokładnie takie same”, zostaje zastąpiony wymogiem, że zachowanie programu nie powinno zależeć od tego, czy wywołanie jest implementowane z widoczną definicją wbudowaną, czy też definicją zewnętrzną funkcjonować. Pozwala to na wyspecjalizowanie definicji wbudowanej w celu jej wykorzystania w określonej jednostce tłumaczeniowej. Na przykład zewnętrzna definicja funkcji bibliotecznej może zawierać sprawdzanie poprawności argumentów, które nie jest potrzebne w przypadku wywołań wykonywanych z innych funkcji w tej samej bibliotece. Te rozszerzenia mają pewne zalety; a programiści, którzy są zaniepokojeni kompatybilnością, mogą po prostu przestrzegać bardziej rygorystycznych reguł C ++.

Należy pamiętać, że to jest nie odpowiednie dla implementacje zapewnienie inline definicje standardowych funkcji bibliotecznych w standardowych nagłówków, ponieważ może to złamać trochę starszy kod redeclares standardowe funkcje biblioteczne po tym ich nagłówki. To inlinesłowo kluczowe ma na celu jedynie zapewnienie użytkownikom przenośnego sposobu sugerowania wbudowanych funkcji. Ponieważ standardowe nagłówki nie muszą być przenośne, implementacje mają inne opcje, takie jak:

#define abs(x) __builtin_abs(x)

lub inne nieprzenośne mechanizmy wstawiania standardowych funkcji bibliotecznych.

Jonathan Leffler
źródło
Dzięki, jest to bardzo szczegółowe - i prawie tak czytelne, jak sam standard. :-) Wiedziałem, że czegoś mi brakuje. Czy możesz podać odniesienie, skąd to masz?
Sven Marnach
Po prostu przyszło
Sven Marnach
@Sven: pożyczyłem adres URL z twojego wyszukiwania i umieściłem go w odpowiedzi. Dokument, którego użyłem, był kopią uzasadnienia, które zapisałem wcześniej (w 2005 r.), Ale był to również V5.10.
Jonathan Leffler
4
Dzięki jeszcze raz. Najcenniejszym wnioskiem, jaki uzyskałem z odpowiedzi, jest to, że istnieje dokument zawierający uzasadnienie standardu C99 (i prawdopodobnie są też dokumenty dotyczące innych norm). Podczas gdy odpowiedź Nemo dała mi rybę, ta nauczyła mnie łowić.
Sven Marnach
0

> Pojawia się błąd konsolidatora (niezdefiniowane odniesienie do f)

Działa tutaj: Linux x86-64, GCC 4.1.2. Może to być błąd w twoim kompilatorze; Nie widzę w cytowanym akapicie ze standardu niczego, co zabrania danego programu. Zwróć uwagę na użycie if zamiast iff .

Definicja wbudowana stanowi alternatywę dla definicji zewnętrznej , której tłumacz może użyć do zaimplementowania dowolnego wywołania funkcji w tej samej jednostce tłumaczeniowej.

Tak więc, jeśli znasz zachowanie funkcji fi chcesz wywołać ją w ścisłej pętli, możesz skopiować i wkleić jej definicję do modułu, aby zapobiec wywołaniom funkcji; lub możesz podać definicję, która na potrzeby bieżącego modułu jest równoważna (ale pomija sprawdzanie poprawności danych wejściowych lub dowolną optymalizację, jaką możesz sobie wyobrazić). Twórca kompilatora ma jednak zamiast tego opcję optymalizacji pod kątem rozmiaru programu.

Fred Foo
źródło
2
Standard stwierdza, że ​​kompilator zawsze może założyć, że istnieje funkcja zewnętrzna o tej samej nazwie i wywołać ją zamiast wersji wbudowanej, więc nie sądzę, że jest to błąd kompilatora. Próbowałem z gcc 4.3, 4.4 i 4.5, wszystkie dają błąd linkera.
Sven Marnach