Forum: Compiler & IDEs Was macht diese return Anweisung?


von Be M. (bemi)


Lesenswert?

Hallo,

irgendwie mag mich der Compiler nicht :-(
1
typedef char bool;
2
3
23:         if (msize > free) {UNLOCK_IRQ; return false;}             // fail, not enough space on MQ
4
+00000060:   1792        CP      R25,R18          Compare
5
+00000061:   F420        BRCC    PC+0x05          Branch if carry cleared
6
+00000062:   9478        SEI                      Global Interrupt Enable
7
+00000063:   E080        LDI     R24,0x00         Load immediate
8
+00000064:   E090        LDI     R25,0x00         Load immediate
9
+00000065:   9508        RET                      Subroutine return
1
typedef short bool;
2
3
23:         if (msize > free) {UNLOCK_IRQ; return false;}             // fail, not enough space on MQ
4
+00000060:   1792        CP      R25,R18          Compare
5
+00000061:   F420        BRCC    PC+0x05          Branch if carry cleared
6
+00000062:   9478        SEI                      Global Interrupt Enable
7
+00000063:   E080        LDI     R24,0x00         Load immediate
8
+00000064:   E090        LDI     R25,0x00         Load immediate
9
+00000065:   9508        RET                      Subroutine return
1
typedef long bool;
2
3
23:         if (msize > free) {UNLOCK_IRQ; return false;}             // fail, not enough space on MQ
4
+00000060:   1792        CP      R25,R18          Compare
5
+00000061:   F430        BRCC    PC+0x07          Branch if carry cleared
6
+00000062:   9478        SEI                      Global Interrupt Enable
7
+00000063:   E060        LDI     R22,0x00         Load immediate
8
+00000064:   E070        LDI     R23,0x00         Load immediate
9
+00000065:   E080        LDI     R24,0x00         Load immediate
10
+00000066:   E090        LDI     R25,0x00         Load immediate
11
+00000067:   9508        RET                      Subroutine return

Ich habe bool per typedef als Type definiert. Die drei hier 
dargestellten Fragmente sind ein Teil einer Funktion, die den 
Rückgabewert bool hat. Eigentlich hatte ich bool als char definiert (1. 
Beispiel). Wie zu sehen ist soll false zurückgegeben werden. In diesem 
Fall wird also 0 in R24/25 zurückgegeben. Eigenlich komisch, dass hier 
zwei Register auf 0 gesetzt werden, da bool ja nur vom Type char ist.
Gleiche Spielchen, aber dieses Mal habe ich bool als short definiert. Es 
wird abermals 0 in R24/25 zurückgegeben. Das gleiche wie bei char.
3. Beispiel: Dieses Mal ist bool vom Type long und siehe da, es wird 0 
in R22/23/24/25 zurückgeliefert.
Mein Fazit: Bei long ist der Rückgabewert 4 Byte, bei short ist der 
Rückgabewert 2 Byte und bei char ist der Rückgabewert ebenfalls 2 Byte.

Mache ich irgendwo einen Denkfehler? Optimierung (-Os) ist aktiv.

von Stefan B. (stefan) Benutzerseite


Lesenswert?

Es kommt auf den Rückgabewert der Funktion an und nicht, wie du 
innerhalb der Funktion eine Variable definiert hast, die zur Bildung des 
Rückgabewerts benutzt wird.

von Be M. (bemi)


Lesenswert?

1
bool Reserve(unsigned char size) {
2
  unsigned char msize=size+1;
3
  LOCK_IRQ
4
  MQSIZE free=msgQueueFree;
5
  if (msize > free) {UNLOCK_IRQ; return false;}             // fail, not enough space on MQ
6
  msgQueueFree = free-msize;                                // new free space
7
  unsigned char * wp = msgQueueWritePtr;
8
  msgQueueWritePtrTemp = wp;                                // remember current start position for later writing
9
  if ( (wp += msize) >= msgQueue+64) wp -= 64;
10
  msgQueueWritePtr = wp;
11
  UNLOCK_IRQ
12
  *msgQueueWritePtrTemp++ = size;
13
  if (msgQueueWritePtrTemp==msgQueue+64) msgQueueWritePtrTemp=msgQueue;
14
  return true;
15
}
Der Rückgabewert ist ja bool.

von Karl H. (kbuchegg)


Lesenswert?

Ich könnte mir durchaus vorstellen, dass das pure Absicht ist.

<Spekulation an>
Die Idee dahinter könnte folgende sein:
Ist beim Aufruf der Funktion kein Protoyp sichtbar, dann muss der
Compiler Standardannahmen treffen. Eine dieser Standardannahmen
lautet, dass der Returnwert ein int ist.
Ist die Situation tatsächlich so, dass es keinen Prototypen
für diese Funktion beim Aufruf gibt, so ist man daher auf der
sicheren Seite, wenn man ein 16 Bit Ergebnis liefert. Zumal das
nun auch wieder nicht der riesen Aufwand ist.
Für den Fall, dass alles in Ordnung ist, beim AUfrufer daher ein
Protoyp sichtbar ist, verliert man nicht allzuviel, weil dieser
eine Registerlader nun auch wieder nicht so gross zuschlägt.
<Spekualtion aus>

Was passiert eigentlich, wenn du einen unsigned char draus machst:


typedef unsigned char bool;

von Stefan B. (stefan) Benutzerseite


Lesenswert?

Der Rückgabewert bei char ist in einem, short (int) und int in zwei 
Register und bei long (int) vier Register.

Man kann tatsächlich darüber diskutieren, ob bei char-Rückgabe bei der 
ASM-Codegenerierung eine Anweisung (LDI R25,0x00) eingespart werden 
könnte.

Schlage es den Compiler-Entwicklern vor. BTW. mit welcher Optimierung 
arbeitest du? Vielleicht ist diese Optimierung schon drin ;-)

von Be M. (bemi)


Lesenswert?

Wie schon gesagt, compiliere ich mit (-Os) wenn ich mir den erzeugten 
ASM Code ansehen will. Zum debuggen lass ich da lieber die Optimierung 
aus.

@Karl Heinz:
Was meinst Du mit "Protoyp sichtbar"? Die Funktion wird allgemein in 
einer Headerdatei deklariert. Abgesehen davon, bei long hat der Compiler 
es ja auch mitbekommen, warum sollte er es dann bei char nicht erkennen?
Auch bei unsigned char sind es 2 Byte.

von Mark .. (mork)


Lesenswert?

>Der Rückgabewert bei char ist in einem ... Register

Auszug aus der avr-libc-FAQ:Return values:
8-bit in r24 (not r25!), 16-bit in r25:r24, up to 32 bits in r22-r25, up 
to 64 bits in r18-r25. 8-bit return values are zero/sign-extended to 16 
bits by the caller (unsigned char is more efficient than signed char - 
just clr r25)

MfG Mark

von Be M. (bemi)


Lesenswert?

Was ich auch mache, es werden nicht weniger als 2 Byte (auch bei 
unsigned char).

Welches ist eigetlich die beste Optimierung für Speed? O3?

Der Compiler macht da manchmal eigenartige Dinge.

Beispielsweise macht er einen RJMP nach vorne, schreibt dann die 
Rückgabewerte R24/25 macht noch einen RJMP um dann zwei POPs 
durchzufüren bevor dann RET kommt.

Wäre es da nicht besser die 2 RJMP wegzulassen und gleich statt dessen 
Rückgabewert schreiben;POP;POP;RET Direkt hinter die Rückgabewerte zu 
hängen? Nun ja, vielleicht sollte man sich auch einfach nicht so viele 
Gedanken machen, aber in ASM würde man so vieles anders machen.

von Be M. (bemi)


Lesenswert?

Hier noch ein interesantes Stückchen Code:
msgQueueFree ist vom Type unsigned char
1
bool MQCheckFree_i(unsigned char size) {
2
  return size<=msgQueueFree;
3
} // end of MQCheckSpace_i
1
@000000FF: MQCheckFree_i
2
72:       bool MQCheckFree_i(unsigned char size) {
3
+000000FF:   91900103    LDS     R25,0x0103       Load direct from data space
4
+00000101:   E020        LDI     R18,0x00         Load immediate
5
+00000102:   E030        LDI     R19,0x00         Load immediate
6
+00000103:   1798        CP      R25,R24          Compare
7
+00000104:   F010        BRCS    PC+0x03          Branch if carry set
8
+00000105:   E021        LDI     R18,0x01         Load immediate
9
+00000106:   E030        LDI     R19,0x00         Load immediate
10
74:       } // end of MQCheckSpace_i
11
+00000107:   01C9        MOVW    R24,R18          Copy register pair
12
+00000108:   9508        RET                      Subroutine return

