Forum: Compiler & IDEs [Erledigt] Bug im Compiler? uint8_t Variable um 128 verschoben.


von Bastel K. (bastel_k)


Angehängte Dateien:

Lesenswert?

Hi Leute,

ich bin da auf etwas merkwürdiges gestoßen. Und zwar habe ich mir eine 
kleine MovingAverage-Klasse geschrieben um eben recht einfach einen 
gleitenden Durchschnitt berechnen zu können. Aber irgendwie ist der 
Durchschnittswert, den mir meine Klasse zurück gibt, immer um 128 
größer. Ich kompiliere das ganze mit avr-gcc V4.7.0 auf meiner 
Linux-Maschine und flashe dann mit avrdude einen Atmega8.
Nutze ich die selbe Klasse in einem normalen gcc-Projekt, das auf meinem 
Linux läuft und simuliere die Eingangsdaten, dann stimmt alles. Es kommt 
also der richtige Durchschnittswert raus.

Leider bin ich noch nicht so bewandert mit der ganzen 
avr-gcc-Kompiliererei, dass ich mir da den ASM-Output geben lassen kann 
und den auch noch verstehe, um zu sehen, was möglicherweise falsch 
laufen könnte. Jedenfalls ist es sehr ärgerlich.

Im Anhang befindet sich der C++-Code, der genau das simuliert, was auf 
dem Atmega8 passiert. Dieser bekommt Daten aus dem ADC von einem 
Joystick, der in Mittelstellung ist, aber ein bisschen rauscht. Die 
direkten Daten aus dem ADC sind korrekt und liegen immer so im Bereich 
124-130. Nach dem Nutzen der MovingAverage-Klasse sollten sie immer noch 
in diesem Bereich liegen. Allerdings tun sie das nicht, sondern 
schwanken dann um 0 herum, also konkret um 253-255 und 0-3, wegen des 
Überlaufs. Der avr-gcc scheint da also irgendwas falsch zu machen. Der 
gcc macht es richtig.

Grüße,
Nicolas

von n.n. (Gast)


Lesenswert?

hallo,

zwei dinge..

template<typename T>
class MovingAverage {
...
}

templates und überhaupt klassen für avr? heikel..
da dann den asm-code durchzusteigen verlangt übung.

dann:
printf("%d: add(%d) -> %d\n", i, value, result);
%d ist für signed int,
%u wäre unsigned decimal integer

ist das beides so gewollt?

von (prx) A. K. (prx)


Lesenswert?

1
#if ((T) - 1 < 0)
2
    int32_t sum;
3
#else
4
    uint32_t sum;
5
#endif

Template-Parameter mit dem Präprozessor zu testen ist nicht drin. Nicht 
einmal wenn korrekt formuliert.

von (prx) A. K. (prx)


Lesenswert?

n.n. schrieb:
> templates und überhaupt klassen für avr? heikel..

Völlig ok. Solange das nicht 100x expandiert wird kommt hier auch nix 
anderes bei raus als in C.

In dem Fall hier hätte allerdings auch die Länge noch als 
Template-Parameter gepasst, damit den malloc() erspart und evtl. die 
eine oder andere Division erleichtert.

von (prx) A. K. (prx)


Lesenswert?

Besser wär freilich, wenn keine nicht initialisierten values subtrahiert 
würden. Eine Funktion clear() zu definieren allein reicht nicht.

von (prx) A. K. (prx)


Lesenswert?

Der Algorithmus selbst ist auch nicht so ganz der Bringer. Du dividierst 
bereits den ersten Wert durch maxvalues, so dann sich average langsam 
von unten kommend an den Mittelwert herantastet.

von (prx) A. K. (prx)


Lesenswert?

Apropos "Bug im Compiler": Wenn das Ergebnis in Linux stimmt, auf dem 
AVR aber nicht, dann hat trotzdem selten der Compiler Schuld. Es gibt 
beispielsweise gewisse Unterschiede bei der Initialisierung des 
Speichers. In Linux kriegst du beim ersten malloc() eines Speichers 
Nullen geliefert, weil der Systemaufruf dazu den schon selbst 
initialisiert. Der AVR tut dir den Gefallen nicht, der liefert aus was 
zufällig da drinsteht.

von Michael (Gast)


Lesenswert?

Fassen wir das alles mal zusammen: Der TO hat printf nicht richtig 
verwendet und ist darum mit dem Vorzeichenbit (bit 7 = 128) 
durcheinandergekommen.

von (prx) A. K. (prx)


Lesenswert?

Das printf ist völlig ok. Alle 3 Werte kommen als "int" an.

von Bastel K. (bastel_k)


Lesenswert?

Danke für die Antworten. Ich nehme dann mal Stellung:

n.n. schrieb:
> printf("%d: add(%d) -> %d\n", i, value, result);
> %d ist für signed int,
> %u wäre unsigned decimal integer
>
> ist das beides so gewollt?
Im Atmega8-Code habe ich ja eine ganz andere main-Methode. Das hier mag 
nicht vollkommen korrekt sein, habe ich aber auch nur mal schnell zu 
Testzwecken mit dem gcc dahin gehackt.

