Co robi extern inline?

93

Rozumiem, że inlinesamo w sobie jest sugestią dla kompilatora i według własnego uznania może wbudować funkcję lub nie, a także wygeneruje kod wynikowy, który można połączyć.

Myślę, że static inlinerobi to samo (może być wbudowane lub nie), ale po wstawieniu nie utworzy kodu wynikowego, który można połączyć (ponieważ żaden inny moduł nie może się z nim połączyć).

Gdzie to extern inlinepasuje do obrazu?

Załóżmy, że chcę zastąpić makro preprocesora funkcją wbudowaną i wymagam, aby ta funkcja została wbudowana (np. Ponieważ używa makr __FILE__i, __LINE__które powinny być rozwiązywane dla wywołującego, ale nie tej wywoływanej funkcji). Oznacza to, że chcę zobaczyć błąd kompilatora lub konsolidatora na wypadek, gdyby funkcja nie została wstawiona. Czy extern inlineto robi? (Zakładam, że jeśli tak nie jest, nie ma innego sposobu na osiągnięcie tego zachowania niż trzymanie się makra).

Czy są różnice między C ++ i C?

Czy istnieją różnice między różnymi dostawcami i wersjami kompilatorów?

dragosht
źródło

Odpowiedzi:

129

w K&R C lub C89 inline nie było częścią języka. Wiele kompilatorów zaimplementowało to jako rozszerzenie, ale nie było zdefiniowanej semantyki dotyczącej tego, jak to działa. GCC był jednym z pierwszych do wdrożenia inline i wprowadził inline, static inlinei extern inlinekonstrukcje; większość kompilatorów sprzed C99 zazwyczaj podąża za tym przykładem.

