Forum: Compiler & IDEs warum __inline zwingend erforderlich?


von Peter Z. (Gast)


Lesenswert?

Hi,
ich habe mit dem ATmel Studio 6.1 für den ATtiny10 ein kleines Blinkytoy 
für einen WS2812B LED-Streifen programmiert.
Hier ein Teil des Codes:
1
  while(1)
2
  {
3
    Color = 2;
4
    Lauf_rechts();
5
    Color = 3;
6
    Lauf_links();
7
    Color = 1;
8
    Lauf_rechts();
9
    Color = 5;
10
    Lauf_links();
11
    Color = 4;
12
    Lauf_rechts();
13
    Color = 6;
14
    Lauf_links();
15
  }
16
}
17
//*********************************************************************
18
__inline void Lauf_rechts(void)
19
{
20
  uint8_t n;
21
  for(n = 0;n <= 77;n++) Set_pos(n);
22
}
23
//*********************************************************************
24
__inline void Lauf_links(void)
25
{
26
  uint8_t n;
27
  for(n = 76;n;n--)      Set_pos(n);
28
}
29
//*********************************************************************
das __inline ist für die Funktionen Lauf_rechts() und Lauf_links 
zwingend erforderlich, sonst leuchtet nix!
Wie kann das sein?

von Pupsomat (Gast)


Lesenswert?

Was macht Set_pos? Code?

von Peter Z. (Gast)


Lesenswert?

Pupsomat schrieb:
> Was macht Set_pos? Code?
1
//*********************************************************************
2
void Set_pos(uint8_t n)
3
{
4
  if(n <= 17) light_wave(17 - n);
5
  else
6
  {
7
    for(n -= 17;n;n--)
8
    {
9
      Send_Byte(0);
10
      Send_Byte(0);
11
      Send_Byte(0);
12
    }
13
    light_wave(0);
14
  }
15
  _delay_ms(30);
16
}
17
//*********************************************************************

von Bernd K. (prof7bit)


Lesenswert?

Schieb die mal nach oben oberhalb der main(). Normalerweise sollte das 
gar nicht kompilieren, Du kannst eine Funktion erst aufrufen wenn der 
Compiler die Stelle an der sie deklariert wurde schon gesehen hat. Also 
nach oben damit. (oder hast Du forward-Deklarationen dafür?)

: Bearbeitet durch User
von Peter II (Gast)


Lesenswert?

Bernd K. schrieb:
> Normalerweise sollte das
> gar nicht kompilieren

In C ist das zulässig.

von Bernd K. (prof7bit)


Lesenswert?

Peter II schrieb:
> Bernd K. schrieb:
>> Normalerweise sollte das
>> gar nicht kompilieren
>
> In C ist das zulässig.

Seit wann? Mein gcc meckert jedenfalls.

von Peter II (Gast)


Lesenswert?

Bernd K. schrieb:
> Seit wann? Mein gcc meckert jedenfalls.

schon immer, gcc sollte eine Warnung ausgeben.

Es könnte seit C99 nicht mehr zulässig sein.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Peter II schrieb:
> Bernd K. schrieb:
>> Normalerweise sollte das
>> gar nicht kompilieren
>
> In C ist das zulässig.

Na und?  Kommt trotzdem gerne Müll raus.

Oder hast du ne Kristallkugel, die anhand von
 
1
int main (void)
2
{
3
    foo (1);
4
    return 0;
5
}

erkennt, wie z.B. ein anderes Modul foo implementiert und diese Funktion 
die Argumente erwartet? Z.B. als:
1
void foo (int);
2
void foo (long);
3
void foo (float);
4
void foo (const char*, ...);
5
void foo (struct xyz);
6
struct xyz foo (int);

Übrigens erwartet foo im letzten Fall i.d.R. 2 Parameter.

von Peter II (Gast)


Lesenswert?

Johann L. schrieb:
> Oder hast du ne Kristallkugel

nein, aber es ist ja zum glück Definiert wie sich er Compiler zu 
verhalten hat. Und die Anzahl der Paramter ist ziemlich egal, sonst 
würde ein Printf mit variablen Anzahl nicht funktionieren.

von Rolf M. (rmagnus)


Lesenswert?

Peter Zz schrieb:
> das __inline ist für die Funktionen Lauf_rechts() und Lauf_links
> zwingend erforderlich, sonst leuchtet nix!
> Wie kann das sein?

