C - Differenzen von zwei unsigned int?
C - Differenzen von zwei unsigned int?
Abend zusammen :-)
Ich habe hier die Situation, dass ich eine Differenz aus zwei uint32_t Variablen berechnen möchte. Jede Variable für sich kann natürlich nur 0 oder positiv sein, die Differenz jedoch kann bekanntermassen ja negativ ausfallen.
Anschliessen rufe ich die abs Funktion aus math.h aus, da ich die Differenz positiv brauche. Die Funktionen warnt nun ja zurecht, dass da nie was negatives "reinkommen" kann, da der Ausdruck vom Typ unsigned int sei.
Wie löst man ein solches Problem am einfachsten? Einen cast zu int32_t fällt ja ins Wasser, da dieser überlaufen könnte.
Ich könnte nun zwar zu int64_t casten, nur frage ich mich dann, wie ich verfahren soll, wenn ich die Differenz aus zwei uint64_t berechnen will.
Geht das irgendwie eleganter, als zuvor mittels Verzweigung zu prüfen, welcher Wert der beiden der grössere ist?
Wie habt ihr jeweils sowas gelöst?
Ich habe hier die Situation, dass ich eine Differenz aus zwei uint32_t Variablen berechnen möchte. Jede Variable für sich kann natürlich nur 0 oder positiv sein, die Differenz jedoch kann bekanntermassen ja negativ ausfallen.
Anschliessen rufe ich die abs Funktion aus math.h aus, da ich die Differenz positiv brauche. Die Funktionen warnt nun ja zurecht, dass da nie was negatives "reinkommen" kann, da der Ausdruck vom Typ unsigned int sei.
Wie löst man ein solches Problem am einfachsten? Einen cast zu int32_t fällt ja ins Wasser, da dieser überlaufen könnte.
Ich könnte nun zwar zu int64_t casten, nur frage ich mich dann, wie ich verfahren soll, wenn ich die Differenz aus zwei uint64_t berechnen will.
Geht das irgendwie eleganter, als zuvor mittels Verzweigung zu prüfen, welcher Wert der beiden der grössere ist?
Wie habt ihr jeweils sowas gelöst?
- Krishty
- Establishment
- Beiträge: 8008
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: C - Differenzen von zwei unsigned int?
Außerdem gibt sie ihr Ergebnis signed zurück, so dass ein Unterschied von 2^32-1 zu -1 wird.
Geht es dir wirklich um die positive Differenz? Dann ist wahrscheinlich if die einfachste Lösung, gemeinsam mit std::max(a, b) - std::min(a, b) (weil man sich vorzüglich streiten kann, ob Branches oder Funktionsaufrufe einfacher sind).
Wie ich sowas gelöst habe? Meist habe ich den Wertebereich der Operanden eingeschränkt, so dass die Differenz nie überlaufen kann. Haben wohl auch ein paar Spiele so gemacht. 2³² ist nun wirklich eine Menge Werte :)
- Schrompf
- Moderator
- Beiträge: 4593
- Registriert: 25.02.2009, 23:44
- Benutzertext: Lernt nur selten dazu
- Echter Name: Thomas Ziegenhagen
- Wohnort: Dresden
- Kontaktdaten:
Re: C - Differenzen von zwei unsigned int?
Das Problem ist real, das ist mir auch schon begegnet. Die Differenz, wie Krishty schon sagte, kann 2^32 positiv oder negativ sein, passt also nicht mehr in einen int32 oder uint32 rein. Cast zu int64_t und dann Differenz bilden gibt Dir das allzeit korrekte Ergebnis, aber bei uint64-Vergleich hast Du diese Möglichkeit nicht mehr. max() - min() ist da ne super Idee.
Häuptling von Dreamworlds. Baut an was Neuem. Hilft nebenbei nur höchst selten an der Open Asset Import Library mit.
Re: C - Differenzen von zwei unsigned int?
Ich benutze in letzter Zeit eigentlich gar keine unsigned Datentypen mehr wenn ich nicht muss (z.B. weil ich mit Schnittstellen arbeite die unsigned erwarten). Letztendlich war die Erkenntnis, dass ich vermutlich noch nie das Intervall jenseits von 2^31 sinnvoll verwendet habe, aber durchaus schonmal das ein oder andere Problem hatte, weil negative Zahlen nicht gingen. Ich dachte irgendwie, es sei elegant und robust, wenn der Datentyp gar nicht erst negative Werte zulässt, aber genützt hat mir das noch nie, geschadet aber schon das ein oder andere mal. Daher mache ich das jetzt nicht mehr. Und wenn man mehr als 2^31 braucht, dann braucht man vermutlich auch mehr als 2^32 und sollte direkt auf 64 Bit wechseln.
Lieber dumm fragen, als dumm bleiben!
- Krishty
- Establishment
- Beiträge: 8008
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: C - Differenzen von zwei unsigned int?
Das ist btw auch die Empfehlung der C++ Core Guidelines und wird dadurch torpediert, dass size_t (und damit alles mit sizeof) unsigned ist. Irgendwo gibt es einen kompletten Talk darüber, wie sehr die C++-Community diese Entscheidung bereut.
- dot
- Establishment
- Beiträge: 1704
- Registriert: 06.03.2004, 18:10
- Echter Name: Michael Kenzel
- Kontaktdaten:
Re: C - Differenzen von zwei unsigned int?
Das Grundproblem hier ist das gängige Missverständnis, dass unsigned bedeutet "kann nicht negativ sein". Das ist nicht was unsigned in C und C++ bedeutet. unsigned Integer sind keine normalen Ganzzahlen. Der Unterschied zwischen signed und unsigned in C und C++ ist dass unsigned Integer einen Restklassenring darstellen während signed Integer normale Ganzzahlen sind. Die Tatsache dass unsigned Integer keine negativen Werte annehmen, ist lediglich Konsequenz aber nicht der eigentliche Zweck dessen, was unsigned Integer sind. Daher sind unsigned Integer auch gänzlich ungeeignet, um "kann nicht negativ sein" auszudrücken. Leider ist das aber genau was die meisten meinen zu tun wenn sie unsigned verwenden. Aber nichts hindert dich daran, einem unsigned Integer einen negativen Wert zuzuweisen. Das ist völlig legal und wohldefiniert, gibt immer den Rest Modulo 2ⁿ. Denn das ist was unsigned eben bedeutet. unsigned Integer können nicht überlaufen weil sie eben per Definition immer zum Rest Modulo 2ⁿ werden, welcher niemals größer als 2ⁿ - 1 sein kann. Die Differenz zweier unsigned Integer ist daher auch niemals negativ. Die Differenz zweier unsigned Integer ist der Rest der Differenz Modulo 2ⁿ…
Langer Rede kurzer Sinn: Die Wahrscheinlichkeit ist groß, dass diese beiden uint32_t, von denen du hier die Differenz bilden willst, von vorn herein schon gar keine uint32_t sein sollten. Restklassenarithmetik ist nur sehr sehr selten was man eigentlich wirklich will. In dem Moment wo du einen unsigned Typ auswählst, drückst du aus, dass Wraparound statt Overflow das gewünschte korrekte Verhalten ist. Für Dinge wie irgendwelche Bitmasken oder Indexberechnungen in einem Ringbuffer ist das vielleicht der Fall. Das wars dann so ziemlich aber auch schon…
Das ist auch einer der Hauptgründe wieso es einfach falsch ist, dass size_t ein unsigned Typ ist. Sizes verhalten sich nicht so. Die Summe zweier Sizes kann niemals kleiner als die einzelnen Sizes sein. C und infolge auch C++ haben aber leider genau das zum offiziell "korrekten" Verhalten erklärt. Wie Krishty schon erwähnt hat, leider ein sehr großer Fehler mit Konsequenzen von Sicherheitslücken bis hin zur Tatsache dass Pointerarithmetik deswegen fundamental nicht wohldefiniert ist (eben genau das Problem das du hier auch hast: die Differenz zweier Pointer ist potentiell nicht repräsentierbar, - ist nicht die Inverse von +, einfach großartig).
Achja, unsigned Integer bieten dem Compiler daher generell auch weniger Möglichkeiten zu Optimieren: https://kristerw.blogspot.com/2016/02/h ... ables.html. Normale signed Integer haben viele nützliche Eigenschaften wie z.B. dass gilt x < x + 1. Für unsigned Integer gilt das z.B. eben genau nicht… ;-)
Wenn sich also das nächste mal in eurer Gegenwart jemand mal wieder lautstark darüber äußern muss, wie dumm C++ doch ist weil signed Integer nicht richtig "Zweierkomplement" machen weil Overflow statt Wraparound (btw: zwei Dinge die überhaupt nichts miteinander zu tun haben), dann wisst ihr jetzt zumindest was Sache ist (hint: C++ macht's richtig, abgesehen von size_t, der jemand der sich am aufregen ist hat nur keine Ahnung).
Re: C - Differenzen von zwei unsigned int?
https://en.wikipedia.org/wiki/Integer_overflow
Also, "signed integer sind normale Ganzzahlen" ist es doch auch irgendwie nicht. Wenn es bei zu großen Zahlen zu UB kommt, würde ich vermuten (ohne es getestet zu haben), dass man dann bei "größte Zahl plus eins" einfach häufig bei "betragsmäßig größte negative Zahl" rauskommt. Und ein x < x + 1 gilt dann auch nicht immer, aber wenn es nicht gilt, interessiert es eben keinen, weil UB. unsigned vs. signed ist dann irgendwie "es passiert etwas komisches" vs. "es passiert irgendetwas komisches".
Also, "signed integer sind normale Ganzzahlen" ist es doch auch irgendwie nicht. Wenn es bei zu großen Zahlen zu UB kommt, würde ich vermuten (ohne es getestet zu haben), dass man dann bei "größte Zahl plus eins" einfach häufig bei "betragsmäßig größte negative Zahl" rauskommt. Und ein x < x + 1 gilt dann auch nicht immer, aber wenn es nicht gilt, interessiert es eben keinen, weil UB. unsigned vs. signed ist dann irgendwie "es passiert etwas komisches" vs. "es passiert irgendetwas komisches".
Lieber dumm fragen, als dumm bleiben!
Re: C - Differenzen von zwei unsigned int?
Wahnsinn, wieder ne Menge gelernt! Vielen Dank!
Sehe ich das richtig, dass demnach auch ein Vergleich zwischen unsigned und signed problematisch ist und folglich vermieden werden muss?
Sehe ich das richtig, dass demnach auch ein Vergleich zwischen unsigned und signed problematisch ist und folglich vermieden werden muss?
Code: Alles auswählen
if(12U > 3) ... // don't
if(12 > 3U) ... // don't
if(12U > 3U)... // ok
if(12 > 3)... //ok
- dot
- Establishment
- Beiträge: 1704
- Registriert: 06.03.2004, 18:10
- Echter Name: Michael Kenzel
- Kontaktdaten:
Re: C - Differenzen von zwei unsigned int?
Overflow (integer or otherwise) ist UB. Ein Programm das UB invoked muss sich auf gar keine bestimmte Art und Weise verhalten. Beachte: Ich spreche hier nicht Umsonst vom Programm als Ganzes. UB ist eine globale Eigenschaft einer Ausführung deines Programmes und nicht nur auf das Ergebnis einer einzelnen Operation beschränkt. Das wäre unspecified Behavior, nicht undefined Behavior. Das Verhalten eines Programmes dessen Ausführung UB enthält ist gänzlich undefiniert. Die Frage was rauskommt stellt sich überhaupt erst gar nicht weil eben überhaupt erst gar nichts rauskommen muss. In einem C++ Programm mit wohldefiniertem Verhalten, verhalten sich signed Integer wie normale Ganzzahlen. Das ist eben genau was dadurch erreicht wird, dass signed Overflow UB ist. Die Frage wie signed Integer sich in einem Programm ohne wohldefinertem Verhalten verhalten, macht von vornherein schon gar keinen Sinn. C++ ist eine Programmierschnittstelle. Du programmierst entweder gegen die von C++ definierte Schnittstelle oder nicht. So lange du dich innerhalb der spezifizierten Schnittstelle bewegst, kannst du dich darauf verlassen, dass signed Integer Ganzzahlen modellieren und dass x < x + 1 immer wahr sein wird. Wenn du die Vorbedingungen der C++ Schnittstelle verletzt, dann programmierst du nicht mehr gegen die C++ Schnittstelle. Die Bedeutung deines Programmes ist dann hinfällig. Zumindest kann sie nicht mehr basierend auf den Regeln von C++ erschlossen werden, denn diese Regeln sind gar nicht erst anwendbar. Daher kann auch keine Menge an Tests dir sagen, was rauskommen wird. Es gibt kein konsistentes Verhalten das du überhaupt Testen könntest. In jedem Test kann etwas anderes rauskommen oder eben auch gar nichts rauskommen. Wenn der Compiler z.B. sieht, dass ein bestimmter Codepfad an einer bestimmten Stelle immer einen Overflow produziert, kann der Compiler diesen Codepfad einfach komplett wegoptimieren (beachte: das enthält auch alles was dieser Pfad macht bevor die Operation mit dem eigentlichen UB ausgeführt wird), weil ein Programm mit wohldefiniertem Verhalten ja niemals in diesen Pfad gehen könnte und nicht wohldefiniertes Verhalten muss der generierte Maschinencode nicht implementieren. Und ja, Compiler machen genau das auch wirklich. Gab iirc z.B. mal einen berühmten Exploit im Linux Kernel der dadurch verursacht wurde, dass ein Check ob der Benutzer über bestimmte Rechte verfügt einfach wegoptimiert wurde, weil der Codepfad wo der Benutzer über diese Rechte nicht verfügt UB enthielt und der Check in einem Programm mit wohldefiniertem Verhalten also niemals nicht erfolgreich ausfallen konnte…Jonathan hat geschrieben: ↑13.03.2023, 07:40 https://en.wikipedia.org/wiki/Integer_overflow
Also, "signed integer sind normale Ganzzahlen" ist es doch auch irgendwie nicht. Wenn es bei zu großen Zahlen zu UB kommt, würde ich vermuten (ohne es getestet zu haben), dass man dann bei "größte Zahl plus eins" einfach häufig bei "betragsmäßig größte negative Zahl" rauskommt. Und ein x < x + 1 gilt dann auch nicht immer, aber wenn es nicht gilt, interessiert es eben keinen, weil UB. unsigned vs. signed ist dann irgendwie "es passiert etwas komisches" vs. "es passiert irgendetwas komisches".
Nachdem alle hier verwendeten Werte in allen hier verwendeten Typen repräsentiert werden können, passiert hier überall das Gleiche. Aber du hast richtig erkannt, dass signed vs unsigned Vergleiche sehr problematisch sind. C und C++ haben leider die extrem kaputte Angewohnheit, dass signed Typen von gleichem Integer-Conversion-Rank in Arithmetik implizit zum entsprechenden unsigned Typen konvertiert werden.starcow hat geschrieben: ↑13.03.2023, 18:43 Wahnsinn, wieder ne Menge gelernt! Vielen Dank!
Sehe ich das richtig, dass demnach auch ein Vergleich zwischen unsigned und signed problematisch ist und folglich vermieden werden muss?
Code: Alles auswählen
if(12U > 3) ... // don't if(12 > 3U) ... // don't if(12U > 3U)... // ok if(12 > 3)... //ok
Seit C++20 gibt's std::cmp_less etc. für sichere signed vs unsigned Comparisons.
Zuletzt geändert von dot am 13.03.2023, 20:36, insgesamt 3-mal geändert.
- Schrompf
- Moderator
- Beiträge: 4593
- Registriert: 25.02.2009, 23:44
- Benutzertext: Lernt nur selten dazu
- Echter Name: Thomas Ziegenhagen
- Wohnort: Dresden
- Kontaktdaten:
Re: C - Differenzen von zwei unsigned int?
Anmerkung: wenn Du die Differenz zweier int32 bilden willst, können ganz genauso Differenzen entstehen, die der Datentyp nicht mehr abbilden kann, wie bei uint32. Der ganze Rest... tja, manchmal will man definierten Overflow haben, manchmal will man die Vorteile von UB haben.
Häuptling von Dreamworlds. Baut an was Neuem. Hilft nebenbei nur höchst selten an der Open Asset Import Library mit.
- dot
- Establishment
- Beiträge: 1704
- Registriert: 06.03.2004, 18:10
- Echter Name: Michael Kenzel
- Kontaktdaten:
Re: C - Differenzen von zwei unsigned int?
Sowas die definierten Overflow gibt es in C und C++ nicht. Falls du Wraparound meinst: Das kann man ganz einfach erreichen indem man nach unsigned Castet und wieder zurück.Schrompf hat geschrieben: ↑13.03.2023, 20:26 Anmerkung: wenn Du die Differenz zweier int32 bilden willst, können ganz genauso Differenzen entstehen, die der Datentyp nicht mehr abbilden kann, wie bei uint32. Der ganze Rest... tja, manchmal will man definierten Overflow haben, manchmal will man die Vorteile von UB haben.
Re: C - Differenzen von zwei unsigned int?
Danke für die Erklärung. UB war mir im Wesentlichen bekannt, habs aber wegen der Details trotzdem gerne nochmal gelesen.dot hat geschrieben: ↑13.03.2023, 19:56 Overflow (integer or otherwise) ist UB. Ein Programm das UB invoked muss sich auf gar keine bestimmte Art und Weise verhalten. Beachte: Ich spreche hier nicht Umsonst vom Programm als Ganzes. UB ist eine globale Eigenschaft einer Ausführung deines Programmes und nicht nur auf das Ergebnis einer einzelnen Operation beschränkt. Das wäre unspecified Behavior, nicht undefined Behavior. Das Verhalten eines Programmes dessen Ausführung UB enthält ist gänzlich undefiniert. Die Frage was rauskommt stellt sich überhaupt erst gar nicht weil eben überhaupt erst gar nichts rauskommen muss. In einem C++ Programm mit wohldefiniertem Verhalten, verhalten sich signed Integer wie normale Ganzzahlen. Das ist eben genau was dadurch erreicht wird, dass signed Overflow UB ist. Die Frage wie signed Integer sich in einem Programm ohne wohldefinertem Verhalten verhalten, macht von vornherein schon gar keinen Sinn. C++ ist eine Programmierschnittstelle. Du programmierst entweder gegen die von C++ definierte Schnittstelle oder nicht. So lange du dich innerhalb der spezifizierten Schnittstelle bewegst, kannst du dich darauf verlassen, dass signed Integer Ganzzahlen modellieren und dass x < x + 1 immer wahr sein wird. Wenn du die Vorbedingungen der C++ Schnittstelle verletzt, dann programmierst du nicht mehr gegen die C++ Schnittstelle. Die Bedeutung deines Programmes ist dann hinfällig. Zumindest kann sie nicht mehr basierend auf den Regeln von C++ erschlossen werden, denn diese Regeln sind gar nicht erst anwendbar. Daher kann auch keine Menge an Tests dir sagen, was rauskommen wird. Es gibt kein konsistentes Verhalten das du überhaupt Testen könntest. In jedem Test kann etwas anderes rauskommen oder eben auch gar nichts rauskommen. Wenn der Compiler z.B. sieht, dass ein bestimmter Codepfad an einer bestimmten Stelle immer einen Overflow produziert, kann der Compiler diesen Codepfad einfach komplett wegoptimieren (beachte: das enthält auch alles was dieser Pfad macht bevor die Operation mit dem eigentlichen UB ausgeführt wird), weil ein Programm mit wohldefiniertem Verhalten ja niemals in diesen Pfad gehen könnte und nicht wohldefiniertes Verhalten muss der generierte Maschinencode nicht implementieren. Und ja, Compiler machen genau das auch wirklich. Gab iirc z.B. mal einen berühmten Exploit im Linux Kernel der dadurch verursacht wurde, dass ein Check ob der Benutzer über bestimmte Rechte verfügt einfach wegoptimiert wurde, weil der Codepfad wo der Benutzer über diese Rechte nicht verfügt UB enthielt und der Check in einem Programm mit wohldefiniertem Verhalten also niemals nicht erfolgreich ausfallen konnte…
Aber worauf ich eigentlich hinaus wollte ist die Frage, welchen Unterschied das in der Praxis macht. Zunächst: In einer ganzen Reihe von Fällen wird der Compiler kein UB erkennen können, weil z.B. die Variablen von Usereingaben abhängen also unmöglich vorauszusagen sind. Und da es ja eine elementare Operation ist, wird dann ja in der Praxis "irgendetwas definiertes" passieren (der Quellcode hat kein definiertes Verhalten, der Maschinencode jedoch schon), was dann vermutlich auch einfach ein Wraparound ist?
Aber egal. Worüber ich eigentlich nachgedacht habe ist, welches Programm bei einem unsigned Wraparound noch "wohldefiniert" oder viel eher "so wie es soll" funktioniert. Sagen wir mal du speicherst irgendwo die Anzahl der Partikel oder Levelgröße oder Punkte oder irgendwas, und dann kommt ein Wraparound, dann ist der Wert dieser Variablen ja Quatsch, egal wie wohldefiniert er sein mag. Das Programm wird höchstwahrscheinlich also trotzdem kurz darauf Quatsch machen und abstürzen, man ist also im Wesentlichen in der selben Lage wie bei UB durch Overflows. Sprich: Wenn man nicht garantieren kann, dass die Grenzen nicht überschritten werden (in dem man z.B. explizit nicht erlaube dass mehr Objekte erzeugt werden, oder mehr Punkte erreicht werden können etc.) kann eh nie irgendetwas gutes passieren.
Lieber dumm fragen, als dumm bleiben!
- dot
- Establishment
- Beiträge: 1704
- Registriert: 06.03.2004, 18:10
- Echter Name: Michael Kenzel
- Kontaktdaten:
Re: C - Differenzen von zwei unsigned int?
Was passiert hängt eben davon ab, wie der Compiler sich entschlossen hat, alles andere als das UB zu implementieren (UB wird vom Compiler ja dazu verwendet, abzuleiten, welche Fälle vom generierten Maschinencode nicht behandelt werden müssen). Ja, es wird oft sein, dass einfach Wraparound passiert. Aber das weißt du halt nicht. Es gibt keine Garantie gegen die du coden kannst. Je nach Situation und Compilerversion und Optimizationlevel und anderen Flags passiert halt potentiell was komplett anderes. Im Extremfall passiert nach jedem Build des selben Code was anderes. Oder selbst bei jedem Mal Ausführen des selben Kompilats. Du kannst nie wissen, dass ein bestimmter Overflow in einer bestimmte Zeile Wraparound macht. Kann sein. Kann genausogut nicht sein. Und nur weils gestern so war heißt nicht, dass es heute und morgen auch so ist. UB halt…Jonathan hat geschrieben: ↑14.03.2023, 11:02 Aber worauf ich eigentlich hinaus wollte ist die Frage, welchen Unterschied das in der Praxis macht. Zunächst: In einer ganzen Reihe von Fällen wird der Compiler kein UB erkennen können, weil z.B. die Variablen von Usereingaben abhängen also unmöglich vorauszusagen sind. Und da es ja eine elementare Operation ist, wird dann ja in der Praxis "irgendetwas definiertes" passieren (der Quellcode hat kein definiertes Verhalten, der Maschinencode jedoch schon), was dann vermutlich auch einfach ein Wraparound ist?
Beide Programme haben einen Bug. Im einen Fall ist der Bug, dass durch inkorrekte Verwendung von unsigned der Code nicht das ausdrückt, was eigentlich ausgedrückt werden sollte. Der Code sagt, dass Wraparound das gewünschte, korrekte Verhalten ist. Und der Code tut dann eben genau das. Und das war aber halt doch nicht das gewünschte, korrekte Verhalten. Und ja, es ist in der Tat so, dass Wraparound in der Praxis fast immer ein Bug ist. Eben genau der Grund, wieso unsigned so selten wirklich der korrekte Typ is. Im anderen Fall ist der Bug, dass der verwendete signed Type nicht über die nötige Range verfügt, um alle potentiellen Werte abzudecken. Wenn man nicht garantieren kann, dass Grenzen nicht überschritten werden, dann hat man entweder was falsch gemacht oder muss zu einer Arbitrary-Precision-Integer-Library greifen.Jonathan hat geschrieben: ↑14.03.2023, 11:02 Aber egal. Worüber ich eigentlich nachgedacht habe ist, welches Programm bei einem unsigned Wraparound noch "wohldefiniert" oder viel eher "so wie es soll" funktioniert. Sagen wir mal du speicherst irgendwo die Anzahl der Partikel oder Levelgröße oder Punkte oder irgendwas, und dann kommt ein Wraparound, dann ist der Wert dieser Variablen ja Quatsch, egal wie wohldefiniert er sein mag. Das Programm wird höchstwahrscheinlich also trotzdem kurz darauf Quatsch machen und abstürzen, man ist also im Wesentlichen in der selben Lage wie bei UB durch Overflows. Sprich: Wenn man nicht garantieren kann, dass die Grenzen nicht überschritten werden (in dem man z.B. explizit nicht erlaube dass mehr Objekte erzeugt werden, oder mehr Punkte erreicht werden können etc.) kann eh nie irgendetwas gutes passieren.
Ja, in beiden Fällen hast du einen Bug der sich potentiell sogar auf die exakt gleiche Art und Weise äußert. Beachte aber, dass im zweiten Fall hier der Code zumindest richtig ausdrückt, dass Overflow nicht passieren darf. Das bedeutet, dass Tools wie z.B. UBSan den Overflow detektieren können. In der Variante die fälschlich unsigned verwendet, ist der Overflow für solche Tools nichtmal detektierbar, weil der Code ja explizit sagt, dass Wraparound gewünscht ist und somit überhaupt erst gar kein Overflow passiert…
Re: C - Differenzen von zwei unsigned int?
Danke für die Erklärung der Details dot!
Ich finde es schon sehr interessant (und wichtig) zu wissen, dass ein fundamentaler Unterschied besteht, zwischen einem Überlauf eines Signed und eines Unsigned. Läuft ein Unsigned über, fällt vielleicht das Ergebnis anders aus als erwartet, das Programm jedoch befindet sich auch weiterhin in einem wohldefinierten Zustand (so habe ich es jedenfalls jetzt verstanden).
Offensichtlich ganz im Kontrast zu einem "Überlauf" eines Signed! Das Programmverhalten ist danach UB! Und das ist ja eigentlich genau das, was man wirklich nie haben möchte.
Ich habe jetzt nach eurem Feedback die meisten der Variablen auf signed umgestellt.
Bei folgenden zwei Fällen bin ich mir jedoch nicht sicher, ob ich sie ebenfalls auf sigend umstellen kann (darf oder sollte) oder ob hier tatsächlich unsigned die bessere (nötige) Wahl ist.
1) Zum einen habe ich Variablen, deren Bits ich für "Fehlercodes" nutze. Das sind 8 und 16 Bit Variablen, bei welchen ich die Bits (auch das Höchstwertigste) mittels Bitoperatoren gezielt setze (meintest du das mit "Bitmasken" dot?).
Darf ich denn überhaupt das höchstwertige Bit eines signed Datentyps "manuell" setzen und in diesem Sinn verwenden? Oder sollte ich in einem solchen Szenario unsigned Datentypen verwenden?
Im zweiten Fall habe ich einen Buffer mit 8Bit Farbwerten. Also Farbkanäle die genau 1 Byte breit sind und hintereinander liegen.
Da die tatsächlichen Werte zwischen 0 und 255 liegen und ich die Werte auch mal als HEX Zahl ausgebe, habe ich jetzt intuitiv angenommen, ein uint8_t sei hier die beste Wahl.
Aber vielleicht wäre auch hier ein int8_t zu bevorzugen?
Gruss starcow
Ich finde es schon sehr interessant (und wichtig) zu wissen, dass ein fundamentaler Unterschied besteht, zwischen einem Überlauf eines Signed und eines Unsigned. Läuft ein Unsigned über, fällt vielleicht das Ergebnis anders aus als erwartet, das Programm jedoch befindet sich auch weiterhin in einem wohldefinierten Zustand (so habe ich es jedenfalls jetzt verstanden).
Offensichtlich ganz im Kontrast zu einem "Überlauf" eines Signed! Das Programmverhalten ist danach UB! Und das ist ja eigentlich genau das, was man wirklich nie haben möchte.
Ich habe jetzt nach eurem Feedback die meisten der Variablen auf signed umgestellt.
Bei folgenden zwei Fällen bin ich mir jedoch nicht sicher, ob ich sie ebenfalls auf sigend umstellen kann (darf oder sollte) oder ob hier tatsächlich unsigned die bessere (nötige) Wahl ist.
1) Zum einen habe ich Variablen, deren Bits ich für "Fehlercodes" nutze. Das sind 8 und 16 Bit Variablen, bei welchen ich die Bits (auch das Höchstwertigste) mittels Bitoperatoren gezielt setze (meintest du das mit "Bitmasken" dot?).
Darf ich denn überhaupt das höchstwertige Bit eines signed Datentyps "manuell" setzen und in diesem Sinn verwenden? Oder sollte ich in einem solchen Szenario unsigned Datentypen verwenden?
Code: Alles auswählen
enum
{
...
ERROR_FILE = 128
};
uint8_t error = 0;
error |= ERROR_FILE;
/* ist das auch erlaubt mit einem signed Datentyp? */
int8_t error = 0;
error |= ERROR_FILE;
Da die tatsächlichen Werte zwischen 0 und 255 liegen und ich die Werte auch mal als HEX Zahl ausgebe, habe ich jetzt intuitiv angenommen, ein uint8_t sei hier die beste Wahl.
Aber vielleicht wäre auch hier ein int8_t zu bevorzugen?
Gruss starcow
Re: C - Differenzen von zwei unsigned int?
Zu 1) In dem Fall ist es egal, ob du einen signed oder einen unsigned Typ verwendest. Das macht keinen Unterschied, da du letztendlich die Bits explizit setzt. Im Hintergrund passiert auch nicht magisch etwas, wenn du das Vorzeichen-Bit änderst. Ich tendiere hier eher zu einem vorzeichenlosen Typ, das lässt sich im Fall der Fälle etwas leichter interpretieren, wenn man sich den Wert anschaut. Was aber auch eher selten der Fall sein sollte ^^
Am einfachsten Stellst du dir in dem Fall die Bitmaske einfach als ein Array von Boolschen Werten vor. Dann merkt man recht intuitiv, dass das ganze mit Zahlen eigentlich nichts mehr zu tun hat.
Zu 2) Wenn du in dem Fall einen vorzeichenbehafteten 8-Bit-Integer verwenden würdest, könntest du deinen Wertebereich ja gar nicht mehr abbilden. Du brauchst Farbwerte von 0 bis 255, der uint8 bietet dir aber nur Werte von -127 bis 128. Letzterer ist also hier immer die falsche Wahl.
Letztendlich hängt die Wahl des korrekten Typen dann davon ab, was du mit den Werten machen möchtest und wie du das umsetzt. Wenn du zwei Bilder zu gleichen Teilen miteinander blenden möchtest, könntest du sie erst addieren und dann das Ergebnis durch Zwei dividieren. Bei einem uint8 läufst du damit natürlich aus dem Wertebereich raus. Du brauchst dann entweder einen Typen mit größerem Wertebereich oder trickst ein wenig rum, indem du vorher die Bilder durch zwei dividierst und dann halbierst. Damit bleibst du dann auch im Wertebereich. Was sinnvoll ist, hängt da von vielen Faktoren ab.
Wenn du aber zum Beispiel einfach nur eine Farbe durch eine andere ersetzen willst, dann kannst du super mit uint8 arbeiten. Da besteht keine Gefahr, dass du den Wertebereich verlässt.
Am einfachsten Stellst du dir in dem Fall die Bitmaske einfach als ein Array von Boolschen Werten vor. Dann merkt man recht intuitiv, dass das ganze mit Zahlen eigentlich nichts mehr zu tun hat.
Zu 2) Wenn du in dem Fall einen vorzeichenbehafteten 8-Bit-Integer verwenden würdest, könntest du deinen Wertebereich ja gar nicht mehr abbilden. Du brauchst Farbwerte von 0 bis 255, der uint8 bietet dir aber nur Werte von -127 bis 128. Letzterer ist also hier immer die falsche Wahl.
Letztendlich hängt die Wahl des korrekten Typen dann davon ab, was du mit den Werten machen möchtest und wie du das umsetzt. Wenn du zwei Bilder zu gleichen Teilen miteinander blenden möchtest, könntest du sie erst addieren und dann das Ergebnis durch Zwei dividieren. Bei einem uint8 läufst du damit natürlich aus dem Wertebereich raus. Du brauchst dann entweder einen Typen mit größerem Wertebereich oder trickst ein wenig rum, indem du vorher die Bilder durch zwei dividierst und dann halbierst. Damit bleibst du dann auch im Wertebereich. Was sinnvoll ist, hängt da von vielen Faktoren ab.
Wenn du aber zum Beispiel einfach nur eine Farbe durch eine andere ersetzen willst, dann kannst du super mit uint8 arbeiten. Da besteht keine Gefahr, dass du den Wertebereich verlässt.
- Schrompf
- Moderator
- Beiträge: 4593
- Registriert: 25.02.2009, 23:44
- Benutzertext: Lernt nur selten dazu
- Echter Name: Thomas Ziegenhagen
- Wohnort: Dresden
- Kontaktdaten:
Re: C - Differenzen von zwei unsigned int?
Vorsicht: einzelne Bits setzen heißt meist auch Bitshift. Und der ist für signed Ints nicht definiert, mindestens in eine Richtung.
Häuptling von Dreamworlds. Baut an was Neuem. Hilft nebenbei nur höchst selten an der Open Asset Import Library mit.
Re: C - Differenzen von zwei unsigned int?
Danke EyDu, das leuchtet ein! (Du meintest wohl -128 bis 127 - "Zweierkomplement", richtig?)EyDu hat geschrieben: ↑16.03.2023, 22:30 Zu 2) Wenn du in dem Fall einen vorzeichenbehafteten 8-Bit-Integer verwenden würdest, könntest du deinen Wertebereich ja gar nicht mehr abbilden. Du brauchst Farbwerte von 0 bis 255, der uint8 bietet dir aber nur Werte von -127 bis 128. Letzterer ist also hier immer die falsche Wahl.
Uff! X-) Das hatte ich jetzt nicht auf dem Schirm! Danke Schrompf!
- dot
- Establishment
- Beiträge: 1704
- Registriert: 06.03.2004, 18:10
- Echter Name: Michael Kenzel
- Kontaktdaten:
Re: C - Differenzen von zwei unsigned int?
Das Programmverhalten ist in beiden Fällen falsch denn Wraparound, egal wie er zustandekommt, ist so oder so eben das falsche Verhalten. Der Vorteil der signed Variante ist, dass es eben UB ist und daher von Tools wie UBsan detektiert werden kann. Mit unsigned drückt dein Code (fälschlicherweise) aus, dass Wraparound das gewünschte Verhalten wäre…
Klingt für mich so, als ob du einfach negative Werte für Fehlercodes verwenden willst? Wieso also nicht einfach -128 returnen und per ret < 0 checken ob alles geklappt hat, statt mit dem MSB rumfummeln?starcow hat geschrieben: ↑16.03.2023, 21:37 1) Zum einen habe ich Variablen, deren Bits ich für "Fehlercodes" nutze. Das sind 8 und 16 Bit Variablen, bei welchen ich die Bits (auch das Höchstwertigste) mittels Bitoperatoren gezielt setze (meintest du das mit "Bitmasken" dot?).
Darf ich denn überhaupt das höchstwertige Bit eines signed Datentyps "manuell" setzen und in diesem Sinn verwenden? Oder sollte ich in einem solchen Szenario unsigned Datentypen verwenden?
Ja, das ist, wo das Fehlen eines richtigen unsigned Typen in C und C++ leider anstrengend wird. Was du hier semantisch eigentlich willst, ist ein 8-bit Integer, der den Wertebereich [0,255] hat. Genau das gibt's in C und C++ aber eben leider genau nicht (weil unsigned ist ja eben kein Integer, sondern ein Restklassenring). In dem Fall muss man also leider Kompromisse eingehen und sich überlegen, was einem wichtiger ist: Dass der Wertebereich des Typs dem gewünschten entspricht oder dass das Verhalten des Typs dem gewünschten entspricht? Beides kann man in C und C++ leider nicht haben. Modernere Sprachen wie Rust versuchen zumindest, aus diesem Fehler zu lernen; dort sind Signedness und Wraparound unabhängige Eigenschaften und in allen Kombinationen erhältlich…starcow hat geschrieben: ↑16.03.2023, 21:37 Im zweiten Fall habe ich einen Buffer mit 8Bit Farbwerten. Also Farbkanäle die genau 1 Byte breit sind und hintereinander liegen.
Da die tatsächlichen Werte zwischen 0 und 255 liegen und ich die Werte auch mal als HEX Zahl ausgebe, habe ich jetzt intuitiv angenommen, ein uint8_t sei hier die beste Wahl.
Aber vielleicht wäre auch hier ein int8_t zu bevorzugen?
Re: C - Differenzen von zwei unsigned int?
Stimmt, das ginge natürlich auch (dann mit Verzicht auf Bitshift).
Gut zu wissen, danke für die Ausführung dot!Ja, das ist, wo das Fehlen eines richtigen unsigned Typen in C und C++ leider anstrengend wird. Was du hier semantisch eigentlich willst, ist ein 8-bit Integer, der den Wertebereich [0,255] hat. Genau das gibt's in C und C++ aber eben leider genau nicht (weil unsigned ist ja eben kein Integer, sondern ein Restklassenring). In dem Fall muss man also leider Kompromisse eingehen und sich überlegen, was einem wichtiger ist: Dass der Wertebereich des Typs dem gewünschten entspricht oder dass das Verhalten des Typs dem gewünschten entspricht? Beides kann man in C und C++ leider nicht haben. Modernere Sprachen wie Rust versuchen zumindest, aus diesem Fehler zu lernen; dort sind Signedness und Wraparound unabhängige Eigenschaften und in allen Kombinationen erhältlich…