Forum: Compiler & IDEs Seltsamer cast in Ansi C 'if.'


von Christian S. (christian_s435)


Lesenswert?

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
    static uint8_t value;
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

von Experte (Gast)


Lesenswert?


von Dirk B. (dirkb2)


Lesenswert?

Werden nicht alle Berechnungen mindestens in int durchgeführt?

von Christian S. (christian_s435)


Lesenswert?

Manchmal wenn Du gängige Praxis wegen eines einzigen gesparten 
Tastenanschlages sparst.....
1
#define RGB_LED_COUNT 28U

und ich hatte noch mit (uint8_t)RGB_LED_COUNT getestet.. das war auch 
Essig.

Danke (Gast)Experte ;)

: Bearbeitet durch User
von Programmierer (Gast)


Lesenswert?

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?

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


Lesenswert?

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

von W.S. (Gast)


Lesenswert?

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.

von Christian S. (christian_s435)


Lesenswert?

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.

von A. S. (Gast)


Lesenswert?

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.

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


Lesenswert?

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.

von Christian S. (christian_s435)


Lesenswert?

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.

von Eins N00B (Gast)


Lesenswert?

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.

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


Lesenswert?

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.

von alter Bastler (Gast)


Lesenswert?

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

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


Lesenswert?

alter Bastler schrieb:
> eine weitere interessante Betrachtung

Das ist ja das, was er bezweckt - und was ich als "obfuscated" ansehe.

von MR9000 (Gast)


Lesenswert?

Warum nicht einfach if(value < 6) ?

von dfIas (Gast)


Lesenswert?

1
if (value - 6 > RGB_LED_COUNT) ...
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:
1
for (uint8_t ui8 = 234; (int8_t) ui8 >= 0; ui8--) ...
Ohne Cast wäre das eine Endlosschleife.

von Oliver S. (oliverso)


Lesenswert?

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
1
if (value < 6)

Oliver

von Peter D. (peda)


Lesenswert?

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.
1
void foo(int x)
2
{
3
  if ((x - 30U) < 0)
4
    PORTB = 1;
5
}
Der Code wird komplett wegoptimiert.

von Oliver S. (oliverso)


Lesenswert?

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

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.