Stacküberlauf vielleicht? Das Ding hat immerhin nur 32 Bytes RAM.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Peter II schrieb:
> Johann L. schrieb:
>> Oder hast du ne Kristallkugel
>
> nein, aber es ist ja zum glück Definiert wie sich er Compiler zu
> verhalten hat.

Daß der Compiler sich definiert verhält hilft nix wenn er sich in Modul 
A anders definiert verhält als in Modul B.  Siehe Beispiel.

> Und die Anzahl der Paramter ist ziemlich egal, sonst
> würde ein Printf mit variablen Anzahl nicht funktionieren.

Für den Compiler ist ein Funktionsaufruf ohne Prototyp nicht als 
varargs-Funktion erkennbar — wie auch.

Ganz nebenbei werden die Argumente anders übergaben auch wenn es sich 
um die gleichen Typen handelt.  foo (x) mit int x übergibt x in R24/25 
oder auf dem Stack, je nachdem.  Das ist schon ein kleiner Unterschied.

Aber auch ohne varargs werden Argumente anders übergeben: foo(int x) 
erwartet x in R24-25.  foo(long x) erwartet x in R22-25 mit LSB in R24 
bzw. R22. Und foo(float) verwendet zwar die gleichen Register wie 
foo(long), interpretiert dise aber etwas anders.

Ergo: Ob der Compiler sich definiert verhält ist total Banane.  Was 
zählt ist ob er richtigen Code generiert, und das kann er nur mit 
korrekten Prototypen.  Zumindest bei avr-gcc um den es hier ja geht.

von Peter II (Gast)


Lesenswert?

Johann L. schrieb:
> Ergo: Ob der Compiler sich definiert verhält ist total Banane.  Was
> zählt ist ob er richtigen Code generiert, und das kann er nur mit
> korrekten Prototypen.  Zumindest bei avr-gcc um den es hier ja geht.

in vielen Fällen stimmt das, aber nicht in allen. In diesem Fall hier 
werde keine Parameter übergeben, damit ist der Aufruf eindeutig.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Peter Zz schrieb:
> ATtiny10
>
> das __inline ist für die Funktionen Lauf_rechts() und Lauf_links
> zwingend erforderlich, sonst leuchtet nix!
> Wie kann das sein?

Schau mal den erzeugten Code an bzw. Mapfile.  Kann sein daß dem 
Hänfling die Resourcen ausgehen, z.B. Stack?

von Peter Z. (Gast)


Lesenswert?

Johann L. schrieb:
> Kann sein daß dem
> Hänfling die Resourcen ausgehen, z.B. Stack?

habe ich auch vermutet.
Habe dann meine Tabelle die 18 von den 32Byte RAM frisst ins Flash 
gemapt.
1
static const __flash uint8_t Wave_Tab[18] = {1,2,4,8,16,32,64,128,255,128,64,32,16,8,4,2,1,0};
Und schon ein neues Problem, ich bekomme beim Compilieren zwei 
Fehlermeldungen:
Register not supported
illegal opcode elpm for mcu attiny10

von Peter Z. (Gast)


Lesenswert?

Jetzt funktionierts!   :-)
1
#include <avr/pgmspace.h>
2
*
3
*
4
*
5
const uint8_t PROGMEM Wave_Tab[18] = {1,2,4,8,16,32,64,128,255,128,64,32,16,8,4,2,1,0};
6
*
7
*
8
*
9
  Helligkeit = pgm_read_byte(&Wave_Tab[n]);
10
*
11
*
12
*
__inline ist nicht mehr erforderlich.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Peter Zz schrieb:
> Johann L. schrieb:
>> Kann sein daß dem Hänfling die Resourcen ausgehen,
>> z.B. Stack?
>
> habe ich auch vermutet.
> Habe dann meine Tabelle die 18 von den 32Byte RAM frisst ins Flash
> gemapt.
>
1
> static const __flash uint8_t Wave_Tab[18] = 
2
> {1,2,4,8,16,32,64,128,255,128,64,32,16,8,4,2,1,0};
3
>
> Und schon ein neues Problem, ich bekomme beim Compilieren zwei
> Fehlermeldungen:
> Register not supported
> illegal opcode elpm for mcu attiny10

Ok.  Dann hat die Toolchain keinen Support für ATtiny.  Bestenfalls kann 
man von der Toolchain sagen, daß sie für ATtiny aufgebohrt wurde...

Wenn die Toolchain ATtiny gscheit unterstützt dann sollte ein
1
static const uint8_t Wave_Tab[18] = { ... };
vollkommen ausreichen:

1) Der Compiler legt diese Daten nach .rodata.

