Forum: Compiler & IDEs va_arg() und signed long: Wo ist das Vorzeichen?!


von Philipp B. (philipp_burch)


Angehängte Dateien:

Lesenswert?

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
16
243:      debug_send_var("val", val);
17
+00000EC2:   01B6        MOVW    R22,R12          Copy register pair
18
+00000EC3:   01A5        MOVW    R20,R10          Copy register pair
19
+00000EC4:   E380        LDI     R24,0x30         Load immediate
20
+00000EC5:   E091        LDI     R25,0x01         Load immediate
21
+00000EC6:   940E1345    CALL    0x00001345       Call subroutine

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
23
+0000102F:   01AC        MOVW    R20,R24          Copy register pair
24
+00001030:   01BD        MOVW    R22,R26          Copy register pair
25
+00001031:   E380        LDI     R24,0x30         Load immediate
26
+00001032:   E091        LDI     R25,0x01         Load immediate
27
+00001033:   940E1345    CALL    0x00001345       Call subroutine
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_t v;
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

von Simon K. (simon) Benutzerseite


Lesenswert?

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"

von Philipp B. (philipp_burch)


Lesenswert?

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Philipp B. (philipp_burch)


Angehängte Dateien:

Lesenswert?

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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_t x;
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.

von Philipp B. (philipp_burch)


Lesenswert?

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_t x;
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.

von Philipp B. (philipp_burch)


Lesenswert?

Urks. Ich denke, ich habe den Fehler gefunden...
1
            len += n;
2
          }
3
          break;
4
5
        case 'p':
6
          //Pointer (16 Bit integer, format hh:ll
7
          buf[0] = '0'; buf[1] = '0'; buf[2] = '0';
8
          uint16_t val = (uint16_t)va_arg(vl, void *);
9
          n = 0;
10
          if (val < 0x1000) ++n;
11
          if (val < 0x0100) ++n;
12
          if (val < 0x0010) ++n;
13
          LtoA(val, buf + n, 16);
14
          buf[4] = buf[3];
15
          buf[3] = buf[2];
16
          buf[2] = ':';
17
          buf += 5;
18
          bcnt += 5;
19
          break;
20
21
        case 'f':
22
        case 'F':
23
          //Fixed point number
24
          if (length == 2) {
25
            val = va_arg(vl, int16_t);
26
          } else {
27
            val = va_arg(vl, int32_t);
28
          }

Bei case 'p' deklariere ich val als uint16_t. Warum zur Hölle meckert 
der Compiler da nicht wegen einer Neudeklaration?!

Ach herrje...

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Philipp B. (philipp_burch)


Lesenswert?

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...

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.