Hallo zusammen,
heute bin ich über ein mir neues Verhalten des gnu-c Compilers
gestolpert.
Es geht bei dem betreffenden Code um eine Überprüfung auf eine
Feldgrenzenüberschreitung. Der Compiler castet unerwartet.
1
#define RGB_LED_COUNT 28
[...]
1
do{
2
staticuint8_tvalue;
3
4
++value;
5
if(value>=RGB_LED_COUNT)value=0;
6
7
// Diese Fallbedingung ist immer false
8
if((value-6)>RGB_LED_COUNT){
9
// Dieser Code wird nie ausgeführt
10
printf("Feldgrenze ueberschritten Teil 1");
11
}
12
13
// Diese Bedingung wird erfüllt
14
if((uint8_t)(value-6)>RGB_LED_COUNT){
15
// Dieser Code wird ausgeführt
16
printf("Feldgrenze ueberschritten Teil 2");
17
}
18
19
}while(1);
[...]
Im Maschinencode zeigt sich beim ersten Fall, das auf ein int verglichen
wird; eine Umrechnung auf das zu erwartende 8Bit Datum erfolgt nicht.
Getestet bei int=16Bit (ATXmega) sowie int=32Bit Architektur(ARM M4).
Wo sagt der Ansi C Standard das das Ergebnis der Berechnung auf ein int
erweitert wird?
Grüße
Christian S. schrieb:> Ansi C Standard
Im ANSI-C Standard steht nichts von uint8_t. Hast du es selbst definiert
oder ist es vielleicht gar kein ANSI-C (C89) sondern C99?
Der Code ist auch reichlich undurchsichtig, würde ich so nicht
schreiben. Der baut ja darauf, dass 0 - 6 eine große (uint8) Zahl wird,
aber um das zu erfassen, muss man beim Draufgucken eine Weile
nachdenken.
Das, was der Compiler draus macht (wenn der Wert vorher auf 0 gesetzt
worden ist, ist er um 6 verringert natürlich immer noch kleiner als das
Maximum) wäre das, was ich auch als völlig logisch empfinden würde,
wobei man sich natürlich dann fragt, wofür der sinnlose Test überhaupt
da ist.
Wenn das auch in 3 Jahren noch jemand verstehen soll, dann schreib das
um (was auch immer dein negativer Überlauf erreichen soll da).
Programmierer schrieb:> Im ANSI-C Standard steht nichts von uint8_t. Hast du es selbst definiert> oder ist es vielleicht gar kein ANSI-C (C89) sondern C99?
Irgendwer hat es immer selbst definiert. Es ist in Wirklichkeit nix
anderes als ein unsigned char. Und den gibt es seit ewig. Naja, fast
ewig.
Aber manchmal frag ich mich auch, welche Art von Genialität hinter
solchen Ausdrücken wie ((uint8_t)(value-6) > RGB_LED_COUNT) stehen soll.
W.S.
Jörg W. schrieb:> [...]dass 0 - 6 eine große (uint8) Zahl wird [...]
Korrekt, das ist die gewünschte Funktion. Ich habe mich gewundert, warum
1
if((value-6)>RGB_LED_COUNT){...
immer false ist und konnte mir die Übersetzung nicht erklären. Die
Lösung kommt von 'Experte (Gast)', da ich das #define für diesen
expliziten Fall falsch belegt habe.
Der Code soll die Problemstellung exemplarisch zeigen. Das Ziel ist
einen Ringpuffer von der aktuellen Position in beide Richtungen variabel
bearbeiten zu können. In diesem Beispiel trifft das für die letzten 6
Elemente zu.
Christian S. schrieb:> Das Ziel ist> einen Ringpuffer von der aktuellen Position in beide Richtungen variabel> bearbeiten zu können. In diesem Beispiel trifft das für die letzten 6> Elemente zu
Ist hier zwar off topic, aber wenn es Dir nur um Ringpuffer UND linearen
Speicher geht, kann ich Dir meine Buffer empfehlen
Beitrag "Advance Serial Buffer"
Wir benutzen die hier immer dann, wenn komplett geprüfte Telegramme z.B.
aus dem Uarttreiber purzeln sollen, die Telegramm-Größe stark schwankt
und man trotzdem eine Mindestzeit puffern können will. Z.B. 100ms bei
100kBaud und Telegrammlängen von 10...1000 Byte.
Christian S. schrieb:> Das Ziel ist einen Ringpuffer von der aktuellen Position in beide> Richtungen variabel bearbeiten zu können
Würde ich trotzdem versuchen, lesbarer zu schreiben. Dass das alles nur
von einem u im #define abhängt, ist bösartig versteckt.
Ja, lesbarer soll es werden. Und typsicher sollte es auch sein.
Ich werde hier im Forum gerne nach schönen Lösungsansätzen für
Ringpuffer suchen, kann ja nicht so schwer sein. Oben kam ja schon ein
toller Ansatz.
Die Aufgabe kam über einen RGB-Led Ring, bei dem eine / einige Farben
gesetzt werden, danach sollen die Farben verwischt werden (blurring),
das Ganze dann animiert. Für die Abarbeitung des Codes gibt es eine
sportliche Deadline, daher muss der Code effizient sein. Der oben
angeführte Vergleich kann über Flags laufen und ist auf allen
Architekturen einsetzbar. Über die Lesbarkeit kann man stolpern, über
die Implementierung auch. Dennoch: Es funktioniert im ersten Ansatz und
ist vorzeigbar. Wenn es gefallen findet, wird es verbessert und
weiterentwickelt, wenn nicht geht's in die Tonne.
W.S. schrieb:> Irgendwer hat es immer selbst definiert. Es ist in Wirklichkeit nix> anderes als ein unsigned char. Und den gibt es seit ewig. Naja, fast> ewig.
Praktisch wohl meistens ja, allerdings ist das nicht im C-Standard
garantiert, afaik.
Eins N00B schrieb:> Praktisch wohl meistens ja
Wenn es denn überhaupt definierbar ist. Das ist das, was W.S.'
Gebetsmühle sich einfach noch nicht verinnerlicht hat: auf einer
Architektur, die keine 8-Bit-Objekte kennt, gibt es zwar unsigned char,
aber eben kein uint8_t. (unsigned char ist dort dann schlicht größer
als nur 8 Bit, obwohl per definitionem in C immer noch gilt:
sizeof(char) = 1.)
Auf solchen Architekturen gibt es natürlich dann trotzdem noch
uint_least8_t und uint_fast8_t. Aber die sind bei W.S. eh noch nicht
vorbei gekommen.
eine weitere interessante Betrachtung:
was ist:
(uint8_t)(value-6)
wenn z.B. value den Wert 3 hat?
klar: hexadezimal 0xFD.
und damit immer größer RGB_LED_COUNT.
Damit wird auch die 2. Bedingung erfüllt.
Spannend...
Um so einen Bereichswechsel zu vermeiden, sollte man die Ungleichung
umschreiben:
1
if(value>RGB_LED_COUNT+6)...
Auf der rechten Seite ist die Addition kein Problem, da in Integerbreite
weitergerechnet wird. Es kommt somit zu keinem Überlauf, weder links
noch rechts.
Die Abfrage eines Null-Unterlaufs bei einer rückwärts laufenden Schleife
wird hingegen gern mit einem Cast von unsigned => signed gesichert:
dfIas schrieb:> if (value - 6 > RGB_LED_COUNT) ...> Um so einen Bereichswechsel zu vermeiden, sollte man die Ungleichung> umschreiben:if (value > RGB_LED_COUNT + 6) ...
Kann man machen, das hat nur nichts mit dem Programm des TO zu tun.
Christian S. schrieb:> if((uint8_t)(value-6) > RGB_LED_COUNT){
ist nichts anderes als eine obfuscated Version von
Christian S. schrieb:> Der Compiler castet unerwartet.
Die 6 ohne irgendwas ist mindestens 16Bit int, d.h. value (8Bit
unsigned) wird zum größeren Format (int) gecastet und dann wird
gerechnet.
Eine schöne Fallgrube ist auch:
Sind beide Operanden 16Bit, aber einer unsigned, dann ist das größere
Format unsigned, d.h. die Rechnung erfolgt unsigned.
Peter D. schrieb:> Eine schöne Fallgrube ist auch
eine Programmiersprache zu nutzen, die man nicht kann.
Ja, jeder, der C benutzt, stolpert irgendwann mal in seinem Leben über
die Integer Promotion. Danach weiß man, daß es das gibt, und wie man
damit umgeht.
Oliver