2) WIMRE mappt ATtiny Flash in den Adressbereich von LD/ST, nämlich ab 
0x4000.

3) Das Linkerscript sollte .rodata also ab 0x4000 legen.

Ergo: Wenn die Toolchain ATtiny unterstützt dann brauch't das ganze 
__flash oder progmem Geraffel nicht mehr.

Evtl. kann man mit eigenem Linkerscript nachhelfen das .rodata und 
andere Daten, die nur gelesen werden müssen, ab 0x4000 lokatiert, 
beispielsweise .ctors und .dtors.

von Rolf M. (rmagnus)


Lesenswert?

Johann L. schrieb:
> Aber auch ohne varargs werden Argumente anders übergeben: foo(int x)
> erwartet x in R24-25.  foo(long x) erwartet x in R22-25 mit LSB in R24
> bzw. R22. Und foo(float) verwendet zwar die gleichen Register wie
> foo(long), interpretiert dise aber etwas anders.

Natürlich werden unterschiedliche Parametertypen unterschiedlich 
übergeben.

> Ergo: Ob der Compiler sich definiert verhält ist total Banane.  Was
> zählt ist ob er richtigen Code generiert, und das kann er nur mit
> korrekten Prototypen.  Zumindest bei avr-gcc um den es hier ja geht.

In C gab es ursprünglich gar keine Prototypen, und trotzdem konnten 
Compiler korrekten Code erzeugen. Und auch heute geht das noch. Man muß 
dann natürlich selber darauf achten, die Daten mit richtigem Typ zu 
übergeben. Da das sehr mühsam und fehlerträchtig ist, gibt es 
Prototypen, aber zwingend erforderlich sind sie nicht.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Rolf Magnus schrieb:
> Johann L. schrieb:
>> Aber auch ohne varargs werden Argumente anders übergeben: foo(int x)
>> erwartet x in R24-25.  foo(long x) erwartet x in R22-25 mit LSB in R24
>> bzw. R22. Und foo(float) verwendet zwar die gleichen Register wie
>> foo(long), interpretiert dise aber etwas anders.
>
> Natürlich werden unterschiedliche Parametertypen unterschiedlich
> übergeben.
>
>> Ergo: Ob der Compiler sich definiert verhält ist total Banane.  Was
>> zählt ist ob er richtigen Code generiert, und das kann er nur mit
>> korrekten Prototypen.  Zumindest bei avr-gcc um den es hier ja geht.
>
> In C gab es ursprünglich gar keine Prototypen, und trotzdem konnten
> Compiler korrekten Code erzeugen.

Das lag und liegt aber nicht am Compiler sondern am ABI, das für 
Parameterübergabe nicht komplizierter war als "Pack den Krempel 
wortweise auf den Stack und gut is".

Hätte man bei avr-gcc auch machen können, aber zu welcher Codegüte das 
geführt hätte kann sich hier jeder ausmalen.   Der Teil des avr-gcc ABI, 
der festlegt, wo was wann wie übergeben wird, ist 1/2 DIN A4 Seite lang 
oder mehr.

> Und auch heute geht das noch.

Ja, mit entsprechendem ABI.

> Man muß dann natürlich selber darauf achten, die Daten mit
> richtigem Typ zu übergeben. Da das sehr mühsam und
> fehlerträchtig ist, gibt es Prototypen, aber zwingend
> erforderlich sind sie nicht.

Beispiele hab ich oben schon gegeben.  Konkret: Wenn der Funktionsaufruf
1
 func (0);
ist, dann kannst du nicht ausdrücken, ob 0

1) in R24/25 oder
2) in R22/23 oder
3) auf dem Stack

übergeben wird, bzw. 0 wird immer gemäß 1) übergeben.  Oder wie willst 
du es ohne Prototyp anstellen, dass 0 in R22/23 oder auf dem Stack 
übergeben wird?  Hypnose?

Das sind zumindest laut avr-gcc ABI die 3 Möglichkeiten für func, den 
ersten Parameter zu erhalten wenn dieser vom Typ int ist.

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

Das avr-gcc ABI wurde eindeutig im Hinblick auf Prototypes definiert. 
Ohne sie hätte es anders ausfallen müssen. Aber auch ohne Prototypes 
wäre ein ABI definierbar, das eine begrenzte Anzahl Parameter in 
Registern übergibt (varargs/stdarg.h wäre dann nur etwas komplizierter).

