Hallo zusammen,
ich habe mal wieder eine Eigenartigkeit in meinem Programm entdeckt.
Folgendes Problem: Ich habe zweimal den Code
1
if(length==2){
2
val=va_arg(vl,int16_t);
3
}else{
4
val=va_arg(vl,int32_t);
5
}
in einem Switch-Statement. 'val' ist ein int32_t und übergeben (Also von
va_arg gelesen) wird jeweils ein 16- oder 32-Bit-Wert, abhängig von
'length'. Soweit so gut. Beim ersten Auftreten dieses Codes funktioniert
auch alles einwandfrei:
1
237: if (length == 2) {
2
+00000EB5: 81BE LDD R27,Y+6 Load indirect with displacement
3
+00000EB6: 30B2 CPI R27,0x02 Compare with immediate
4
+00000EB7: F409 BRNE PC+0x02 Branch if not equal
5
+00000EB8: C11B RJMP PC+0x011C Relative jump
6
240: val = va_arg(vl, int32_t);
7
+00000EB9: 81E9 LDD R30,Y+1 Load indirect with displacement
8
+00000EBA: 81FA LDD R31,Y+2 Load indirect with displacement
9
+00000EBB: 9634 ADIW R30,0x04 Add immediate to word
10
+00000EBC: 83FA STD Y+2,R31 Store indirect with displacement
11
+00000EBD: 83E9 STD Y+1,R30 Store indirect with displacement
12
+00000EBE: 90D2 LD R13,-Z Load indirect and predecrement
13
+00000EBF: 90C2 LD R12,-Z Load indirect and predecrement
14
+00000EC0: 90B2 LD R11,-Z Load indirect and predecrement
15
+00000EC1: 90A2 LD R10,-Z Load indirect and predecrement
Es werden alle vier Bytes vom Stack geholt (Wenn length != 2 ist),
soweit ist alles bestens. In einem switch-Zweig etwas weiter unten ist
nun nochmal exakt die Selbe if-Anweisung mit der Zuweisung nach 'val'.
Das Disassembly davon sieht aber ein klein wenig anders aus:
1
342: if (length == 2) {
2
+0000101C: 81EE LDD R30,Y+6 Load indirect with displacement
3
+0000101D: 30E2 CPI R30,0x02 Compare with immediate
4
+0000101E: F409 BRNE PC+0x02 Branch if not equal
5
+0000101F: C215 RJMP PC+0x0216 Relative jump
6
347: val = va_arg(vl, int32_t);
7
+00001020: 81E9 LDD R30,Y+1 Load indirect with displacement
8
+00001021: 81FA LDD R31,Y+2 Load indirect with displacement
9
+00001022: 9634 ADIW R30,0x04 Add immediate to word
10
+00001023: 83FA STD Y+2,R31 Store indirect with displacement
11
+00001024: 83E9 STD Y+1,R30 Store indirect with displacement
12
+00001025: 9734 SBIW R30,0x04 Subtract immediate from word
13
+00001026: 80C0 LDD R12,Z+0 Load indirect with displacement
14
+00001027: 80D1 LDD R13,Z+1 Load indirect with displacement
15
350: debug_send_var("val", val);
16
+00001028: 01C6 MOVW R24,R12 Copy register pair
17
+00001029: 27AA CLR R26 Clear Register
18
+0000102A: 27BB CLR R27 Clear Register
19
+0000102B: 878A STD Y+10,R24 Store indirect with displacement
20
+0000102C: 879B STD Y+11,R25 Store indirect with displacement
21
+0000102D: 87AC STD Y+12,R26 Store indirect with displacement
22
+0000102E: 87BD STD Y+13,R27 Store indirect with displacement
Aber hallo?! Die zwei oberen Bytes des Arguments interessieren den
Compiler wohl nicht, wie? Etwas später im Code wird der Wert von 'val'
dann auch auf >= 0 geprüft, das ist dann logischerweise immer true.
Doch wie kommt es, dass sich der Compiler nicht um das Vorzeichen (Bzw.
beide oberen Bytes) schert wenn ich einen int32_t von der Argumentliste
hole?
Der folgende Code sieht übrigens so aus (Bei der fehlerhaften Stelle):
1
case'f':
2
//Fixed point number
3
if(length==2){
4
int16_tv;
5
v=va_arg(vl,int16_t);
6
val=v;
7
}else{
8
val=va_arg(vl,int32_t);
9
}
10
11
debug_send_var("val",val);
12
13
start=buf;
14
15
if(val>=0&&flags&FORCE_SIGN){
16
if(flags&USE_PADDING_SPACE){
17
*buf++=' ';
18
}else{
19
*buf++='+';
20
}
21
}
22
23
bcnt+=buf-start;
24
25
if(precision||val){
26
if(length==2){
27
itoa((int16_t)val,buf,10);
28
}else{
29
ltoa(val,buf,10);
30
}
31
}else{
32
*buf=0;
33
}
Im Anhang mal der betreffende Code. Es handelt sich um eine eigene
Implementierung von printf() die meinen Anforderungen besser entsprechen
sollte...
Mit meinem Latein bin ich nun aber wirklich am Ende. Vielleicht kann mir
jemand anderes sagen, wo ich den Hund begraben habe...
EDIT: BTW: Ich weiss, der Code ist teilweise nicht so dolle und auch
nicht übermässig effizient. Verbesserungsvorschläge dazu sind natürlich
auch willkommen!
Danke und Gruss,
Philipp
Philipp Burch wrote:
> EDIT: BTW: Ich weiss, der Code ist teilweise nicht so dolle und auch> nicht übermässig effizient. Verbesserungsvorschläge dazu sind natürlich> auch willkommen!
Zu deinem Problem kann ich leider nichts sagen, aber statt itoa() ist
die Subtraktionsmethode schneller.
Beitrag "Zahlenausgabe"
Simon K. wrote:
> Philipp Burch wrote:>> EDIT: BTW: Ich weiss, der Code ist teilweise nicht so dolle und auch>> nicht übermässig effizient. Verbesserungsvorschläge dazu sind natürlich>> auch willkommen!>> Zu deinem Problem kann ich leider nichts sagen, aber statt itoa() ist> die Subtraktionsmethode schneller.>> Beitrag "Zahlenausgabe"
Hm, itoa() verwendet also tatsächlich "normale" Divisionen... Naja,
werde ich mir halt auch noch eigene Pendants schreiben, danke für den
Hinweis. Ausserdem könnte ich dann gleich noch die technische
Schreibweise einbauen, dürfte manchmal noch ganz praktisch sein.
Philipp Burch wrote:
Ich mag diese Disassembler-Listings mit dem vermutlicherweise an
dieser Stelle zugehörigen Sourcecode drin überhaupt nicht. Ich
würde mir das Teil gern selbst compilieren (in eine Assembler-Datei),
aber...
> Im Anhang mal der betreffende Code.
...der lässt sich so nicht compilieren (fehlender Headerdateien).
Außerdem hast du weder deinen MCU type noch die Compileroptionen
dazu geschrieben.
Jörg Wunsch wrote:
> Philipp Burch wrote:>> Ich mag diese Disassembler-Listings mit dem vermutlicherweise an> dieser Stelle zugehörigen Sourcecode drin überhaupt nicht. Ich> würde mir das Teil gern selbst compilieren (in eine Assembler-Datei),> aber...
Ok, sorry.
>> Im Anhang mal der betreffende Code.>> ...der lässt sich so nicht compilieren (fehlender Headerdateien).> Außerdem hast du weder deinen MCU type noch die Compileroptionen> dazu geschrieben.
MCU ist ATmega644, F_CPU ist 2MHz, Optimierung O2, sonst keine
speziellen Einstellungen.
Im Anhang mal das komplette Projekt. Sollte sich mit WinAVR ohne
Probleme kompilieren lassen, Makefile ist auch drin. Problematisch ist
printf.c.
Danke für deine Mühe.
Gut, WinAVR habe ich nicht, aber das genügte, dass ich das Compilieren
nachvollziehen kann.
Warum das genau passiert, habe ich auf die Schnelle auch nicht
erfasst. Aus irgendeinem Grund ist der Compiler der Meinung, diese
beiden Bytes werden nicht benutzt. Wenn ich den Code so abändere:
1
case'f':
2
//Fixed point number
3
{
4
int32_tx;
5
if(length==2){
6
//int16_t v;
7
val=va_arg(vl,int16_t);
8
x=val;
9
}else{
10
x=va_arg(vl,int32_t);
11
12
}
13
14
debug_send_var("val",x);
15
val=x;
16
}
...dann scheint mir das Ergebnis korrekt zu sein.
Könnte ein Compilerbug sein, ist aber auf Grund des riesigen Umfangs
schwer zu sagen. Wenn du willst, kannst du mal auf der avr-gcc-list
(at nongnu.org) nachfragen, dort lesen einige Leute mit, die sich
mit dem GCC besser auskennen.
Jörg Wunsch wrote:
> Gut, WinAVR habe ich nicht, aber das genügte, dass ich das Compilieren> nachvollziehen kann.>> Warum das genau passiert, habe ich auf die Schnelle auch nicht> erfasst. Aus irgendeinem Grund ist der Compiler der Meinung, diese> beiden Bytes werden nicht benutzt. Wenn ich den Code so abändere:>>
1
>case'f':
2
>//Fixed point number
3
>{
4
>int32_tx;
5
>if(length==2){
6
>//int16_t v;
7
>val=va_arg(vl,
8
>int16_t);
9
>x=val;
10
>}else{
11
>x=va_arg(vl,int32_t);
12
>
13
>}
14
>
15
>debug_send_var("val",x);
16
>val=x;
17
>}
18
>
>> ...dann scheint mir das Ergebnis korrekt zu sein.
Leider nein. Dann werden zwar alle vier Bytes korrekt ausgelesen aber
nur die unteren beiden weiterverwendet...
(Zumindest bei mir mit dem gerade aktualisierten GCC 4.2.2)
> Könnte ein Compilerbug sein, ist aber auf Grund des riesigen Umfangs> schwer zu sagen. Wenn du willst, kannst du mal auf der avr-gcc-list> (at nongnu.org) nachfragen, dort lesen einige Leute mit, die sich> mit dem GCC besser auskennen.
Gemacht.
Philipp Burch wrote:
> Bei case 'p' deklariere ich val als uint16_t.
Ich hatte sowas vermutet, hab's aber auch nicht wirklich gefunden.
Ich fand das Ganze, ähem, ein wenig unübersichtlich gestrickt...
Aber ich weiß, das vfprintf aus der avr-libc ist auch nicht viel
besser.
> Warum zur Hölle meckert> der Compiler da nicht wegen einer Neudeklaration?!
Weil sie in einem anderen Scope erfolgt war. Die geschweifte Klammer
nach dem switch hat einen neuen Block eröffnet. Das Überschatten
von gleichnamigen Definitionen wird nur angewarnt, wenn man sich damit
einen Funktionsparameter überschattet.
Jörg Wunsch wrote:
> Philipp Burch wrote:>>> Bei case 'p' deklariere ich val als uint16_t.>> Ich hatte sowas vermutet, hab's aber auch nicht wirklich gefunden.> Ich fand das Ganze, ähem, ein wenig unübersichtlich gestrickt...
Ich weiss, mein Code sieht leider meistens irgendwie so aus...
> Aber ich weiß, das vfprintf aus der avr-libc ist auch nicht viel> besser.
Beruhigend zu hören ;)
>> Warum zur Hölle meckert>> der Compiler da nicht wegen einer Neudeklaration?!>> Weil sie in einem anderen Scope erfolgt war. Die geschweifte Klammer> nach dem switch hat einen neuen Block eröffnet. Das Überschatten> von gleichnamigen Definitionen wird nur angewarnt, wenn man sich damit> einen Funktionsparameter überschattet.
Hm, er warnt mich selbst dann nicht, wenn ich im selben Block zweimal
denselben Variablennamen verwende? Ziemlich gefährlich. Naja, ich weiss
schon, der Compiler nimmt einem das Denken nicht ab...