mikrocontroller.net

Forum: PC-Programmierung C: Unterwartetes Vorzeichen


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
Autor: Walter T. (nicolas)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
Guten Morgen,

ich habe gerade ein Brett vorm Kopf. Ich habe eine primitive Funktion, 
die aus Debug-Gründen so aussieht:
#include <stdint.h>
#include <stdio.h>

int_fast8_t mocksdl_encoder_get_diff(void);

void egml_getEncoderIncrement(int *enc)
{
    int dx =  mocksdl_encoder_get_diff();

    *enc += dx;
    printf("size=%i,%i ", sizeof(*enc), sizeof(enc));
    printf("dx=%i, enc0 = %i", dx, *enc);
    //sclamp(enc, 0, egml_action_end-1);
    printf(" enc1 = %i\n", *enc);
}


// Sinngemaess
int main(void)
{
    volatile struct
    {
        int enc;
    }
    State;
    State.enc = 0;
    
    while(1)
    {
       egml_getEncoderIncrement(&(State.enc));

    }
    return 0;
}

Die Konsolenausgabe sieht so aus:
...
size=4,8 dx=0, enc0 = 18615 enc1 = 18615
size=4,8 dx=0, enc0 = 18615 enc1 = 18615
size=4,8 dx=0, enc0 = 18615 enc1 = 18615
size=4,8 dx=0, enc0 = 18615 enc1 = 18615
size=4,8 dx=255, enc0 = 18870 enc1 = 18870
size=4,8 dx=0, enc0 = 18870 enc1 = 18870
...
(mocksdl_encoder_get_diff() gibt üblicherweise -1, 0 oder 1 zurück.)
Das Ganze wird mit MinGW unter Windows x64 kompiliert. Der Rückgabewert 
dieser Funktion und dx sind vorzeichenbehaftet. printf() hat auch 
Parameter für vorzeichenbehaftete Zahlen bekommen. Trotzdem erfolgt die 
Zuweisung so, als wäre der Rückgabewert unsigned char.

Im Compiler gibt es natürlich eine Warnung, weil der Struct als volatile 
deklariert ist, weil er sonst wegoptimiert würde.

Was passiert hier?


Nachtrag: Wenn ich den Schnipsel in einzeln baue (siehe angehängte 
Datei), kann ich den Fehler nicht nachvollziehen. Die 
Original-Funktionen so abzuspecken, dass sie sich einzeln bauen lassen, 
wird ein Weilchen dauern. Vielleicht fällt dem einen oder anderen aber 
trotzdem eine Möglichkeit ein, was mir oben in die Suppe spucken könnte.

: Bearbeitet durch User
Autor: Irgend W. (Firma: egal) (irgendwer)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Walter T. schrieb:
> mocksdl_encoder_get_diff()

...und wo ist der Beweis dafür das nicht diese Funktion schon die 255 
liefert?

Autor: Rolf M. (rmagnus)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Irgend W. schrieb:
> ...und wo ist der Beweis dafür das nicht diese Funktion schon die 255
> liefert?

Der liegt darin:
int_fast8_t mocksdl_encoder_get_diff(void);
und darin, dass unter mingw64 int_fast8_t ein typedef für signed char 
ist.
Der kann gar nicht den Wert 255 annehmen.

Das Problem könnte ggf hier liegen:

Walter T. schrieb:
> printf("size=%i,%i ", sizeof(*enc), sizeof(enc));

Auf einem 64-Bit-System wird size_t auch 64 Bit breit sein, aber %i 
erwartet nur 32 Bit. Da sollte eigentlich auch eine Warnung kommen. 
Richtig wäre hier %z.

Autor: Walter T. (nicolas)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
Rolf M. schrieb:
> Das Problem könnte ggf hier liegen:

Wenn man sich die Summe anschaut, wird sie auch als 255 interpretiert. 
Nicht nur im printf.

Die Funktion ist kein Geheimnis.
/** Differenz des Drehgeber-Standes zwischen aufeinanderfolgenden Aufrufen der
 * Funktion auslesen.
 *
 * @return: Differenz zu vorherigem Aufruf */
int_fast8_t mocksdl_encoder_get_diff(void)
{
  static uint_fast8_t enc_last;
  uint_fast8_t enc_this;
  int_fast8_t enc_diff;

  static_assert(sizeof(uint_fast8_t)==sizeof(int_fast8_t), \
      "variable must be of same size");

  enc_this = mocksdl_encoder_get();
  enc_diff = (int_fast8_t) (enc_this-enc_last);
  enc_last = enc_this;

  return enc_diff;
} 
Die Funktion mocksdl_encoder_get() wiederum kann bauartbedingt nur ein 
uint_fast8_t zurückgeben, das zwar überläuft, aber pro Aufruf maximal um 
einen Wert unterscheidet. Reproduzieren lässt sich das im Original-Build 
schon mit diesem Stub:
uint_fast8_t mocksdl_encoder_get(void)
{
     static uint_fast8_t a = 0;
     return a--;
}

Bei einem extra-Build der angehängte Datei passiert das nicht. Auch 
nicht mit den gleichen Compiler-Optionen:

gcc.exe -O1 -g2 -DMOCKUP_SDL=1 -DDOUBLE_MATH_ENABLED=1 
-DGPIO_EMULATION=1 -DUNITTEST_ENABLED=1 -c src_application\debugme.c -o 
obj\mockup\src_application\debugme.o -MMD 
-IC:\Toolchain\TDM-GCC-64\bin\..\include 
-IC:\Toolchain\TDM-GCC-64\bin\..\arm-none-eabi\include 
-IC:\Toolchain\TDM-GCC-64\bin\..\arm-none-eabi


Der Fehler liegt also irgendwo außerhalb des hier sichtbaren Codes. Aber 
wo? Wo kann ich suchen?

: Bearbeitet durch User
Beitrag #5933979 wurde vom Autor gelöscht.
Autor: Frank M. (ukw) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
$ cc -Wall mi.c
mi.c: In function ‘egml_getEncoderIncrement’:
mi.c:41:19: warning: format ‘%i’ expects argument of type ‘int’, but argument 2 has type ‘long unsigned int’ [-Wformat=]
     printf("size=%i,%i ", sizeof(*enc), sizeof(enc));
                   ^
mi.c:41:22: warning: format ‘%i’ expects argument of type ‘int’, but argument 3 has type ‘long unsigned int’ [-Wformat=]
     printf("size=%i,%i ", sizeof(*enc), sizeof(enc));
                      ^
mi.c: In function ‘main’:
mi.c:60:33: warning: passing argument 1 of ‘egml_getEncoderIncrement’ discards ‘volatile’ qualifier from pointer target type [-Wdiscarded-qualifiers]
        egml_getEncoderIncrement(&(State.enc));
                                 ^
mi.c:36:6: note: expected ‘int *’ but argument is of type ‘volatile int *’
 void egml_getEncoderIncrement(int *enc)
      ^~~~~~~~~~~~~~~~~~~~~~~~

Auf jeden Fall gibst Du die unsigned longs mit dem falschen Format %i 
aus. Hier solltest Du %lu verwenden.
    printf("size=%lu,%lu ", sizeof(*enc), sizeof(enc));

Bei mir sieht die Ausgabe folgendermaßen aus:
size=4,8 dx=0, enc0 = 0 enc1 = 0
size=4,8 dx=-1, enc0 = -1 enc1 = -1
size=4,8 dx=-1, enc0 = -2 enc1 = -2
size=4,8 dx=-1, enc0 = -3 enc1 = -3
size=4,8 dx=-1, enc0 = -4 enc1 = -4
size=4,8 dx=-1, enc0 = -5 enc1 = -5
size=4,8 dx=-1, enc0 = -6 enc1 = -6
size=4,8 dx=-1, enc0 = -7 enc1 = -7
size=4,8 dx=-1, enc0 = -8 enc1 = -8
size=4,8 dx=-1, enc0 = -9 enc1 = -9
size=4,8 dx=-1, enc0 = -10 enc1 = -10
size=4,8 dx=-1, enc0 = -11 enc1 = -11
size=4,8 dx=-1, enc0 = -12 enc1 = -12
size=4,8 dx=-1, enc0 = -13 enc1 = -13
size=4,8 dx=-1, enc0 = -14 enc1 = -14
size=4,8 dx=-1, enc0 = -15 enc1 = -15
size=4,8 dx=-1, enc0 = -16 enc1 = -16
size=4,8 dx=-1, enc0 = -17 enc1 = -17
size=4,8 dx=-1, enc0 = -18 enc1 = -18
size=4,8 dx=-1, enc0 = -19 enc1 = -19

Achja:
$ cc -v
gcc version 6.3.0 20170516 (Debian 6.3.0-18+deb9u1)

Autor: Jim M. (turboj)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Walter T. schrieb:
> Trotzdem erfolgt die
> Zuweisung so, als wäre der Rückgabewert unsigned char.
 printf("Sizeof int_fast8_t ist: %d\n", sizeof(int_fast8_t));

Bei mir kommt da 4 raus, weil fast8 als normales int definiert wird.

Der Überlauf in das Sign Bit bei vorzeichenbehaftetem integern ist in C 
undefiniert, da kann einem der Compiler in die Suppe spucken. Sieht man 
leider nur im Disassembly.

Autor: Rolf M. (rmagnus)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Walter T. schrieb:
> -IC:\Toolchain\TDM-GCC-64\bin\..\arm-none-eabi\include

