Forum: Compiler & IDEs AVR GCC inline Funktionen


von Markus M. (dermarkus)


Lesenswert?

Hallo,
ich verwende WinAVR-20090313 mit einem mega128 und den folgenden 
Compiler Optionen:

-pedantic
--std=c99
-gdwarf-2
-O3

Folgende Funktion:
Fifo.h
extern inline STD_BOOL bGetFifoBufferEmpty( const struct stFifoBuffer* 
const pFifoBuffer );

Fifo.c
STD_BOOL bGetFifoBufferEmpty( const struct stFifoBuffer* const 
pFifoBuffer )
{
  return pFifoBuffer->m_numBytes == 0;
}

wird in main.c aufgerufen:
#include "Fifo.h"
int main( void )
{
   struct stFifoBuffer fifo;

   bGetFifoBufferEmpty( &fifo );

   return 0;
}

erzeugt den folgenden Code:

<snip>
bGetFifoBufferEmpty( &fifo );
8fa:  ce 01         movw  r24, r28
8fc:  01 96         adiw  r24, 0x01  ; 1
8fe:  0e 94 d2 1b   call  0x37a4  ; 0x37a4 <bGetFifoBufferEmpty>

Mir ist bewusst das das inline Schlüsselwort dem Compiler nur einen 
Hinweis gibt die Funktionen zu inlinen. Allerdings mache ich in meiner 
Andwenung massiv Gebrauch von der Funktion und somit leidet die 
Performance erheblich. Welche Möglichkeit habe ich den Compiler zu 
zwingen die Funktion zu inlinen ?

von (prx) A. K. (prx)


Lesenswert?

Gibt ein Attribut dazu, always-inline oder so. Siehe Doku. Vielleicht 
reicht auch schon "static inline" statt "extern inline".

von Markus M. (dermarkus)


Lesenswert?

Du meinst sicher:
__attribute__((always_inline));

Das zwingt den Compiler aber nur dazu die Funktion zu inlinen wenn keine 
Optimierung aktiviert ist ? Hat jedenfalls auch keine Änderung gebracht 
:|

static inline ist zwar eine Möglichkeit aber dann muss die Funktion im 
Header implementiert werden ?!

von (prx) A. K. (prx)


Lesenswert?

Markus Markus schrieb:

> static inline ist zwar eine Möglichkeit aber dann muss die Funktion im
> Header implementiert werden ?!

Wenn der Compiler die Implementierung der Funktion nicht kennt, dann 
kann er sie nicht inlinen. Egal was du ihm für Tipps gibst, Glaskugel 
hat er keine.

Im Regelfall übersetzt ein Compiler ein Quellfile separat vom anderen. 
Erst der Linker sieht alles, aber der inlined nichts meher.

von Karl H. (kbuchegg)


Lesenswert?

Wie A.K. schon sagte: Wie soll den der Compiler den Funktionsrumpf 
inlinen, wenn er den Quelltext des Funktionsrumpfes nicht kennt?

Fifo.h
1
inline STD_BOOL bGetFifoBufferEmpty( const struct stFifoBuffer* const pFifoBuffer )
2
{
3
  return pFifoBuffer->m_numBytes == 0;
4
}

von Sven P. (Gast)


Lesenswert?

Hm, gibt das so nicht später Meckerei vom Linker?
Prinzipiell würde ich vor das 'inline' noch ein 'static' packen, dann 
wär es idiotensicher -- was mich zur Frage bringt:

Impliziert das 'inline' schon ein 'static' oder nicht?

von (prx) A. K. (prx)


Lesenswert?

Ohne "static" wird der Compiler die Funktion in jedem Fall erzeugen und 
den Namen exportieren. Auch wenn er sie vielleicht nie aufruft, weil er 
das erst der Linker weiss.

