Forum: Compiler & IDEs Unsigned Variablen vermeiden


von Andi K. (fry12)


Lesenswert?

Hallo Leute!

Ich lese gerade das Buch "Expert C Programming" von Peter van der 
Linden. Dort werden unter anderem die Typkonversionen bei arithemtischen 
Operationen ein wenig beleuchtet. Der Autor gibt daraufhin den Tipp, 
unsigned Variablen zu vermeiden bzw. nur für Dinge wie Bitfelder zu 
nutzen. Vor allem sollte es vermieden werden, eine Variable als unsigned 
zu deklarieren, nur weil der Wert dieser Variable nicht negativ werden 
kann.

Konkret schreibt der Autor:
"Use a signed type like int and you won't have to worry about boundary 
cases in the detailed rules for promoting mixed types."

Kann mir das jemand genauer erklären? So ganz verstehe ich die 
Erläuterung des Autors im Buch nämlich nicht, es wird leider nur aus dem 
ANSI C Standard zitiert (bin kein blutiger C Anfänger, würde mich 
vielleicht als "fortgeschrittenen Anfänger" bezeichnen ;)).

Meiner Meinung nach widerspricht dieser Tipp der gängigen Praxis, 
zumindest für den Code, den ich zu Gesicht bekomme (fast ausschließlich 
AVR/ARM-Code). Dort ist es eigentlich Gang und Gäbe für Variablen, die 
nicht negativ werden können unsigned-Typen zu verwenden.

Vielen Dank schon mal für eure Antworten!

von (prx) A. K. (prx)


Lesenswert?

Andi K. schrieb:
> Kann mir das jemand genauer erklären?

Wenn auf 1 auf "unsigned short" addierst, dann hat das Zwischenergebnis 
üblicherweise den Typ "unsigned", wenn es eine 8/16-Bit Maschine ist und 
den Typ "int", wenn es eine 32/64-Bit Maschine ist.

Ähnlich sieht es aus, wenn du 1L auf "unsigned" addierst. Das Ergebnis 
ist "unsigned long" wenn sizeof(unsigned) == sizeof(long) (wie Win64), 
andernfalls "long" (wie Linux64).

: Bearbeitet durch User
von Rene S. (Firma: BfEHS) (rschube)


Lesenswert?

Hallo,

wenn du in deinem Programm sicherstellen kannst, das du nie in den 
negativen Bereich kommst, ist unsigned okay.

Wenn du das nicht kannst ist es sicherer signed zu verwenden.

Außerdem musst du auch das obere Ende dieses Zahlenbereiches beobachten. 
Es ist zwar nur ein Bit, leider aber das mit der höchsten Wertigkeit.

Grüße

von Kapuzenzwerg (Gast)


Lesenswert?

Es geht dabei in erster linie darum, unbemerkte Überläufe zu verhindern.
Hier ein paar Pro- und Kontra-Argumente:
http://www.soundsoftware.ac.uk/c-pitfall-unsigned

von (prx) A. K. (prx)


Lesenswert?

Bei einigen 8-Bit Microcontrollern ist es jedoch ungünstig, Typen mit 
Vorzeichen zu verwenden, weil deren Vergleichsbefehle bzw. Sprünge das 
von Haus aus nicht hergeben und der Code deshalb umständlicher wird. Das 
betrifft beispielsweise 8051 und die PICs, nicht aber AVR.

: Bearbeitet durch User
von Sebastian V. (sebi_s)


Lesenswert?

Das Problem mit unsigned Variablen ist, dass Konvertierungen zwischen 
signed und unsigned sehr merkwürdig sein kann und nicht die Ergebnisse 
liefert die man erwartet. Ein Beispiel:
1
unsigned int one = 1;
2
int minus_one = -1;
3
if(one < minus_one)
4
  printf("This is true!");

Ein anderes Argument warum häufig unsigned benutzt wird ist wie du schon 
sagst, dass man den Wertebereich auf postive Zahlen beschränken will. 
Leider funktioniert das genauso wenig, da negative Zahlen einfach 
komplett ohne Warnung zu unsigned konvertiert werden:
1
void foo(unsigned int i)
2
{
3
  printf("%u", i);
4
}
5
...
6
foo(-1); // Gibt keine Warnung