Ah, deine Zielplattform ist gar nicht mingw. Das ist nur die, wo der 
Compiler drauf läuft. Hättest ja auch sagen können, dass du einen 
Crosscompiler verwendest, vor allem, wenn du in "PC-Programmierung" 
postest, wo man eigentlich vom PC als Zielplattform ausgeht. Wie groß 
ist int_least8_t denn dort?

Frank M. schrieb:
> Auf jeden Fall gibst Du die unsigned longs mit dem falschen Format %i
> aus. Hier solltest Du %lu verwenden.    printf("size=%lu,%lu ",
> sizeof(*enc), sizeof(enc));

Nein. Wie ich schon schrieb, ist %zu (hatte nur %z geschrieben) das 
richtige. sizeof gibt einen size_t zurück, der ein Typedef für unsigned 
long sein kann, aber nicht muss. z ist der Größen-Modifier für 
size_t. Damit ist es immer richtig.

: Bearbeitet durch User
Autor: Walter T. (nicolas)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Rolf M. schrieb:
> Ah, deine Zielplattform ist gar nicht mingw. Das ist nur die, wo der
> Compiler drauf läuft.

Die Zielplattform ist MinGW, allerdings findet der Build unter EmBitz 
statt. Dem kann man nicht abgewöhnen, den entsprechenden Pfad auch 
einzubeziehen.

Autor: Walter T. (nicolas)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ich habe es (beinahe) gefunden.
void egml_getEncoderIncrement(int *enc)
{
    int_fast8_t dx0 = mocksdl_encoder_get_diff();
    int dx =  dx0;
    int dx1 = mocksdl_encoder_get_diff();

    int a = 10;
    printf("dx0=%i, dx1=%i, x0=%i, x1=%i \n", dx0, dx1, a + dx0, a+dx1);
}

liefert die Konsolenausgabe:
dx0=-1, dx1=255, x0=9, x1=265
Was wäre, wenn die Deklaration fehlte, und der Compiler einfach auf int 
zurückfiele?
a) es müßte eine Warnung ausgegeben werden
b) es würde das obengenannte Phänomen erklären.

Also die Funktionsdeklaration direkt in der Datei (und nicht nur über 
den Header) angegeben - Schwupps, kommt das erwartete heraus.

Also hat dieser Hurensohn von Compiler einfach die Warnung in der 
Richtung "implizit declaration" oder so verschluckt. So macht das keinen 
Spaß. Jetzt muß ich dafür die Ursache suchen.

: Bearbeitet durch User
Autor: Rolf M. (rmagnus)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Walter T. schrieb:
> Die Zielplattform ist MinGW, allerdings findet der Build unter EmBitz
> statt. Dem kann man nicht abgewöhnen, den entsprechenden Pfad auch
> einzubeziehen.

Ah, ok. Dann musst du aber sicherstellen, dass da nicht irgendwelche 
Header für die falsche Zielplattform eingebunden werden. Das könnte ja 
auch alles komplett durcheinander bringen.

Walter T. schrieb:
> Was wäre, wenn die Deklaration fehlte, und der Compiler einfach auf int
> zurückfiele?
> a) es müßte eine Warnung ausgegeben werden

Kommt drauf an. In C99 oder neuer müsste eine ausgegeben werden. Bei 
einer früheren Version wird eine Warnung ausgegeben, wenn die 
entsprechende Einstellung (-Wimplicit-function-declaration, Teil von 
-Wall) an ist.

> b) es würde das obengenannte Phänomen erklären.

Möglicherweise. Mir würde jedenfalls kein anderer sinnvoller Grund 
einfallen, warum für dx0 und dx1 unterschiedliche Werte angezeigt 
werden. Was steht denn in dx drin?

> Also die Funktionsdeklaration direkt in der Datei (und nicht nur über
> den Header) angegeben - Schwupps, kommt das erwartete heraus.

Ist die Deklaration auch exakt gleich? Wird wirklich der Header 
verwendet, von dem du denkst, dass er verwendet wird?

Autor: Walter T. (nicolas)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
Rolf M. schrieb:
> Was steht denn in dx drin?

In dx steht in jedem Fall -1 drin.

Rolf M. schrieb:
> wenn die
> entsprechende Einstellung (-Wimplicit-function-declaration, Teil von
> -Wall) an ist.

Okay, ich habe es gefunden. In EmBitz ist es nicht so einfach, die 
Compiler-Aufruf-Zeile zu finden. -Wall und -Wpedantic wurden in der 
Umgebung auf meinem einen Rechner nicht richtig durchgereicht. Dafür 
finde ich auf dem anderen gerade nicht die Stelle, wo man einstellt, daß 
die komplette Build-Zeile im Log steht.

Edit: Für alle, die nach mir suchen (obwohl EmBitz so gut wie tot ist): 
Siehe Bild.

: Bearbeitet durch User
Autor: Oliver S. (oliverso)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Wie schon geschrieben wurde, ein aktueller gcc, der c99 als default hat, 
warnt auch ohne -Wall.

Oliver

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.

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