Je nach Art der Übersetzung wird der Linker dann entweder meckern oder 
ggf. die redundanten Versionen weglassen (insbesondere bei 
-ffunction-sections). Lezteres ist vor allem in C++ ungemein nützlich.

Es ist bei in Includes definierten Inline-Funktionen also i.d.R. 
sinnvoll, sie mit "static" zu verzieren.

von Karl H. (kbuchegg)


Lesenswert?

Sven P. schrieb:
> Hm, gibt das so nicht später Meckerei vom Linker?

Nein. Sollte nicht.

Es ist Aufgabe des Compilers dafür zu sorgen, dass der Linker, sollte er 
diese Funktion jemals zu Gesicht kriegen, dies nur einmal tut.

> Prinzipiell würde ich vor das 'inline' noch ein 'static' packen,

Das ist fast immer eine gute Idee.

> Impliziert das 'inline' schon ein 'static' oder nicht?

Nein. static und inline haben so eigentlich nichts miteinander zu tun 
und doch hat static Auswirkungen auf inline :-)

Was ist das 'Problem' beim inline?

Selbst wenn der Compiler an einer Stelle entscheidet, dass inlining 
angebracht ist, so mus das an anderer Stelle nicht so sein oder z.B. 
Compiler-Optionen verhindern inlining. Da der Compiler allerdings 
normalerweise nicht alle weiteren Aufrufstellen einer Funktion kennt, 
bleibt ihm nichts anderes übrig als die Funktion als nicht-inline 
Version ebenfalls anzulegen.
Es gibt nur einen Fall, in dem der Compiler sicher sein kann, alle 
Funktionsverwendungen zu kennen und das ist wenn die Funktion static 
ist. Hat der Compiler also in einer SourceCode Unit eine static Funktion 
und inlined die an allen Stellen, dann kann er sicher sein, dass die 
eigentliche Funktion nirgends anders gebraucht wird und braucht daher 
keine linkbare Funktion dafür anzulegen. In dem Sinne kann static inline 
ein wenig Object Code einsparen.

von (prx) A. K. (prx)


Lesenswert?

Karl heinz Buchegger schrieb:

> Es ist Aufgabe des Compilers dafür zu sorgen, dass der Linker, sollte er
> diese Funktion jemals zu Gesicht kriegen, dies nur einmal tut.

Bei "extern inline" mitsamt Funktionsrupf im Include-File sind die 
Möglichkeiten des Compilers begrenzt und es kann sehr wohl Zoff geben.

Es läuft eher darauf hinaus, dem Linker die Möglichkeit zu geben, sich 
aus all den ihm in den Objektfiles präsentierten Implementierungen der 
immer gleichen Funktion eine rauszusuchen und die übrigen wegzuwerfen.

von Markus M. (dermarkus)


Lesenswert?

Ok, ich habe jetzt verstanden warum der Compiler ohne static Keyword 
nicht inlinen kann. Trotzdem stellt sich fuer mich die Frage warum 
"extern inline" nicht inlined. Laut Doku:

http://gcc.gnu.org/onlinedocs/gcc/Inline.html:

"...This combination of inline and extern has almost the effect of a 
macro. The way to use it is to put a function definition in a header 
file with these keywords, and put another copy of the definition 
(lacking inline and extern) in a library file. The definition in the 
header file will cause most calls to the function to be inlined. If any 
uses of the function remain, they will refer to the single copy in the 
library...."

sollte das genau meinen gewünschten Effekt erbringen ?! Mit der Option 
-Os würde ich verstehen das er trotzdem nicht inlined mit Option -O3 
hätte ich es schon erwartet ( wie auch immer die technische Umsetztung 
Compiler / Linker intern aussieht )

von (prx) A. K. (prx)


Lesenswert?

Markus Markus schrieb:

> Ok, ich habe jetzt verstanden warum der Compiler ohne static Keyword
> nicht inlinen kann.

Offensichtlich nicht, denn er kann das sehr wohl.

