Forum: Compiler & IDEs GNU ARM GCC ignoriert "signed" Variablen


von Peter R. (dk7ih)


Lesenswert?

Guten Abend zusammen,

ich habe vor Jahren ein Projekt entwickelt um den BMP180-Druck- und 
Temperatursensor in eine Telemetrieeinheit für einen Stratosphärenballon 
zu integrieren. Jetzt habe ich den Code auf einen STM32F4 portiert und 
stehe vor einem kleinen Problem:

Ich lese die Kalibrierdaten aus dem EEPROM mit I2C aus, unter denen sich 
auch einige negative Werte befinden.

Z. B. hat die Variable "ac2" aus dem Parametersatz den Wert -1056. 
Diesen habe ich ermittelt, indem ich den Sensor an den AVR angeschlossen 
habe, um mir die Werte aus dem EEPROM anzeigen zu lassen. Auf dem AVR 
ist die Ausgabe korrekt ("-1056"), auf dem STM32 nicht. Beide Compiler 
(jeweils aus der GCC) laufen auf dem gleichen Rechner unter Linux Mint 
18.

Das Auslesen der Daten aus dem Sensor beim STM32 funktioniert 
fehlerfrei. Wenn ich die Software auf diesem Kontroller laufen lasse, 
erhalte ich aus den beiden Registern die zur Variablen "ac2" gehörenden 
Werte 0xAC=251 und 0xAD=224. Nach der entsprechenden Addition von MSB 
und LSB ergibt sich ein Wert von 64480 also genau das, was dem Wert 
-1056 als "signed" Variable entspricht.

Hier der relevante Codeauszug:

int ac2;
...
int msb, lsb;

msb = i2c_read(0xAC);
lsb = i2c_read(0xAD);
ac2 = (msb << 8) + lsb;

Die Funktion zum Lesen der Daten aus dem Sensor ist ebenfalls als 
"signed" definiert:

int i2c_read(uint8_t regaddr)
{....}

Wie bekomme ich den Compiler dazu, das 2er-Komplement korrekt zu 
behandeln? Eine explizite Typumwandlung hat keinen Erfolg gebracht.

Vielen Dank für's Lesen!

Peter

von 2⁵ (Gast)


Lesenswert?

int ist auf dem avr 16 bit groß und auf dem arm 32 bit.

von 2⁵ (Gast)


Lesenswert?

Verwende statt int int16_t und am Anfang des c-Files "#include 
<stdint.h>". Dann sollte der Quelltext auf AVR und ARM gleichermaßen 
laufen. Zumindest das kurze Code-Beispiel.

von Nop (Gast)


Lesenswert?

2⁵ schrieb:
> int ist auf dem avr 16 bit groß und auf dem arm 32 bit.

Das kann man mit int16_t in den Griff bekommen. Das Handling der 
Endianess sieht ja schon gut aus.

von Peter R. (dk7ih)


Lesenswert?

Vielen Dank Euch, das Problem ist gelöst! :-)

Peter

von Al Fine (Gast)


Lesenswert?

Peter R. schrieb:
> int ac2;
> ...
> int msb, lsb;
>
> msb = i2c_read(0xAC);
> lsb = i2c_read(0xAD);
> ac2 = (msb << 8) + lsb;

Das ist ein potentieller Overflow.

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


Lesenswert?

Al Fine schrieb:

>> int msb, lsb;
>>
>> msb = i2c_read(0xAC);
>> lsb = i2c_read(0xAD);
>> ac2 = (msb << 8) + lsb;
>
> Das ist ein potentieller Overflow.

Insbesondere ist es undefined behaviour, wenn "msb" eine negative Zahl 
ist.

"If E1 has a signed type and nonnegative value, and E1 × 2 E2 is
representable in the result type, then that is the resulting value; 
otherwise, the behavior is undefined."

von Klaus W. (mfgkw)


Lesenswert?

Wenn lsb negativ ist, geht es auch schief (wird dann bei der Addition 
mit Einsen auf volle Länge aufgezogen).

Besser wäre es m.E etwa so:
1
uint16_t   msb, lsb;
2
msb = 0xFF & i2c_read(0xAC);
3
lsb = 0xFF & i2c_read(0xAD);
4
ac2 = (int16_t)( (msb << 8) | lsb );

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

In der Annahme, dass i2c_read() so deklariert ist, dass es einen uint8_t 
zurück gibt, braucht es das "0xFF &" nicht.

Aber ja, die Zwischenwerte vorzeichenlos zu speichern, ist auf jeden 
Fall sinnvoll.

von Klaus W. (mfgkw)


Lesenswert?

Deshalb:

Peter R. schrieb:
> Die Funktion zum Lesen der Daten aus dem Sensor ist ebenfalls als
> "signed" definiert:
>
> int i2c_read(uint8_t regaddr)
> {....}

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


Lesenswert?

Klaus W. schrieb:
> Deshalb:

OK, aber da würde ich lieber das ändern, als das irgendwie dann durch 
verUNDen mit 0xFF zu kaschieren.

Die Funktion liest ja erst einmal Bytes, und das bezeichnet man 
sinnvollerweise mit uint8_t. Zusammenbau größerer Objekte oder 
Uminterpretieren in vorzeichenbehaftet würde ich erst danach machen.

von Klaus W. (mfgkw)


Lesenswert?

Du hast da vollkommen recht.

Aber ich hatte es so verstanden, daß die Lesefunktion unabänderlich 
vorgegeben ist und unsinnigerweise ein Byte als int liefert.

Wenn man einen Daumen auf der Funktion hat, sollte man sie in der Tat zu 
uint8_t abändern.
Dann würden meine beiden 0xFF&... entfallen.

von Rolf M. (rmagnus)


Lesenswert?