Meine Empfehlung zur Benutzung von unsigned ist, dass man auf unsigned 
verzichten sollten außer wenn:
- man mit Bitfeldern und Bitoperationen wie Shifts arbeiten möchte
- man auf modulo 2^32 (oder welche Bitzahl auch immer) Rechnungen 
angewiesen ist

Leider hat man im Standard den Typen size_t eingeführt, der für Größen 
benutzt wird und unsigned ist. Daher muss man die oberen Punkte wohl 
erweitern um:
- wenn man mit Libraries arbeitet die unsigned Variablen nutzen

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


Lesenswert?

A. K. schrieb:
> Das betrifft beispielsweise 8051 und die PICs, nicht aber AVR.

Selbst auf einem AVR spart einem aber der Wegfall der sign extension
oft genug etwas Code ein, gerade bei der Arbeit mit kleinen Zahlen
(uint8_t).

Über die im verlinkten Artikel genannten Probleme mit unsigned
underflow bin ich eigentlich relativ selten gestolpert.  Allerdings
muss ich natürlich auch sagen, wer bei einer Rechnung wie:
1
unsigned candidates = total - lengths[i] + 1;

nicht auf den Gedanken kommt zu verifizieren, dass sein "total"
immer mindenstens so groß ist wie "lengths[i]", der würde auf einem
kleinen Microcontroller auch mit vorzeichenbehafteten Typen baden
gehen, da er dann vermutlich genauso schnell aus dem Blick verliert,
dass die Regel „vorzeichenbehaftete Typen laufen nicht über“ bei
32767 auf so einem Controller ihre jähe Grenze findet ...

Vermutlich will das Buch eher auf die nicht immer sofort mental
erschließlichen Implikationen der integer promotion rules hinaus,
und über diese muss man in der Tat immer wieder gründlich nachdenken.

von Peter D. (peda)


Lesenswert?

A. K. schrieb:
> Das
> betrifft beispielsweise 8051 und die PICs, nicht aber AVR.

Auch den AVR.
Alle 8Bitter brauchen zusätzliche Schritte bei Rechnungen >8Bit.
Wenn man signed 8Bit mit 16 oder 32Bit verrechnet, muß zuerst eine 
Vorzeichenerweiterung erfolgen.

von (prx) A. K. (prx)


Lesenswert?

Peter Dannegger schrieb:
> Alle 8Bitter brauchen zusätzliche Schritte bei Rechnungen >8Bit.

Klar, aber das meinte ich nicht. AVR kann auch mit Vorzeichen auf ">" 
vergleichen, PIC nicht unmittelbar. AVR könnte aber eine billigere 
Vorzeichenerweiterung gebrauchen. Die hatte schon 6809, aber vielleicht 
waren die Designer beim AVR dafür zu prüde.

: Bearbeitet durch User
von Kaj (Gast)


Lesenswert?

Kapuzenzwerg schrieb:
> Es geht dabei in erster linie darum, unbemerkte Überläufe zu verhindern.

Was sagt denn der C-Standard zum Thema signed/unsigned Overflow?

Nagelt mich nicht an die Wand, aber ich meine hier im Forum gelesen zu 
haben, das Laut dem Standard das verhalten bei signed overflow nicht 
definiert ist.

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


Lesenswert?

Kaj schrieb:
> Nagelt mich nicht an die Wand, aber ich meine hier im Forum gelesen zu
> haben, das Laut dem Standard das verhalten bei signed overflow nicht
> definiert ist.

So ist es.

Unsigned „rollt definiert über“, und das ist halt das, worüber der
Verfasser des obigen Links gestolpert ist: wenn man einen 0 hat und
davon 1 abzieht, dann bekommt man eine (u. U. sehr) große Zahl. ;-)

Allerdings funktioniert seine Blauäugigkeit „ich will mir vorher keine
Gedanken um den Wertebereich machen“ nur so einigermaßen, seit der Typ
“int” auf gängigen Maschinen 32 bit hat, denn da sind die Über- und
Unterläufe einer vorzeichenbehafteten Zahl jenseits von (betragsmäßig)
zwei Milliarden, was für viele Dinge einfach wirklich ohne nähere
Größenabschätzung genügt, als dass er mit seiner Gedankenlosigkeit
mit der vorzeichenbehafteten Zahl in der Tat das kleinere Risiko hat.

Im Embedded-Bereich kann man sich derartige Sorglosigkeit normalerweise
nicht leisten (gerade nicht auf kleinen Controllern), da muss man sich
zwangsläufig um den Wertebereich Gedanken machen.  Hat man sich diese
aber gemacht, dann ist unsigned underflow auch kein Problem.

von Peter D. (peda)


Lesenswert?

Jörg Wunsch schrieb:
> Unsigned „rollt definiert über“

Und das ist auch gut so.
Z.B. wenn man 2 Zeitstempel hat und die dazwischen vergangene Zeit 
wissen will. Dann wäre es unschön, bei jedem Timerüberlauf falsche 
Ergebnisse zu erhalten.

von B. S. (bestucki)


Lesenswert?

Wie bei allem in C muss man halt wissen was man tut und die Details der 
Sprache kennen.

Welcher Wert wird c zugewiesen?
1
unsigned int a = 10;
2
int b = -2;
3
int c = a / b; /* c wird 0 zugewiesen */

Was der Compiler tut:
1
int c = 10 / (unsigned int)-2;

So sind halt einfach die Regeln von C. Der GCC spuckt mit 
-Wsign-onversion auch Warnungen aus. Für obiges Beispiel:
1
[Warning] conversion to 'unsigned int' from 'int' may change the sign of the result [-Wsign-conversion]
2
[Warning] conversion to 'int' from 'unsigned int' may change the sign of the result [-Wsign-conversion]

Um das Problem zu umschiffen, muss man die Berechnung mit einem 
grösseren signed Typen durchführen.


Ich selbst nutzte sowohl signed als auch unsigned Typen. Das kommt immer 
darauf an, welchen Wertebereich die Variable haben soll. Ich mische 
diese auch in Berechnungen, natürlich mit entsprechenden Casts um auf 
der sicheren Seite zu sein.

Auch mit den (u)int_fastXX_t Typen aus stdint muss man aufpassen. Auf 
einem 32-Bit PC sind die 8, 16 und 32 Bit Typen identisch, auf einem 
kleinen uC nicht. So ist es schnell passiert, dass ein Programm, das auf 
einem PC korrekt arbeitet, auf einem uC scheitert.

: Bearbeitet durch User
von Yalu X. (yalu) (Moderator)


Lesenswert?

Ich halte das üblicherweise so:

0. Falls möglich, Formeln so aufstellen, dass von vornherein nur mit
   positiven Zahlen gerechnet werden muss

1. Operanden in normalen Berechnungen (+, -, *, /) nach Möglichkeit
   immer signed

2. Divisionen mit negativen Operanden vermeiden

3. Bit-Operationen (&, |, ^, ~) nur mit positiven¹ Operanden

4. Shift-Operationen (<<, >>) nur mit positivem¹ linken Operanden

5. Absichtliche Überläufe  für Modulo-2**n-Berechnungen) immer unsigned

6. Mischen von signed und unsigned in einer Operation, in der der signed
   Operand negativ werden kann, grundsätzlich vermeiden. Führt kein Weg
   daran herum, unsigned Operand explizit in signed Wert passender Größe
   casten.

7. Berechnung mit größeren Zahlen immer auf mögliche Überläufe prüfen
   (nicht im Programm, sondern im Kopf).

——————————
¹) "positiv" heißt: wenn signed, dann garantiert ≥0

von Rolf M. (rmagnus)


Lesenswert?

Kaj schrieb:
> Kapuzenzwerg schrieb:
>> Es geht dabei in erster linie darum, unbemerkte Überläufe zu verhindern.
>
> Was sagt denn der C-Standard zum Thema signed/unsigned Overflow?

Bei signed ist es nicht definiert, bei unsigned gibt es keine Überläufe, 
aber eine Modulo-Rechnung.