Bring mal den Quelltest so wie du ihn real dem Compiler zum Frass 
vorwirfst. Also nicht ein paar Quellcodezeilen, sondern die Sammlung von 
Files. Vielleicht lässt es sich dann leichter erklären.

von Karl H. (kbuchegg)


Lesenswert?

Markus Markus schrieb:
> Ok, ich habe jetzt verstanden warum der Compiler ohne static Keyword
> nicht inlinen kann. Trotzdem stellt sich fuer mich die Frage warum
> "extern inline" nicht inlined.

Drehen wir den Spiess mal um.

Du bist jetzt der Compiler. Ich geb dir diesen Code
1
extern inline int foo();
2
3
void bar()
4
{
5
  i = foo();
6
}

Du sollst diesen Code in C Code umformen, sodass die Funktion foo 
geinlined ist. Wie lautet das Ergebnis?

Wenn du zum Schluss kommst, das du das nicht kannst, weil du ja 
schliesslich nicht weißt, was in foo() drinnen steht, dann bist du in 
genau dem gleichen Dilemma in dem der Compiler bei deiner (identischen) 
Konstruktion ist.

Ob die Funktionsdeklaration jetzt direkt da steht, oder ob das über ein 
Header File geht, ist ja prinzipiell egal, da ja Header Files vom 
Präprozessor aufgelöst werden noch ehe der Compiler den Code zu Gesicht 
bekommt.

von Markus M. (dermarkus)


Angehängte Dateien:

Lesenswert?

Prima, aber er tut es nicht :D Im Anhang das Testprojekt.

von (prx) A. K. (prx)


Lesenswert?

Was soll ich mit einem ".7z" anfangen?

von Karl H. (kbuchegg)


Lesenswert?

Markus Markus schrieb:
> Prima, aber er tut es nicht :D Im Anhang das Testprojekt.

Na logisch!
Du hast den Code immer noch nicht im Header File!

Was du ständig geflissentlich ignorierst ist, dass sich der Compiler 
jedes *.c File unabhängig von allen anderen vornimmt. Wenn er dein 
Hauptfile compiliert, ist er exakt in der Situation, die ich dir 2 
Posts weiter oben skizziert habe. Du forderst ihn zum inline auf, gibst 
ihm aber nicht die Information die er dafür braucht: nämlich den 
Funktionsinhalt!

Wenn du verstanden hast (und das sollte eigentlich nicht schwer sein), 
warum in meinem Beispiel weiter oben Du als Compiler den inline nicht 
machen kannst, dann solltest du auch verstehen, warum ein richtiger 
Compiler das nicht kann. Der hat nämlich auch keine Glaskugel!

von Stefan E. (sternst)


Lesenswert?

> Prima, aber er tut es nicht

Um das nochmal ganz klar zu sagen:
Der Code der Funktion muss beim Übersetzen der jeweiligen C-Datei mit 
vorhanden sein, entweder direkt in der C-Datei, oder indirekt über ein 
#include, sonst kann der Compiler grundsätzlich nicht Inlinen. Auch 
das "extern" sorgt nicht dafür, dass er sich den Code irgendwie auf 
magische Weise von woanders herholt.

von Markus M. (dermarkus)


Lesenswert?

Karl heinz Buchegger schrieb:
> Du sollst diesen Code in C Code umformen, sodass die Funktion foo
> geinlined ist. Wie lautet das Ergebnis?
>
> Wenn du zum Schluss kommst, das du das nicht kannst, weil du ja
> schliesslich nicht weißt, was in foo() drinnen steht, dann bist du in
> genau dem gleichen Dilemma in dem der Compiler bei deiner (identischen)
> Konstruktion ist.

