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!
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
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
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
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
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
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.
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.
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
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.
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.
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.
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
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
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.
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.
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
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.
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.
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 :)
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
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.
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
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.
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
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!
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.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.