mikrocontroller.net

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


Autor: Philipp Burch (philipp_burch)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
Hallo zusammen,

ich habe mal wieder eine Eigenartigkeit in meinem Programm entdeckt. 
Folgendes Problem: Ich habe zweimal den Code
if (length == 2) {
  val = va_arg(vl, int16_t);
} else {
  val = va_arg(vl, int32_t);
}
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:
237:                if (length == 2) {
+00000EB5:   81BE        LDD     R27,Y+6          Load indirect with displacement
+00000EB6:   30B2        CPI     R27,0x02         Compare with immediate
+00000EB7:   F409        BRNE    PC+0x02          Branch if not equal
+00000EB8:   C11B        RJMP    PC+0x011C        Relative jump
240:                  val = va_arg(vl, int32_t);
+00000EB9:   81E9        LDD     R30,Y+1          Load indirect with displacement
+00000EBA:   81FA        LDD     R31,Y+2          Load indirect with displacement
+00000EBB:   9634        ADIW    R30,0x04         Add immediate to word
+00000EBC:   83FA        STD     Y+2,R31          Store indirect with displacement
+00000EBD:   83E9        STD     Y+1,R30          Store indirect with displacement
+00000EBE:   90D2        LD      R13,-Z           Load indirect and predecrement
+00000EBF:   90C2        LD      R12,-Z           Load indirect and predecrement
+00000EC0:   90B2        LD      R11,-Z           Load indirect and predecrement
+00000EC1:   90A2        LD      R10,-Z           Load indirect and predecrement
243:      debug_send_var("val", val);
+00000EC2:   01B6        MOVW    R22,R12          Copy register pair
+00000EC3:   01A5        MOVW    R20,R10          Copy register pair
+00000EC4:   E380        LDI     R24,0x30         Load immediate
+00000EC5:   E091        LDI     R25,0x01         Load immediate
+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:
342:                if (length == 2) {
+0000101C:   81EE        LDD     R30,Y+6          Load indirect with displacement
+0000101D:   30E2        CPI     R30,0x02         Compare with immediate
+0000101E:   F409        BRNE    PC+0x02          Branch if not equal
+0000101F:   C215        RJMP    PC+0x0216        Relative jump
347:                  val = va_arg(vl, int32_t);
+00001020:   81E9        LDD     R30,Y+1          Load indirect with displacement
+00001021:   81FA        LDD     R31,Y+2          Load indirect with displacement
+00001022:   9634        ADIW    R30,0x04         Add immediate to word
+00001023:   83FA        STD     Y+2,R31          Store indirect with displacement
+00001024:   83E9        STD     Y+1,R30          Store indirect with displacement
+00001025:   9734        SBIW    R30,0x04         Subtract immediate from word
+00001026:   80C0        LDD     R12,Z+0          Load indirect with displacement
+00001027:   80D1        LDD     R13,Z+1          Load indirect with displacement
350:      debug_send_var("val", val);
+00001028:   01C6        MOVW    R24,R12          Copy register pair
+00001029:   27AA        CLR     R26              Clear Register
+0000102A:   27BB        CLR     R27              Clear Register
+0000102B:   878A        STD     Y+10,R24         Store indirect with displacement
+0000102C:   879B        STD     Y+11,R25         Store indirect with displacement
+0000102D:   87AC        STD     Y+12,R26         Store indirect with displacement
+0000102E:   87BD        STD     Y+13,R27         Store indirect with displacement
+0000102F:   01AC        MOVW    R20,R24          Copy register pair
+00001030:   01BD        MOVW    R22,R26          Copy register pair
+00001031:   E380        LDI     R24,0x30         Load immediate
+00001032:   E091        LDI     R25,0x01         Load immediate
+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):
        case 'f':
          //Fixed point number
          if (length == 2) {
            int16_t v;
            v = va_arg(vl, int16_t);
            val = v;
          } else {
            val = va_arg(vl, int32_t);
          }

debug_send_var("val", val);

          start = buf;

          if (val >= 0 && flags & FORCE_SIGN) {
            if (flags & USE_PADDING_SPACE) {
              *buf++ = ' ';
            } else {
              *buf++ = '+';
            }
          }

          bcnt += buf - start;
        
          if (precision || val) {
            if (length == 2) {
              itoa((int16_t)val, buf, 10);
            } else {
              ltoa(val, buf, 10);
            }
          } else {
            *buf = 0;
          }

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

Autor: Simon K. (simon) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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"

Autor: Philipp Burch (philipp_burch)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Philipp Burch (philipp_burch)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht 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.

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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:
                                case 'f':
                                        //Fixed point number
                                {
                                  int32_t x;
                                        if (length == 2) {
                                                //int16_t v;
                                                val = va_arg(vl, int16_t);
                                                x = val;
                                        } else {
                                          x = va_arg(vl, int32_t);
                                          
                                        }

                                        debug_send_var("val", x);
                                val = x;
                                }

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

Autor: Philipp Burch (philipp_burch)
Datum:

Bewertung
0 lesenswert
nicht 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:
>
>
>                                 case 'f':
>                                         //Fixed point number
>                                 {
>                                   int32_t x;
>                                         if (length == 2) {
>                                                 //int16_t v;
>                                                 val = va_arg(vl,
> int16_t);
>                                                 x = val;
>                                         } else {
>                                           x = va_arg(vl, int32_t);
> 
>                                         }
> 
>                                         debug_send_var("val", x);
>                                 val = x;
>                                 }
> 
>
> ...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.

Autor: Philipp Burch (philipp_burch)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Urks. Ich denke, ich habe den Fehler gefunden...
            len += n;
          }
          break;

        case 'p':
          //Pointer (16 Bit integer, format hh:ll
          buf[0] = '0'; buf[1] = '0'; buf[2] = '0';
          uint16_t val = (uint16_t)va_arg(vl, void *);
          n = 0;
          if (val < 0x1000) ++n;
          if (val < 0x0100) ++n;
          if (val < 0x0010) ++n;
          LtoA(val, buf + n, 16);
          buf[4] = buf[3];
          buf[3] = buf[2];
          buf[2] = ':';
          buf += 5;
          bcnt += 5;
          break;

        case 'f':
        case 'F':
          //Fixed point number
          if (length == 2) {
            val = va_arg(vl, int16_t);
          } else {
            val = va_arg(vl, int32_t);
          }

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

Ach herrje...

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Philipp Burch (philipp_burch)
Datum:

Bewertung
0 lesenswert
nicht 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...

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.