Mir ist schon klar das der Compiler an dieser Stelle ein Problem hat und 
nicht inlinen kann. Trotzdem war meine Vermutung das es Compiler interne 
Vorkehrungen gibt ( z.B. erst den gesamten Code zu übersetzen und dann 
einen extra Optimierungdurchlauf ) welche dies trotzdem ermöglichen. 
Wenn man schaut was die Compiler mittlerweile für interessante 
Optimierungsmöglichkeiten haben verwundert es mich umso mehr.

Edit: Ihr seit zu schnell mit euren Antworten :D Das von mir gepostete 
Zitat von der GCC Seite sagt doch explicit das extern inline einem Macro 
gleichzusetzen ist !

von Karl H. (kbuchegg)


Lesenswert?

Oder um es nochmal klarer auszudrücken.

Wenn dein Compiler die main.c compiliert, dann sieht er diesen Quelltext
1
  // Typedef fuer ein bool.
2
  typedef enum 
3
  { 
4
    STD_FALSE = 0,
5
    STD_TRUE
6
  } STD_BOOL;
7
  
8
  // Typedef fuer einen 8 Bit Datentyp.
9
  typedef signed char S8;
10
  typedef unsigned char US8;
11
12
  // Typedef fuer einen 16 Bit Datentyp.
13
  typedef signed int S16;
14
  typedef unsigned int US16; 
15
16
  // Typedef fuer einen 32 Bit Datentyp.
17
  typedef signed long S32;
18
  typedef unsigned long US32;
19
20
  // Typedef fuer einen 32 Bit Float Datentyp.
21
  typedef float FLOAT32;
22
23
  // Typedef fuer einen 64 Bit Float Datentyp.
24
  typedef double FLOAT64;
25
26
// Struktur fuer einen Fifo Buffer.
27
struct stFifoBuffer
28
{
29
  S8* m_pBeginBuffer;  // [-]  Zeiger auf den Buffer Anfang.
30
  S8* m_pEndBuffer;  // [-]  Zeiger auf das Buffer Ende.
31
  S8* m_pPush;    // [-]  Zeiger auf die aktuelle Push Position.
32
  S8* m_pPop;      // [-]  Zeiger auf die aktuelle Pop Position.
33
  US8 m_numByte;      // [-]  Anzahl der gespeicherten Bytes.
34
  US8 m_size;         // [-]  Groesse des Buffers in Bytes.
35
};
36
37
// Pruefen ob der Buffer leer ist.
38
extern inline STD_BOOL bGetFifoBufferEmpty( const struct stFifoBuffer* const pFifoBuffer );
39
40
41
int main( void )
42
{
43
  struct stFifoBuffer fifo;
44
45
  bGetFifoBufferEmpty( &fifo );
46
47
  return 0;
48
}

Er sieht nur das hier, sonst nichts. Der Präprozessor hat bereits alle 
#define und #include aufgelöst.

Und jetzt verrat mir mal, wie der Compiler, nur mit diesem Quelltext 
bewaffnet, die Funktion bGetFifoBufferEmpty inlinen soll?

von Karl H. (kbuchegg)


Lesenswert?

Markus Markus schrieb:

> Wenn man schaut was die Compiler mittlerweile für interessante
> Optimierungsmöglichkeiten haben verwundert es mich umso mehr.

C ist darauf aufgebaut, dass jedes Source Code File unabhängig von 
allen anderen compiliert werden kann. Das ist wichtig, weil reale 
Projekte gross werden können. Wenn in meinem letzten Projekt alle 
C-Source Files compiliert werden müssen, dann dauert das eine Stunde. 
Daher ist es wichtig, dass sich der Compiler jedes File einzeln und in 
beliebiger Reihenfolge vornehmen kann. Denn dann dauert eine Änderung in 
einem File lediglich ein paar Sekunden.

Der Compiler schaut nicht über den Tellerrand! Den interessiert nur der 
eine Quelltext, den er zur Zeit bearbeitet