Was passiert hier? Zuerst wird msgQueueFree in R25 geladen. Dann wird 
eine Dummyvariable R18/19 auf false gesetzt (2 Byte!) dann werden die 2 
Bytewerte verglichen und eventuel ein 2 Byte Temp Variable R18/19 auf 
true gesetzt.
Zum Schluss wird das niederwertige Byte der 2-Byte Temp Variable nach 
R24 (dieses mal nur 1. Byte Rückgabewert) kopiert bevor es zurück geht.

Verdächtig, verdächtig... Vielleicht sollte ich aufhören den Objektcode 
weiter zu analysieren.

von Karl H. (kbuchegg)


Lesenswert?

Bernd M. wrote:

> @Karl Heinz:
> Was meinst Du mit "Protoyp sichtbar"? Die Funktion wird allgemein in
> einer Headerdatei deklariert.

In deinem Fall. Super das du das so machst.
Das muss aber nicht so sein. Also haben die Compilerbauer
eine Strategie genommen, der auch in solchen Fällen funktioniert.

> Abgesehen davon, bei long hat der Compiler
> es ja auch mitbekommen, warum sollte er es dann bei char nicht erkennen?
> Auch bei unsigned char sind es 2 Byte.

Ich sag ja: Wenn der Compiler die Funktion selbst kompiliert, hat
er keine Information darüber, wie sie verwendet wird. Solange
du alles sauber den Regeln nach machst, ist ja auch alles paletti.
Aber dann gibt es ja auch noch die Leute, die unbelehrbar an alten
'Standards festhalten' und den Compiler dazu zwingen Annahmen zu
treffen. Wenn man haben möchte, dass zumindest eine gewisse Chance
besteht, dass auch deren Code funktioniert, kann man einfache
Sicherungen einbauen, die
 * nicht allzuviel kosten
 * zumindest einige dieser Sonderfälle abdecken.

Bsp:
1
int main()
2
{
3
  int res;
4
5
  res = foo();
6
}
7
8
usigned char foo()
9
{
10
  return 1;
11
}

An der Stelle des Aufrufs von foo, hat der Compiler noch keinen
Protoypen für foo gesehen. Daher gelten Standardannahmen. Eine davon
ist, dass die Funktion einen int liefert. Der Compiler holt sich
also über den normal vereinbarten Return-Mechanismus einen int und
legt ihn in res ab.
Jetzt kommt aber die Funktion foo. Und die liefert keinen int.
Die liefert nur einen unsigned char. Wenn der Aufrufer sich daher
2 Bytes holen wird, dann wird er 1 Byte vorfinden, welches nie
auf einen definierten Wert gesetzt wurde.

Das ganze ist aber, wie gesagt Spekulation.

von Benedikt K. (benedikt)


Lesenswert?

Bernd M. wrote:

> Was passiert hier? Zuerst wird msgQueueFree in R25 geladen. Dann wird
> eine Dummyvariable R18/19 auf false gesetzt (2 Byte!) dann werden die 2
> Bytewerte verglichen und eventuel ein 2 Byte Temp Variable R18/19 auf
> true gesetzt.

Genau, alles schön nach ANSI C Norm.

> Zum Schluss wird das niederwertige Byte der 2-Byte Temp Variable nach
> R24 (dieses mal nur 1. Byte Rückgabewert) kopiert bevor es zurück geht.

Nein, es werden beide Bytes kopiert. Ist schon richtig so, wenn auch 
nicht gerade effizient.

von Be M. (bemi)


Lesenswert?

@Karl Heinz:
Wirklich belehrbar bin ich auch nicht. Ich habe C nie expliziet gelernt. 
Ich versuche einfach immer die C++ Regeln und hoffe das C das auch 
irgendwie kapiert ;-)

@Benedikt:
Ups, da habe ich das W am Ende übersehen.

Der C-Standard ist mir nicht geläufig, aber kann es sein, dass unnötige 
2-Byte Operationen ausgeführt werden, obwohl man es auch mit einem Byte 
realisieren kann und dass trotz Optimierung?

Ich kann es zwar jetzt nicht mit Sicherheit sagen, aber meines Wissens 
machen andere Compiler das nicht. Gibt es vielleicht irgend eine 
Einstellung, die dem Compiler sagt, dass er das lassen soll immer 
unnötige Operationen durchzuführen. Da werden ja unter Umständen mehrere 
Prozent der Rechenleistung verbraten, die man lieber anderweitig nutzen 
würde.