be stucki schrieb:
> Auch mit den (u)int_fastXX_t Typen aus stdint muss man aufpassen. Auf
> einem 32-Bit PC sind die 8, 16 und 32 Bit Typen identisch, auf einem
> kleinen uC nicht. So ist es schnell passiert, dass ein Programm, das auf
> einem PC korrekt arbeitet, auf einem uC scheitert.

Es wäre allerdings auch ziemlich dämlich, diese Typen zu benutzen, wenn 
die Größe des Typs exakt stimmen muss.

Yalu X. schrieb:
> 4. Shift-Operationen (<<, >>) nur mit positivem¹ linken Operanden

Der rechte muß ja sowieso positiv sein, also hättest du das nicht 
unbedingt auf den linken einschränken müssen.

von Hannes J. (Firma: _⌨_) (pnuebergang)


Lesenswert?

be stucki schrieb:
> Wie bei allem in C muss man halt wissen was man tut und die Details der
> Sprache kennen.

Das muss man eigentlich bei allen Sprachen. Deshalb stöhne ich innerlich 
auf, wenn mal wieder so ein Bastel-Maker-Kiddi ankommt und die neuste 
Modesprache über den grünen Klee lobt.

von Peter (Gast)


Lesenswert?

Sebastian V. O. schrieb:
> Meine Empfehlung zur Benutzung von unsigned ist, dass man auf unsigned
> verzichten sollten außer wenn:
> - man mit Bitfeldern und Bitoperationen wie Shifts arbeiten möchte
> - man auf modulo 2^32 (oder welche Bitzahl auch immer) Rechnungen
> angewiesen ist

und wenn
- meine Daten einfach unsigned sind, wie z.B. ein 8-Bit Grauwert in 
einem Bild

von Rolf M. (rmagnus)


Lesenswert?

Peter schrieb:
> und wenn
> - meine Daten einfach unsigned sind, wie z.B. ein 8-Bit Grauwert in
> einem Bild

Dazu müßte man sie aber erstmal explizit in einen größeren unsigned-Typ 
konvertieren, denn 8-Bit-Typen werden vor einer Berechnung automatisch 
nach int "promoted", somit wird die Berechnung mit Vorzeichen 
durchgeführt.

von Micha (Gast)


Lesenswert?

C ist ja bekannt für seine gnadenlose Logik inclusive der entsprechenden 
Fallstricke. Am Ende ist tatsächlich immer der Programmierer schuld wenn 
etwas nicht so funktioniert wie erwartet.

Bezugnehmend auf die Frage des TO:
Ich hatte mal so einen Fall - Microcontroller Projekt, eigentlich alles 
unsigned variable verwendet. Mir war nur folgendes Prinzip noch nicht 
vertraut: wenn man von einer unsigned Variable, die gerade Null ist, 
eins abzieht, dann ist die Variable nachher immer noch nicht kleiner als 
Null. genau so einen Vergleich if(x < 0) hatte ich aber in dem Code. Es 
brauchte eine Weile bis ich das begriff und mir dann vor den Kopf 
schlug.
Klar, mit konsequenter Verwendung von signed Variablen hätte ich die 
Klippe umschifft, hätte aber womöglich nie die Lernerfahrung gemacht. 
Ich kann die Meinung des erwähnten Buchautors nicht teilen. Datentypen 
sollten, insbedondere bei Microcontroller-Anwendungwen immer sorgfältig 
für die konkrete Anwendung und zu erwartenden Wertebereich gewählt 
werden. Praxis mit C bekommt man haupsächlich durch das Begehen und 
Auffinden von Fehlern. Es ist ne komplexe Angelegenheit, und mit so 
einfachen Kochbuchrezepten wie "grundsätzlich nur signed Variablen 
vwerwenden" kommt man da nicht wirklich weit.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Rolf Magnus schrieb:
> Yalu X. schrieb:
>> 4. Shift-Operationen (<<, >>) nur mit positivem¹ linken Operanden
>
> Der rechte muß ja sowieso positiv sein, also hättest du das nicht
> unbedingt auf den linken einschränken müssen.

Ja, da hast du natürlich recht :)

von Karl H. (kbuchegg)


Lesenswert?

Micha schrieb:

> Praxis mit C bekommt man haupsächlich durch das Begehen und
> Auffinden von Fehlern. Es ist ne komplexe Angelegenheit, und mit so
> einfachen Kochbuchrezepten wie "grundsätzlich nur signed Variablen
> vwerwenden" kommt man da nicht wirklich weit.


Doppelt unterstreichen, einrahmen, ausdrucken und an die Wand hängen!
Genau das ist die Quintessenz.
Deshalb passt auch der Vergleich Programmieren-Radfahren so gut. Man 
muss auf die Schnauze fallen um weiter zu kommen.
Ein Programmierer mit Erfahrung unterscheidet sich von einem 
Programmierer ohne Erfahrung hauptsächlich dadurch, dass der eine schon 
in mindestens 3000 Fallen mehr getappt ist (und sie gelöst hat), die 
noch auf den anderen warten. Und die 3000 sind wörtlich zu nehmen.

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


Lesenswert?

Micha schrieb:
> genau so einen Vergleich if(x < 0) hatte ich aber in dem Code

Wie soll denn auch eine vorzeichenlose Zahl jemals kleiner als 0
werden können?

Heutzutagen warnt einen allerdings ein Compiler vor sowas.

von (prx) A. K. (prx)


Lesenswert?

Jörg Wunsch schrieb:
>> genau so einen Vergleich if(x < 0) hatte ich aber in dem Code
>
> Wie soll denn auch eine vorzeichenlose Zahl jemals kleiner als 0
> werden können?

Da siehts ein Blinder mit dem Krückstock, aber hier auch?
  unsigned i, n;
  // .... 150 Zeilen, manchmal n == 0 ...
  if (i < n-1)

: Bearbeitet durch User
von Mehmet K. (mkmk)


Lesenswert?

Um solchen und aehnlichen Fehlern vorzubeugen, lass ich mich bei 
grösseren Projekten von PC-Lint vollabern. Fehler im eigentlichen Sinne 
kommen zwar nie zum Vorschein, aber unglaublich, was der an Nuancen zu 
unterscheiden vermag und an Stellen Warnungen ausgibt, auf die ich nie 
und nimmer selbst gekommen waere.

von Oliver (Gast)


Lesenswert?

Micha schrieb:
> C ist ja bekannt für seine gnadenlose Logik inclusive der entsprechenden
> Fallstricke. Am Ende ist tatsächlich immer der Programmierer schuld wenn
> etwas nicht so funktioniert wie erwartet.

Je nun, du wirst es vielleicht nicht hören wollen, aber das ist bei 
allen anderen Programmiersprachen ganz genau so.

Oliver

von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

Karl Heinz schrieb:
> Ein Programmierer mit Erfahrung unterscheidet sich von einem
> Programmierer ohne Erfahrung hauptsächlich dadurch, dass der eine schon
> in mindestens 3000 Fallen mehr getappt ist (und sie gelöst hat), die
> noch auf den anderen warten.

Doppelt unterstreichen, einrahmen, ausdrucken und an die Wand hängen!

von Peter D. (peda)


Lesenswert?

Es gibt ne Menge Dinge, die man aus heutiger Sicht an C verbessern 
könnte.
Aber nur durch das gnadenlose Festhalten an einmal getroffenen 
Vereinbarungen konnte sich C so weit verbreiten.
Und dadurch ist es auch für jede Architektur verfügbar.
Diese Beständigkeit und Verbreitung gibt es bei keiner anderen 
Programmiersprache.

von (prx) A. K. (prx)


Lesenswert?

Oliver schrieb:
> Je nun, du wirst es vielleicht nicht hören wollen, aber das ist bei
> allen anderen Programmiersprachen ganz genau so.

Je mehr man versucht, sich dem "do what I mean" Paradigma zu nähern, an 
Stelle des für C typischen "do what I say", desto komplexer werden die 
entsprechenden Implementierungen. Wodurch die Wahrscheinlichkeit wächst, 
dass dann doch mal die Implementierung Schuld trägt.

: Bearbeitet durch User
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.