(*) gcc ist da eine Ausnahme. Es gibt eine Option mit der man ihm 
mitteilen kann, dass alle in der Commandline angegebenen c-Files das 
komplette Programm bilden und abgesehen von diesen Files nichts mehr 
dazukommt. Dann kann der gcc auch über diese Files optimieren.
Aber das ist die Ausnahme und nicht die Regel!

von Markus M. (dermarkus)


Lesenswert?

Karl heinz Buchegger schrieb:
> Er sieht nur das hier, sonst nichts. Der Präprozessor hat bereits alle
> #define und #include aufgelöst.
>
> Und jetzt verrat mir mal, wie der Compiler, nur mit diesem Quelltext
> bewaffnet, die Funktion bGetFifoBufferEmpty inlinen soll?

Gar nicht :) das ist verstanden ! Trotzdem muss es doch eine Möglichkeit 
geben den überflüssigen Call zu entfernen. Spätestens der Linker sollte 
das merken.

von Markus M. (dermarkus)


Lesenswert?

Karl heinz Buchegger schrieb:

> (*) gcc ist da eine Ausnahme. Es gibt eine Option mit der man ihm
> mitteilen kann, dass alle in der Commandline angegebenen c-Files das
> komplette Programm bilden und abgesehen von diesen Files nichts mehr
> dazukommt. Dann kann der gcc auch über diese Files optimieren.
> Aber das ist die Ausnahme und nicht die Regel!

Aaaha...genau das mein ich doch ! D.d. der avr gcc unterstüzt diese 
Option nicht ? Btw. hast du die Option gerade zur Hand ?

von Karl H. (kbuchegg)


Lesenswert?

Markus Markus schrieb:
> Karl heinz Buchegger schrieb:
>> Er sieht nur das hier, sonst nichts. Der Präprozessor hat bereits alle
>> #define und #include aufgelöst.
>>
>> Und jetzt verrat mir mal, wie der Compiler, nur mit diesem Quelltext
>> bewaffnet, die Funktion bGetFifoBufferEmpty inlinen soll?
>
> Gar nicht :) das ist verstanden ! Trotzdem muss es doch eine Möglichkeit
> geben den überflüssigen Call zu entfernen. Spätestens der Linker sollte
> das merken.

Das ist im Allgemeinen nicht so einfach.
Es gibt Funktionen, für die es im Quelltext keinen Aufruf gibt und die 
trotzdem im kompletten Programm benötigt werden.

Ausserdem ist es nicht Aufgabe des Linkers, Übersetzungsarbeit zu 
leisten. Der Compiler hat sich z.B. viel Mühe gegeben eine optimale 
Registerverteilung zu erreichen und dann kommt da plötzlich ein Linker 
daher und schiebt Code rein und bringt alles durcheinander :-)

von Karl H. (kbuchegg)


Lesenswert?

Markus Markus schrieb:
> Karl heinz Buchegger schrieb:
>
>> (*) gcc ist da eine Ausnahme. Es gibt eine Option mit der man ihm
>> mitteilen kann, dass alle in der Commandline angegebenen c-Files das
>> komplette Programm bilden und abgesehen von diesen Files nichts mehr
>> dazukommt. Dann kann der gcc auch über diese Files optimieren.
>> Aber das ist die Ausnahme und nicht die Regel!
>
> Aaaha...genau das mein ich doch ! D.d. der avr gcc unterstüzt diese
> Option nicht ? Btw. hast du die Option gerade zur Hand ?

Mach deine inlines einfach so, wie sich die Entwickler von C das 
vorgestellt haben.

Du musst das Spiel nach den festgelegten Regeln spielen und nicht die 
Regeln passen sich an das an, was du gerne hättest.

von Stefan E. (sternst)


Lesenswert?

Markus Markus schrieb:

> Edit: Ihr seit zu schnell mit euren Antworten :D Das von mir gepostete
> Zitat von der GCC Seite sagt doch explicit das extern inline einem Macro
> gleichzusetzen ist !