von Benedikt K. (benedikt)


Lesenswert?

Bernd M. wrote:
> Der C-Standard ist mir nicht geläufig, aber kann es sein, dass unnötige
> 2-Byte Operationen ausgeführt werden, obwohl man es auch mit einem Byte
> realisieren kann und dass trotz Optimierung?

bool == int
Es gibt in C zwar bool nicht als eigenen Datentyp, aber das Ergebnis 
eines Vergleichs ist bool/int.

> Ich kann es zwar jetzt nicht mit Sicherheit sagen, aber meines Wissens
> machen andere Compiler das nicht. Gibt es vielleicht irgend eine
> Einstellung, die dem Compiler sagt, dass er das lassen soll immer
> unnötige Operationen durchzuführen. Da werden ja unter Umständen mehrere
> Prozent der Rechenleistung verbraten, die man lieber anderweitig nutzen
> würde.

Man kann int auf 8bit setzen (die genau Option dazu weiß ich nicht 
auswendig). Damit verstößt man aber gegen die ANSI C Norm. Glaube mir, 
das ist dann genauso nervig, denn dann muss man z.B. für char*char auf 
int casten, damit das richtige rauskommt (z.B. bei Codevision).

Der gcc hält sich halt relativ stark an ANSI C...

von Be M. (bemi)


Lesenswert?

Heißt das, dass C außer für den Speicher kein char (1 Byte) kennt und 
immer alles auf 16 Bit Ebene macht?

Bei meinem Beispiel oben stellt man sich ja auch noch die Frage, warum 
der return-Wert erst in R18/19 landet und dann noch einmal umkopiert 
werden muss. Sollte ein optimierender Compiler die Werte nicht gleich in 
die Register 24/25 schreiben. OK R25 wird noch mal verwendet, aber da 
LDI die Flags ja nicht beeinflusst könnte der Vergleich doch als erstes 
durchgeführt werden und dann abhängig vom C-Flag die Register geladen 
werden. Damit würde zumindes das MOVW enfallen.

von Benedikt K. (benedikt)


Lesenswert?

C muss alle Operationen mindestens als int ausführen. Und int ist 
mindestens 16bit groß.

von Be M. (bemi)


Lesenswert?

C vieleicht schon, aber der Optimizer? Ist der nicht dafür da, 
offensichtlich überflüssige Operationen zu eliminieren?

von Andreas K. (a-k)


Lesenswert?

Bernd M. wrote:

> C vieleicht schon, aber der Optimizer? Ist der nicht dafür da,
> offensichtlich überflüssige Operationen zu eliminieren?

Eigentlich schon. Aber GCC wurde in der Annahme gebaut, dass die 
Zielmaschine mit dem Datentyp "int" gut zurecht kommt. Nicht zuletzt 
weil C so definiert ist. Viele Zielmaschinen kommen mit "int" sogar 
besser zurecht als mit kleineren Typen.

Da AVR die einzige Zielmaschine ist, bei der das umgekehrt läuft, sind 
die derartigen Optimierungen folglich nicht dort untergebracht wo sie am 
effektivsten wirken würden, nämlich im maschinenunspezifischen Teil, 
sondern befinden sich im AVR-spezifischen Teil. Und das was man da 
machen kann hat Grenzen und ist ziemlich viel Gefrickel für allerlei 
Einzelfälle.

von der mechatroniker (Gast)


Lesenswert?

> C vieleicht schon, aber der Optimizer? Ist der nicht dafür da,
> offensichtlich überflüssige Operationen zu eliminieren?

Wie soll er das über Funktionsaufrufgrenzen hinweg machen, wenn du es 
ihm nicht erlaubst (-Os)? Funktionsübergreifend optimiert wird eben nur 
bei -O3. Wenn du dann noch die Funktion als static deklarierst, so daß 
der Compiler sicher ist, daß sie nicht in einem anderen Sourcefile in 
einer Weise verwendet wird, in der eine solche Optimierung Probleme 
bereiten würde, dann und nur dann kann eine Optimierung wie du sie 
vorhast überhaupt stattfinden.

von Simon K. (simon) Benutzerseite


Lesenswert?

Die von Benedikt angesprochene Compileroption -mint8 ist aber mit 
Vorsicht zu genießen, da Funktionen, die mit int >= 2bytes designed 
wurden nun nur noch auf 1byte laufen.

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.