GNU89:

  • inline: funkcja może być wbudowana (choć to tylko wskazówka). Wersja poza linią jest zawsze emitowana i widoczna z zewnątrz. W związku z tym możesz mieć tylko taką inline zdefiniowaną w jednej jednostce kompilacji, a każda inna musi ją widzieć jako funkcję poza linią (w przeciwnym razie otrzymasz zduplikowane symbole w czasie łączenia).
  • extern inline nie wygeneruje wersji out-of-line, ale może ją wywołać (którą w związku z tym należy zdefiniować w innej jednostce kompilacji. Obowiązuje jednak reguła jednej definicji; wersja out-of-line musi mieć ten sam kod co w tym miejscu, na wypadek, gdyby kompilator go wywołał.
  • static inlinenie wygeneruje zewnętrznie widocznej wersji out-of-line, chociaż może wygenerować plik statyczny. Zasada jednej definicji nie ma zastosowania, ponieważ nigdy nie ma emitowanego zewnętrznego symbolu ani wezwania do niego.

C99 (lub GNU99):

  • inline: jak GNU89 "extern inline"; żadna widoczna z zewnątrz funkcja nie jest emitowana, ale można ją wywołać i dlatego musi istnieć
  • extern inline: jak GNU89 "inline": emitowany jest kod widoczny z zewnątrz, więc może go użyć co najwyżej jedna jednostka tłumaczeniowa.
  • static inline: jak GNU89 „static inline”. To jedyna przenośna wersja między gnu89 a c99

C ++:

Funkcja, która jest wbudowana w dowolnym miejscu, musi znajdować się w wierszu wszędzie, z tą samą definicją. Kompilator / konsolidator posortuje wiele wystąpień symbolu. Nie ma definicji static inlinelub extern inline, chociaż wiele kompilatorów je ma (zazwyczaj zgodnie z modelem gnu89).

puetzk
źródło
2
W klasycznym klasycznym C „inline” nie było słowem kluczowym; była dostępna do użytku jako nazwa zmiennej. Dotyczyłoby to C89 i pre-standard (K&R) C.
Jonathan Leffler
Wygląda na to, że masz rację. Naprawiony. Myślałem, że to było zarezerwowane jako słowo kluczowe w C89 (choć nie w K&R), ale wydaje mi się, że źle mnie zapamiętałem
puetzk
Chciałbym dodać, że w przypadku Visual C ++ firmy Microsoft istnieje słowo kluczowe __forceinline, które wymusi wprowadzenie funkcji. Jest to oczywiście rozszerzenie specyficzne dla kompilatora tylko dla VC ++.
bez tytułu
Czy jest jakaś różnica między C99 „extern inline” a brakiem specyfikatorów?
Jo So
Semantycznie nie; podobnie jak funkcja nieliniowa extern inlinepodlega regule jednej definicji i to jest definicja. Ale jeśli heurystyka optymalizacji zdefiniowana w ramach implementacji jest zgodna z zaleceniem użycia inlinesłowa kluczowego jako sugestii, że „wywołania funkcji powinny być tak szybkie, jak to możliwe” (ISO 9899: 1999 §6.7.4 (5), extern inlineliczy się
puetzk
31

Uważam, że źle zrozumiałeś __FILE__ i __LINE__ na podstawie tego stwierdzenia:

ponieważ używa makr __FILE__ i __LINE__, które powinny być rozpoznawane dla wywołującego, ale nie dla tej wywołanej funkcji

Istnieje kilka faz kompilacji, a pierwszą jest przetwarzanie wstępne. __FILE__ i __LINE__ są zastępowane podczas tej fazy. Więc zanim kompilator będzie mógł rozważyć funkcję wstawiania, zostały one już zastąpione.

Don Neufeld
źródło
14

Wygląda na to, że próbujesz napisać coś takiego:

inline void printLocation()
{
  cout <<"You're at " __FILE__ ", line number" __LINE__;
}

{
...
  printLocation();
...
  printLocation();
...
  printLocation();

i mając nadzieję, że za każdym razem otrzymasz inne wartości. Jak mówi Don, nie zrobisz tego, ponieważ __FILE__ i __LINE__ są implementowane przez preprocesor, ale wbudowane są implementowane przez kompilator. Więc gdziekolwiek wywołasz printLocation, otrzymasz ten sam wynik.

Tylko sposób można uzyskać to do pracy jest, aby printLocation makro. (Tak, wiem...)

#define PRINT_LOCATION  {cout <<"You're at " __FILE__ ", line number" __LINE__}

...
  PRINT_LOCATION;
...
  PRINT_LOCATION;
...
Roddy
źródło
18
Typową sztuczką jest wywołanie przez makro PRINT_LOCATION funkcji printLocation, przekazując jako parametry FILE i LINE . Może to skutkować lepszym zachowaniem debuggera / edytora / etc, gdy treść funkcji jest nietrywialna.
Steve Jessop
@Roddy Spójrz na moje rozwiązanie - rozszerzenie twojego, ale bardziej kompleksowe i rozszerzalne.
entuzjastyczny
@SteveJessop Coś takiego jak wymieniłem w rozwiązaniu poniżej?
entuzjastyczny
3

Sytuacja z inline, static inline i extern inline jest skomplikowana, nie tylko dlatego, że gcc i C99 definiują nieco inne znaczenia ich zachowania (i prawdopodobnie również C ++). Możesz znaleźć przydatne i szczegółowe informacje o tym, co robią w C. tutaj .

Simon Howard
źródło
2

Zamiast funkcji wbudowanych wybierasz makra. Rzadka sytuacja, w której makra rządzą funkcjami wbudowanymi. Spróbuj wykonać następujące czynności: napisałem ten kod „MAGIA MAKRO” i powinien działać! Przetestowano na gcc / g ++ Ubuntu 10.04

//(c) 2012 enthusiasticgeek (LOGGING example for StackOverflow)

#ifdef __cplusplus

#include <cstdio>
#include <cstring>

#else

#include <stdio.h>
#include <string.h>

#endif

//=========== MACRO MAGIC BEGINS ============

//Trim full file path
#define __SFILE__ (strrchr(__FILE__,'/') ? strrchr(__FILE__,'/')+1 : __FILE__ )

#define STRINGIFY_N(x) #x
#define TOSTRING_N(x) STRINGIFY_N(x)
#define _LINE (TOSTRING_N(__LINE__))

#define LOG(x, s...) printf("(%s:%s:%s)"  x "\n" , __SFILE__, __func__, _LINE, ## s);

//=========== MACRO MAGIC ENDS ============

int main (int argc, char** argv) {

  LOG("Greetings StackOverflow! - from enthusiasticgeek\n");

  return 0;
}

W przypadku wielu plików zdefiniuj te makra w osobnym pliku nagłówkowym, w tym to samo w każdym pliku c / cc / cxx / cpp. Jeśli to możliwe, preferuj funkcje wbudowane lub identyfikatory stałych (zgodnie z wymaganiami) zamiast makr.

entuzjastyczny
źródło
2

Zamiast odpowiadać „co to robi?”, Odpowiadam „jak mam zrobić to, co chcę?” Istnieje 5 rodzajów wstawiania, wszystkie dostępne w GNU C89, standardowym C99 i C ++:

zawsze w tekście, chyba że adres jest zajęty

Dodaj __attribute__((always_inline))do dowolnej deklaracji, a następnie użyj jednego z poniższych przypadków, aby obsłużyć możliwość pobrania jej adresu.

Prawdopodobnie nigdy nie powinieneś tego używać, chyba że potrzebujesz jego semantyki (np. Aby wpłynąć na asemblację w określony sposób lub użyć alloca ). Kompilator zwykle wie lepiej od Ciebie, czy warto.

w tekście i emitują słaby symbol (np. C ++, czyli „po prostu zrób to”)

__attribute__((weak))
void foo(void);
inline void foo(void) { ... }

Zauważ, że pozostawia to kilka kopii tego samego kodu, a linker wybiera jeden arbitralnie.

inline, ale nigdy nie emituj żadnego symbolu (pozostawiając odnośniki zewnętrzne)

__attribute__((gnu_inline))
extern inline void foo(void) { ... }

emituj zawsze (dla jednej jednostki tłumaczeniowej, aby rozwiązać poprzedni)

Wersja ze wskazówkami emituje słaby symbol w C ++, ale silny symbol w każdym dialekcie C:

void foo(void);
inline void foo(void) { ... }

Lub możesz to zrobić bez podpowiedzi, która emituje silny symbol w obu językach:

void foo(void) { ... }

Generalnie wiesz, jakim językiem jest Twoja jednostka językowa, kiedy podajesz definicje, i prawdopodobnie nie potrzebujesz zbyt wiele wstawiania.

inline i emituj w każdej jednostce tłumaczeniowej

static inline void foo(void) { ... }

Dla wszystkich z wyjątkiem staticjednego, możesz dodać void foo(void)deklarację powyżej. Pomaga to w „najlepszej praktyce” pisania czystych nagłówków, a następnie #includetworzenia oddzielnego pliku z definicjami w wierszu. Jeśli korzystasz z wbudowanych w stylu C,#define niektóre makra inaczej w jednej dedykowanej jednostce tłumaczeniowej, aby zapewnić definicje poza linią.

Nie zapomnij, extern "C"jeśli nagłówek może być używany zarówno z C, jak i C ++!

o11c
źródło
Brakujące: a co z MSVC? Ma kilka rozszerzeń dialektu C89, ale nigdy nie używam MSVC i nie wiem, jak uruchomić jego nmodpowiednik.
o11c,