Da steht aber auch:
1
The way to use it is to put a function definition in a header
2
file with these keywords
Definition, nicht Deklaration!

von (prx) A. K. (prx)


Lesenswert?

Markus: Wenn du so programmierst, wie das in C allgemein üblich ist, 
dann wird der Compiler dein Freund sein. Naja, meistens jedenfalls.

Wenn du allerdings vom Compiler verlangst, dass er sich deinen Wünschen 
anpasst, dann wirst du rasch feststellen, dass Maschinen weitaus sturer 
sind all jeder Mensch es sein kann. Diesen Wettbewerb kannst du nicht 
gewinnen.

von Stefan E. (sternst)


Lesenswert?

Ach, noch was:

Markus Markus schrieb:
> Gar nicht :) das ist verstanden ! Trotzdem muss es doch eine Möglichkeit
> geben den überflüssigen Call zu entfernen. Spätestens der Linker sollte
> das merken.

Der Linker kann praktisch nur Adressen ersetzen. Was er auf keinen Fall 
kann, ist, den Code an sich zu verändern.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Markus Markus schrieb:

> Edit: Ihr seit zu schnell mit euren Antworten :D Das von mir gepostete
> Zitat von der GCC Seite sagt doch explicit das extern inline einem Macro
> gleichzusetzen ist !

Der Compiler muss den Quelltext kennen. Punkt.

Dazu gibt es mehrere Wege:

Dir Funktion wird im Header implementierst nebst static inline, siehe 
oben. Eine Deklaration ist eine Deklaration. Sie sagt nix über Interna 
einer Funktion, die Funktion bleibt eine Black Box wenn sie nur 
deklariert wird.

Oder du gibst das Zeug beim Compilieren mit an, etwa so:
1
int a;
2
3
int is0 (const int * const p)
4
{
5
    return *p == 0;
6
}
1
extern inline int is0 (const int * const);
2
extern int a;
3
4
int main()
5
{
6
    return is0 (&a);
7
}

Und compilierst in etwa so:
1
gcc foo.c bar.c -o foobar.elf -W -Wall -fno-keep-inline-functions -Winline -fwhole-program -combine -Os
2
objdump -d foobar.elf > foobar.lst

Beachte, daß gcc die Quelle foo.c prinzipiell kennen kann, während er 
bar.c übersetzt!

Mit folgender Sequenz ist das nicht der Fall. Auch GCC hat noch keine 
Kristallkugel.
1
gcc foo.c -o foo.o -W -Wall -fno-keep-inline-functions -Winline -Os
2
gcc bar.c -o bar.o -W -Wall -fno-keep-inline-functions -Winline -Os
3
gcc foo.o bar.o -o foobar.elf
4
objdump -d foobar.elf > foobar.lst

Johann

von Markus M. (dermarkus)


Lesenswert?

Ich habe nun die Implementierung aller Helper Funktionen in den Header 
gepackt und als static deklariert. Ich danke für eure Hilfe.

von Simon K. (simon) Benutzerseite


Lesenswert?

Karl heinz Buchegger schrieb:
> Markus Markus schrieb:
>> Karl heinz Buchegger schrieb:
>>> Er sieht nur das hier, sonst nichts. Der Präprozessor hat bereits alle
>>> #define und #include aufgelöst.
>>>
>>> Und jetzt verrat mir mal, wie der Compiler, nur mit diesem Quelltext
>>> bewaffnet, die Funktion bGetFifoBufferEmpty inlinen soll?
>>
>> Gar nicht :) das ist verstanden ! Trotzdem muss es doch eine Möglichkeit
>> geben den überflüssigen Call zu entfernen. Spätestens der Linker sollte
>> das merken.
>
> Das ist im Allgemeinen nicht so einfach.
> Es gibt Funktionen, für die es im Quelltext keinen Aufruf gibt und die
> trotzdem im kompletten Programm benötigt werden.

Stichwort Interrupt Routine.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.