Unsigned Variablen vermeiden

#3815632
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!
#3815642
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).
(Firma: BfEHS) #3815647
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
#3815660
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
Moderator Persönliche Seite #3815684
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.
Gast #3815731
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.
Moderator Persönliche Seite #3815751
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.
#3815777
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.
Moderator #3815972
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
#3816684
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.
Gast #3816703
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
Gast #3816873
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.
#3817001
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.
#3818027
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.
Gast #3818056
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
#3818066
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.
#3818069
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.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren