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.
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.
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;
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 ;-)
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.
>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
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.
Hier noch ein interesantes Stückchen Code:
msgQueueFree ist vom Type unsigned char
1
boolMQCheckFree_i(unsignedcharsize){
2
returnsize<=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.
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
intmain()
2
{
3
intres;
4
5
res=foo();
6
}
7
8
usignedcharfoo()
9
{
10
return1;
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.
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.
@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.
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...
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.
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.
> 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.
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.