Forum: PC-Programmierung C: Unterwartetes Vorzeichen


von Walter T. (nicolas)


Angehängte Dateien:

Lesenswert?

Guten Morgen,

ich habe gerade ein Brett vorm Kopf. Ich habe eine primitive Funktion, 
die aus Debug-Gründen so aussieht:
1
#include <stdint.h>
2
#include <stdio.h>
3
4
int_fast8_t mocksdl_encoder_get_diff(void);
5
6
void egml_getEncoderIncrement(int *enc)
7
{
8
    int dx =  mocksdl_encoder_get_diff();
9
10
    *enc += dx;
11
    printf("size=%i,%i ", sizeof(*enc), sizeof(enc));
12
    printf("dx=%i, enc0 = %i", dx, *enc);
13
    //sclamp(enc, 0, egml_action_end-1);
14
    printf(" enc1 = %i\n", *enc);
15
}
16
17
18
// Sinngemaess
19
int main(void)
20
{
21
    volatile struct
22
    {
23
        int enc;
24
    }
25
    State;
26
    State.enc = 0;
27
    
28
    while(1)
29
    {
30
       egml_getEncoderIncrement(&(State.enc));
31
32
    }
33
    return 0;
34
}

Die Konsolenausgabe sieht so aus:
1
...
2
size=4,8 dx=0, enc0 = 18615 enc1 = 18615
3
size=4,8 dx=0, enc0 = 18615 enc1 = 18615
4
size=4,8 dx=0, enc0 = 18615 enc1 = 18615
5
size=4,8 dx=0, enc0 = 18615 enc1 = 18615
6
size=4,8 dx=255, enc0 = 18870 enc1 = 18870
7
size=4,8 dx=0, enc0 = 18870 enc1 = 18870
8
...
(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
von Irgend W. (Firma: egal) (irgendwer)


Lesenswert?

Walter T. schrieb:
> mocksdl_encoder_get_diff()

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

von Rolf M. (rmagnus)


Lesenswert?

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

Der liegt darin:
1
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.

von Walter T. (nicolas)


Angehängte Dateien:

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.
1
/** Differenz des Drehgeber-Standes zwischen aufeinanderfolgenden Aufrufen der
2
 * Funktion auslesen.
3
 *
4
 * @return: Differenz zu vorherigem Aufruf */
5
int_fast8_t mocksdl_encoder_get_diff(void)
6
{
7
  static uint_fast8_t enc_last;
8
  uint_fast8_t enc_this;
9
  int_fast8_t enc_diff;
10
11
  static_assert(sizeof(uint_fast8_t)==sizeof(int_fast8_t), \
12
      "variable must be of same size");
13
14
  enc_this = mocksdl_encoder_get();
15
  enc_diff = (int_fast8_t) (enc_this-enc_last);
16
  enc_last = enc_this;
17
18
  return enc_diff;
19
}
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:
1
uint_fast8_t mocksdl_encoder_get(void)
2
{
3
     static uint_fast8_t a = 0;
4
     return a--;
5
}

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.
von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

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

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

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

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

von Jim M. (turboj)


Lesenswert?

Walter T. schrieb:
> Trotzdem erfolgt die
> Zuweisung so, als wäre der Rückgabewert unsigned char.
1
 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.

von Rolf M. (rmagnus)


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
von Walter T. (nicolas)


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.

von Walter T. (nicolas)


Lesenswert?

Ich habe es (beinahe) gefunden.
1
void egml_getEncoderIncrement(int *enc)
2
{
3
    int_fast8_t dx0 = mocksdl_encoder_get_diff();
4
    int dx =  dx0;
5
    int dx1 = mocksdl_encoder_get_diff();
6
7
    int a = 10;
8
    printf("dx0=%i, dx1=%i, x0=%i, x1=%i \n", dx0, dx1, a + dx0, a+dx1);
9
}

liefert die Konsolenausgabe:
1
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
von Rolf M. (rmagnus)


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?

von Walter T. (nicolas)


Angehängte Dateien:

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
von Oliver S. (oliverso)


Lesenswert?

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

Oliver

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.