So gibt es Stellen, an denen das bestehende ABI überhaupt nur mit 
Prototypes implementierbar ist. Beispielsweise setzen so wie im ABI 
definiert Varargs-Funktionen eine Prototyp-Deklaration zwingend voraus, 
weshalb dein Beispiel mit Übergabe auf dem Stack eine direkte Folge der 
Entscheidung bei der ABI-Definition ist. Parameter kleiner als int kann 
es ohne Prototypes auch nicht geben, ebensowenig wie bei den ungenannten 
Varargs-Parametern im aktuellen ABI.

Aber was du mit R22/23 meinst erschliesst sich mir nicht. Ich sehe 
keinen Weg, bei dem ein "int" als erstem Parameter in R22/23 landen 
könnte und von einer solchen Funktion auch dort erwartet würde.

: Bearbeitet durch User
von äöü (Gast)


Lesenswert?

Gibt's eigentlich einen Unterschied zwischen

"__inline"

und

"inline"

?

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

A. K. schrieb:
> Das avr-gcc ABI wurde eindeutig im Hinblick auf Prototypes definiert.
> Ohne sie hätte es anders ausfallen müssen. Aber auch ohne Prototypes
> wäre ein ABI definierbar, das eine begrenzte Anzahl Parameter in
> Registern übergibt (varargs/stdarg.h wäre dann nur etwas komplizierter).

Die Implementierung im GCC wäre marginal aufwändiger, ein paar Zeilen 
mehr und fertig ist der Lack.  Der erzeugte Code wäre kleiner, schneller 
-- sowohl auf Seiten des Callers als auch des Callees -- und verbrauchte 
weniger Stack.

> So gibt es Stellen, an denen das bestehende ABI überhaupt nur mit
> Prototypes implementierbar ist. Beispielsweise setzen so wie im ABI
> definiert Varargs-Funktionen eine Prototyp-Deklaration zwingend voraus,
> weshalb dein Beispiel mit Übergabe auf dem Stack eine direkte Folge der
> Entscheidung bei der ABI-Definition ist.

Bei der Implementierung hat Denis so lange mit der Registerverwendung 
rumgespielt, bis er die beste Codeerzeugung erreicht hatte :-)

Feinheiten wie die Parameterübergabe bei varargs hatte er bestimmt 
weniger auf dem Radar, wohl unter der Annahme, dass diese vor allem von 
den eh teuren printf/scanf Funktionen generiert werden.  Das trifft auch 
heute noch zu; nicht-stdio varargs-Funktionen bleiben Exoten.

> Aber was du mit R22/23 meinst erschliesst sich mir nicht. Ich sehe
> keinen Weg, bei dem ein "int" als erstem Parameter in R22/23 landen
> könnte und von einer solchen Funktion auch dort erwartet würde.
1
typedef struct { int a[9]; } S;
2
3
S fun (int i)
4
{
5
    S s;
6
    s.a[0] = i;
7
    return s;
8
}

fun erwartet i in R22/23:
1
fun:
2
  movw r30,r24
3
  std Z+1,r23 ;; da
4
  st Z,r22  ;; und da
5
  ret

Der erste Parameter ist implizit und die Adresse, wo der Aufrufer das 
Ergebnis haben möchte.  Und auf Seiten des Callers:
1
extern S yum (int);
2
3
void buh (S *s)
4
{
5
    *s = yum (0);
6
}

buh übergibt i natürlich in R22/23, hier für ATtiny24:
1
buh:
2
  push r16
3
  push r17
4
  push r28
5
  push r29
6
  in r28,__SP_L__
7
  clr r29
8
  subi r28,lo8(-(-18))
9
  out __SP_L__,r28
10
11
  movw r16,r24
12
  ldi r22,0 ;; da
13
  ldi r23,0 ;; und da
14
  movw r24,r28
15
  adiw r24,1
16
  rcall yum
17
  ;; blabla mit memcpy und Epilog...

Damit gibt es auch 2 Möglichkeiten, an welcher Stelle im Stack eine 
varargs-Funktion einen ersten int-Parameter erwartet.

Solche Rückgabewerte sind zwar jenseits des Horizonts der meisten 
Anwendungen, werden aber von C und entsprechend auch vom Compiler 
unterstützt.

Sogar wenn immer alle Argumente auf dem Stack übergeben werden, machen 
die Fälle solcher Rückgabewerte, die ein implizites Argument erzeugen, 
ohne Prototypen Probleme.  Selbst wenn das ABI immer ein implizites, 
erstes (Dummy-)Argument verlangte, wüsste man nicht, wieviel Daten die 
aufgerufene Funktion möglicherweise ab der übergebenen Adresse ablädt 
:-)

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.