A. K. schrieb:
> Template-Parameter mit dem Präprozessor zu testen ist nicht drin. Nicht
> einmal wenn korrekt formuliert.
Guter. Das habe ich gerade auch nochmal nachgelesen. Aber auch wenn das 
in dem Fall jetzt falsch ist, hatte ich vorher fest uint32_t benutzt 
ohne Direktive, und da trat das besagte Problem auch schon auf.

A. K. schrieb:
> In dem Fall hier hätte allerdings auch die Länge noch als
> Template-Parameter gepasst, damit den malloc() erspart und evtl. die
> eine oder andere Division erleichtert.
Die Klasse ist noch nicht ganz fertig. Später soll auch 'maxValues' 
variabel werden, deshalb scheidet es als Template-Parameter hier aus. 
Der gesamte Code ist ja noch in der Entwicklungsphase. Möglicherweise 
wird es auch später doch irgendwann zum Template-Parameter. Mal 
abwarten...

A. K. schrieb:
> Besser wär freilich, wenn keine nicht initialisierten values subtrahiert
> würden. Eine Funktion clear() zu definieren allein reicht nicht.
UND
A. K. schrieb:
> In Linux kriegst du beim ersten malloc() eines Speichers
> Nullen geliefert, weil der Systemaufruf dazu den schon selbst
> initialisiert. Der AVR tut dir den Gefallen nicht, der liefert aus was
> zufällig da drinsteht.
schäm
Daran hätte ich selbst denken können. Das muss ich mal testen. Womöglich 
ist das schon der Fehler.
Ändere ich nämlich den Konstruktor in das hier ab:
1
MovingAverage(size_t maxValues, T average = 0) {
2
  values = (T*) malloc(maxValues * sizeof(T));
3
  for (size_t i = 0; i < maxValues; i++) {
4
    values[i] = rand() % 256;
5
  }
6
7
  this->maxValues = maxValues;
8
  index = 0;
9
  sum = 0;
10
  this->average = average;
11
}
Dann sieht es mit dem gcc ganz ähnlich aus. Aber bis ich das wieder mit 
dem Atmega8 testen kann, muss ich noch bis Mittwoch warten.

A. K. schrieb:
> Der Algorithmus selbst ist auch nicht so ganz der Bringer. Du dividierst
> bereits den ersten Wert durch maxvalues, so dann sich average langsam
> von unten kommend an den Mittelwert herantastet.
Das ist mir bewusst. In dem ursprünglichen Atmega8-Code läuft das ganze 
aber sowieso unendlich oft in einer while-Schleife, womit sich bereits 
nach 1-2 ms der Average-Wert angepasst hat, weil dann alle Felder einmal 
beschrieben wurden. Deshalb stört mich das nicht sonderlich, zumindest 
bis jetzt. Wie gesagt ist es noch in der Entwicklung.

Na gut, dann pack ich mal einen clear()-Aufruf noch in den Konstruktor, 
dann sollte das Problem gelöst sein...

Vielen Dank an alle!

von Bastel K. (bastel_k)


Lesenswert?

Kann man hier irgendwie als Threadstarter eine Markierung anbringen, die 
besagt, dass das Problem gelöst ist? Den Titel kann ich ja auch nicht 
mehr editieren.

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


Lesenswert?

A. K. schrieb:
> In Linux kriegst du beim ersten malloc() eines Speichers
> Nullen geliefert, weil der Systemaufruf dazu den schon selbst
> initialisiert.

Dass er das tut, liegt vor allem an der Mehrnutzerumgebung: es muss
sichergestellt sein, dass kein Prozess Daten fremder Nutzer zu sehen
bekommt.  Das geht nun mal am besten, indem man allen anonymen Speicher,
den ein Prozess bekommt (Stack, Heap) ausnullt.  Man kann natürlich
genauso gut was anderes reinschreiben; wenn man beispielsweise bei
FreeBSD in den MALLOC_OPTIONS den Buchstaben "J" einträgt, dann wird
aller Speicher auf 0xa5 initialisiert.  Da kann man sich dann schön
ansehen, welche Programme alle abstürzen, weil sie vergessen haben,
sich um die Initialisierung ihres Speichers zu kümmern. ;-)

Bastel K. schrieb:
> Kann man hier irgendwie als Threadstarter eine Markierung anbringen, die
> besagt, dass das Problem gelöst ist?

Leider nicht (hätte ich auch gern als Feature), aber ich kann das für
dich tun.

von (prx) A. K. (prx)


Lesenswert?

Jörg Wunsch schrieb:
> Dass er das tut, liegt vor allem an der Mehrnutzerumgebung: es muss
> sichergestellt sein, dass kein Prozess Daten fremder Nutzer zu sehen
> bekommt.

Das ist in Einnutzerumgebungen genauso kritisch.

> Da kann man sich dann schön
> ansehen, welche Programme alle abstürzen, weil sie vergessen haben,
> sich um die Initialisierung ihres Speichers zu kümmern. ;-)

Nette Idee. ;-)

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.