Klaus W. schrieb:
> Aber ich hatte es so verstanden, daß die Lesefunktion unabänderlich
> vorgegeben ist und unsinnigerweise ein Byte als int liefert.

Das hat in C ja Tradition.

von Nop (Gast)


Lesenswert?

Jörg W. schrieb:

> Insbesondere ist es undefined behaviour, wenn "msb" eine negative Zahl
> ist.

Für GCC und Clang kann man das mit -fno-strict-overflow beheben.

von mh (Gast)


Lesenswert?

Nop schrieb:
> Jörg W. schrieb:
>
>> Insbesondere ist es undefined behaviour, wenn "msb" eine negative Zahl
>> ist.
>
> Für GCC und Clang kann man das mit -fno-strict-overflow beheben.

Kann man machen. Man kann es aber auch einfach richtig machen.

von Nop (Gast)


Lesenswert?

mh schrieb:

> Kann man machen. Man kann es aber auch einfach richtig machen.

Das ist "richtig". Deswegen wird das im Linuxkernel auch so gemacht. 
Schließlich sind CPUs, auf denen das Zweierkomplement nicht genutzt 
wird, ausgesprochen unüblich geworden - und nur das war überhaupt der 
Grund für das UB. Mal abgesehen davon, daß das von Anfang an nicht UB, 
sondern IB hätte sein sollen.

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


Lesenswert?

Nop schrieb:
> Das ist "richtig".

Für den hier genannten Fall nicht, denn es geht auch anders (und damit 
durchaus schöner und standardkonform portabel). Auch würde ich sowas wie 
einen Kernel (egal von wem) nicht als Referenz für "das ist richtig" 
benutzen, wenn es um Features einer Programmiersprache geht.

Nicht-2er-Komplement-Maschinen könnten perspektivisch im C-Standard 
keine Berücksichtigung mehr finden, wenn ich die Diskussionen in WG14 
richtig aufgefasst habe, über das, was UB darf und was nicht, wird 
gerade heiß diskutiert.

von Nop (Gast)


Lesenswert?

Jörg W. schrieb:

> Für den hier genannten Fall nicht, denn es geht auch anders

Naja, man kann natürlich einfach entsprechend aufaddieren, um im 
Wertebereich eines uint16_t zu sein. Oder, sofern man die Endianess im 
Griff hat, kann man auch mit memcpy arbeiten (wird ja wegoptimiert), 
oder zumindest für C mit einer union.

> Auch würde ich sowas wie einen Kernel (egal von wem) nicht als Referenz
> für "das ist richtig" benutzen, wenn es um Features einer
> Programmiersprache geht.

Der Hinweis ist dahingehend zu verstehen, daß die leitenden Entwickler 
des Linuxkernels kompetenter sind als jeder Poster hier und insofern 
diese Lösung nicht pauschal als Anfängerquark zu verwerfen ist.

Schlußendlich geht es nicht darum, den C-Standard zu befriedigen, 
sondern richtig arbeitende Software zu schreiben. Wenn man dabei um den 
teilweise ziemlich kaputten Standard herumarbeitet und sich dessen 
bewußt ist, kann das durchaus vertretbar sein.

> Nicht-2er-Komplement-Maschinen könnten perspektivisch im C-Standard
> keine Berücksichtigung mehr finden, wenn ich die Diskussionen in WG14
> richtig aufgefasst habe

Wäre sinnig.

> über das, was UB darf und was nicht, wird gerade heiß diskutiert.

Da ist man damals einfach zu sehr mit der Gießkanne durchmarschiert - 
bis dahin, daß simple Compiletime-Fehler als UB definiert wurden 
(fehlendes abschließendes ' oder " etwa).

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


Lesenswert?

Nop schrieb:
> Der Hinweis ist dahingehend zu verstehen, daß die leitenden Entwickler
> des Linuxkernels kompetenter sind als jeder Poster hier

Das schließe ich daraus nicht.

Sie sind pragmatisch. Das ist nicht per se verkehrt.

von Nop (Gast)


Lesenswert?

Jörg W. schrieb:

> Das schließe ich daraus nicht.

Das ist auch nicht "daraus" zu schließen. Das ist eine Voraussetzung, 
keine Schlußfolgerung.

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


Lesenswert?

Nop schrieb:
> Das ist eine Voraussetzung

Nein, eine Behauptung. Es deklariert eine Art Gottheit, die über alle 
technischen Argumentationen erhaben ist, da sie ja per se alles richtig 
macht.

Das mag ich daran nicht.

von Nop (Gast)


Lesenswert?

Jörg W. schrieb:

> Nein, eine Behauptung. Es deklariert eine Art Gottheit

Nein, tut es nicht. In einer Meritokratie stehen Leute nämlich nicht aus 
Dogma an der Spitze, sondern weil sie mehr zuwege gebracht haben als 
jeder hier im Forum.

Sofern Du keine vergleichbare Meriten vorzuweisen hast, zeugt es 
lediglich von Dunning-Kruger Deinerseits, wenn Du mit religiösen 
Begriffen ankommst.

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


Lesenswert?

Nop schrieb:
> Sofern Du keine vergleichbare Meriten vorzuweisen hast

Wenn das für dich dein einziges Kriterium ist: tschüss. Mit solchen 
Leuten muss ich nicht diskutieren, erst recht nicht anonym.

von Peter D. (peda)


Lesenswert?

Peter R. schrieb:
> Auf dem AVR
> ist die Ausgabe korrekt ("-1056"), auf dem STM32 nicht.

Schön, und nun?
Wie wärs damit, auch mal den fehlerhaften Wert zu zeigen.
Einfach nur "Fehler" zu rufen, bringt nicht viel.
Glück für Dich, daß die Profis den Fehler auch ohne Deine Hilfe entdeckt 
haben.

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.