C - Differenzen von zwei unsigned int?
- starcow
- Establishment
- Beiträge: 560
- Registriert: 23.04.2003, 17:42
- Echter Name: Mischa Schaub
- Kontaktdaten:
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: 8316
- 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: 5047
- Registriert: 25.02.2009, 23:44
- Benutzertext: Lernt nur selten dazu
- Echter Name: Thomas
- 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.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
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!
https://jonathank.de/games/
https://jonathank.de/games/
- Krishty
- Establishment
- Beiträge: 8316
- 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: 1745
- 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!
https://jonathank.de/games/
https://jonathank.de/games/
- starcow
- Establishment
- Beiträge: 560
- Registriert: 23.04.2003, 17:42
- Echter Name: Mischa Schaub
- Kontaktdaten:
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: 1745
- 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: 5047
- Registriert: 25.02.2009, 23:44
- Benutzertext: Lernt nur selten dazu
- Echter Name: Thomas
- 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.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
- dot
- Establishment
- Beiträge: 1745
- 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!
https://jonathank.de/games/
https://jonathank.de/games/
- dot
- Establishment
- Beiträge: 1745
- 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…
- starcow
- Establishment
- Beiträge: 560
- Registriert: 23.04.2003, 17:42
- Echter Name: Mischa Schaub
- Kontaktdaten:
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: 5047
- Registriert: 25.02.2009, 23:44
- Benutzertext: Lernt nur selten dazu
- Echter Name: Thomas
- 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.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
- starcow
- Establishment
- Beiträge: 560
- Registriert: 23.04.2003, 17:42
- Echter Name: Mischa Schaub
- Kontaktdaten:
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: 1745
- 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?
- starcow
- Establishment
- Beiträge: 560
- Registriert: 23.04.2003, 17:42
- Echter Name: Mischa Schaub
- Kontaktdaten:
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…
- starcow
- Establishment
- Beiträge: 560
- Registriert: 23.04.2003, 17:42
- Echter Name: Mischa Schaub
- Kontaktdaten:
Re: C - Differenzen von zwei unsigned int?
Sorry, das ich hier nochmals nachfrage dot. Doch ich versuche gerade Code aus einem Beispielprogramm zu verstehen und realisiere, dass ich dich zuvor möglicherweise falsch verstanden hatte.
Einige deiner Aussagen zusammengefasst:
Code: Alles auswählen
unsigned int a = UINT_MAX + 1; // a == 0. Definiertes Verhalten. KEIN Overflow sondern, Wraparound.
int b = INT_MAX + 1; // Undefiniertes Verhalten durch Overflow von b. (Ab jetzt kann theoretisch alles Mögliche passieren).
Re: C - Differenzen von zwei unsigned int?
Bei einer unsigned Variable ist ein Overflow ein Wraparound (UINT_MAX+1 wird zu 0).
Bei einer signed Variable ist ein Overflow laut C-Standard UB und kann theoretisch einen Interrupt auslösen.
Alle mir bekannten CPUs verwenden heute 2-er Kompliment für UINT und für INT Berechnungen, mit Ausnahme von DSPs
(Stichwort Guard Bits und Sättigungsarithmetik). UINT und INT haben also einen Wraparound von der grössten darstellbaren Zahl zur kleinsten.
In der PC Praxis wird nie ein Interrupt ausgelöst. Das Bitmuster für INT und für UINT ist identisch, nur die Interpretation ist anders.
Du kannst also INT und UINT kunterbunt mischen und zuweisen, der Unterschied tritt nur bei einer Abfragen auf < 0 auf!
Vor einigen Jahren war das ganz entspannt. Kaum einer wusste, das INT Overflow UB ist.
Dann kamen die GCC Entwickler auf die Idee, dass man UB ja so interpretieren könnte,
dass der betroffenen Code nicht ausgeführt werden muss, und wegoptimiert werden kann.
Wenn der GCC Integer Code entdeckt, der überläuft, dann ist das seit neuesten wirklich UB, früher war es einfach Wraparound.
Das bringt natürlich in der Praxis keine Performance (der Code steht ja nicht umsonst da), sondern einfach nur massiven Ärger.
Diese Entscheidung des C-Standards macht Sinn, weil man auch CPUs unterstützen will, die Sättigungsarithmetik haben, oder Mainframes,
die eventuell einen Interrupt auslösen bei einem INT Overflow (Banken, Bahn, Luftfahrt?).
Aber was soll die Anwendung dann mit der Boing 737 machen, wenn ein INT Overflow auftritt? Abstürzen und neu booten?
In der Zeit vor 64 Bit hatte der UINT Type den wichtigen Vorteil, dass der Wertebereich doppelt so gross war.
Ein 64 Bit Typ kann praktisch nicht mehr überlaufen.
Wegen dieser Bequemlichkeit schieben Programme heute die meiste Zeit Null Bytes zwischen der CPU und dem Speicher hin und her.
Wenn du definiertes Verhalten bei Überlauf oder Rundungsfehlern haben möchtest, dann kannst du die FPU verwenden.
Da kannst du genau einstellen was alles einen Interrupt auslösen soll...
Bei einer signed Variable ist ein Overflow laut C-Standard UB und kann theoretisch einen Interrupt auslösen.
Alle mir bekannten CPUs verwenden heute 2-er Kompliment für UINT und für INT Berechnungen, mit Ausnahme von DSPs
(Stichwort Guard Bits und Sättigungsarithmetik). UINT und INT haben also einen Wraparound von der grössten darstellbaren Zahl zur kleinsten.
In der PC Praxis wird nie ein Interrupt ausgelöst. Das Bitmuster für INT und für UINT ist identisch, nur die Interpretation ist anders.
Du kannst also INT und UINT kunterbunt mischen und zuweisen, der Unterschied tritt nur bei einer Abfragen auf < 0 auf!
Vor einigen Jahren war das ganz entspannt. Kaum einer wusste, das INT Overflow UB ist.
Dann kamen die GCC Entwickler auf die Idee, dass man UB ja so interpretieren könnte,
dass der betroffenen Code nicht ausgeführt werden muss, und wegoptimiert werden kann.
Wenn der GCC Integer Code entdeckt, der überläuft, dann ist das seit neuesten wirklich UB, früher war es einfach Wraparound.
Das bringt natürlich in der Praxis keine Performance (der Code steht ja nicht umsonst da), sondern einfach nur massiven Ärger.
Diese Entscheidung des C-Standards macht Sinn, weil man auch CPUs unterstützen will, die Sättigungsarithmetik haben, oder Mainframes,
die eventuell einen Interrupt auslösen bei einem INT Overflow (Banken, Bahn, Luftfahrt?).
Aber was soll die Anwendung dann mit der Boing 737 machen, wenn ein INT Overflow auftritt? Abstürzen und neu booten?
In der Zeit vor 64 Bit hatte der UINT Type den wichtigen Vorteil, dass der Wertebereich doppelt so gross war.
Ein 64 Bit Typ kann praktisch nicht mehr überlaufen.
Wegen dieser Bequemlichkeit schieben Programme heute die meiste Zeit Null Bytes zwischen der CPU und dem Speicher hin und her.
Wenn du definiertes Verhalten bei Überlauf oder Rundungsfehlern haben möchtest, dann kannst du die FPU verwenden.
Da kannst du genau einstellen was alles einen Interrupt auslösen soll...
- dot
- Establishment
- Beiträge: 1745
- Registriert: 06.03.2004, 18:10
- Echter Name: Michael Kenzel
- Kontaktdaten:
Re: C - Differenzen von zwei unsigned int?
signed und unsigned Integer "kunterbunt mischen" ist eine wunderbar schlechte Idee. Die Tatsache dass C das überhaupt erlaubt, ist einer der größten Designfehler in C…
Es ist nicht nur seit neuestem UB, es war immer UB. Die Tatsache dass Compiler davon Gebrauch machen, um besseren Code zu generieren, ist auch alles andere als Neu. Code der dadurch kaputtgeht war immer schon kaputt.
Doch, das bringt in der Praxis Performance.
Nur dass solche CPUs heutzutage effektiv nicht mehr existieren. C++20 und nun auch C23 haben daher mittlerweile Zweierkomplement zur Darstellung von signed Integern standardisiert. Overflow ist aufgrund der weiter oben in diesem Thread bereits diskutierten Gründe (problematische Semantik, ohne wirkliche Vorteile, Verlust von Möglichkeiten zur Optimierung) aber weiterhin UB. [p0907R1]
Wie ebenfalls bereits weiter oben im Thread diskutiert: Wraparound behebt dieses Problem nicht. Dein Code hat so oder so einen Bug und wird sich falsch verhalten. Wie bereits angedeutet, meist sogar auf die exakt selbe Art und Weise falsch. signed Overflow ist zumindest UB und kann daher mit entsprechenden Tools (z.B. UBSan) detektiert werden…
Bei derart kritischen Anwendungen wird zudem mit Software- und Hardwareredundanz gearbeitet. Es gibt mehr als einen Flugcomputer, die Computer sind verschiedener Bauaurt und führen von verschiedenen Teams unabhängig entwickelte Software aus. Die Wahrscheinlichkeit dass mehrere unabhängig entwickelte Programme auf allen Flugcomputern zum selben Zeitpunkt wegen einem Integeroverflow abstürzen ist dementsprechend sehr gering…
Re: C - Differenzen von zwei unsigned int?
Ich glaube du verwechselst C und C++.
C war schon immer ganz nahe dran an der Hardware, und UB bedeutet das der Compiler doch bitte für die Platform etwas Sinnvolles machen soll.
Eine CPU mit 2-er Kompliment soll da einfach einen Wraparound machen. Das erwarte ich mir jedenfalls, weil es das nächstliegende ist, und es am wenigsten Aufwand von Seite des Compilers und von Seiten der Hardware macht. Und eine CPU mit Sättigungsarithmethik soll einfach sättigen,
Das macht Sinn so, auch wenn das in beiden Fällen UB ist.
Jedenfalls brauche ich eine klare und ganz einfache Vorstellung, wie integer Addition auf meiner Platform funktioniert.
Und die Erklärung von so fundamentalen Sachen sollte in einem Satz möglich sein.
Alles andere führt zu Spezialfällen, Fehlern und Kopfschmerzen.
Wenn ich eine Behandlung aller möglichen und unmöglichen Fehlerquellen will - dann programmiere ich nicht C, sondern C++.
Da kann ich mir eine 10000 Zeilen C++ Klasse bauen, die mir die HW Gemeinheiten versteckt, und den Overflow durch Multi-Precision Arithmetik abfängt, oder ein Email an Intel schickt mit der Bitte den 32 Bit Datentyp doch endlich sterben zu lassen.
Was meiner Meinung ganz schlecht ist: das ein Compiler den ungeschriebenen C Standard einer Platform missachtet, und bestehenden Code zerstört für praktisch keinen Nutzen. Die Tools finden sowieso in 99/100 Fällen nichts, außer der trivialen Erkenntnis das ein Integer überlaufen kann.
Meinetwegen kann der Compiler ja eine Warnung rausschmeißen, wenn er meint dass ein Integer Overflow auftritt, aber mehr braucht es nicht.
C war schon immer ganz nahe dran an der Hardware, und UB bedeutet das der Compiler doch bitte für die Platform etwas Sinnvolles machen soll.
Eine CPU mit 2-er Kompliment soll da einfach einen Wraparound machen. Das erwarte ich mir jedenfalls, weil es das nächstliegende ist, und es am wenigsten Aufwand von Seite des Compilers und von Seiten der Hardware macht. Und eine CPU mit Sättigungsarithmethik soll einfach sättigen,
Das macht Sinn so, auch wenn das in beiden Fällen UB ist.
Jedenfalls brauche ich eine klare und ganz einfache Vorstellung, wie integer Addition auf meiner Platform funktioniert.
Und die Erklärung von so fundamentalen Sachen sollte in einem Satz möglich sein.
Alles andere führt zu Spezialfällen, Fehlern und Kopfschmerzen.
Wenn ich eine Behandlung aller möglichen und unmöglichen Fehlerquellen will - dann programmiere ich nicht C, sondern C++.
Da kann ich mir eine 10000 Zeilen C++ Klasse bauen, die mir die HW Gemeinheiten versteckt, und den Overflow durch Multi-Precision Arithmetik abfängt, oder ein Email an Intel schickt mit der Bitte den 32 Bit Datentyp doch endlich sterben zu lassen.
Was meiner Meinung ganz schlecht ist: das ein Compiler den ungeschriebenen C Standard einer Platform missachtet, und bestehenden Code zerstört für praktisch keinen Nutzen. Die Tools finden sowieso in 99/100 Fällen nichts, außer der trivialen Erkenntnis das ein Integer überlaufen kann.
Meinetwegen kann der Compiler ja eine Warnung rausschmeißen, wenn er meint dass ein Integer Overflow auftritt, aber mehr braucht es nicht.
- Krishty
- Establishment
- Beiträge: 8316
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: C - Differenzen von zwei unsigned int?
Ich glaube, du verwechselst C und Assembler.
Mit deiner Fan Fiction stehst du aber nicht allein da. Viele wünschen sich, es wäre so. Wie sieht das mit Rust und Zig aus? Es kann ja nicht sein, dass dauernd alle nur ihre Fantasie über Sprachen und Compiler verteilen, aber niemand sowas tatsächlich implementiert?!
- dot
- Establishment
- Beiträge: 1745
- Registriert: 06.03.2004, 18:10
- Echter Name: Michael Kenzel
- Kontaktdaten:
Re: C - Differenzen von zwei unsigned int?
Nope. Da gibt's nicht wirklich was zu verwechseln, C++ hat den ganzen Kram 1:1 von C geerbt, funktioniert in beiden Sprachen praktisch exakt gleich.
Die Idee, dass C irgendwie besonders nahe an der Hardware ist, ist ein weit verbreiteter Aberglaube. C ist kein Stück näher an der Hardware dran als andere Sprachen wie z.B. C++, Rust, Zig, etc. es auch sind. Und insbesondere auf modernen Prozessoren ist das ein ziemlich schönes Stück von der Hardware entfernt. Ein optimierender Compiler ist genauso absolut essenziell um C effizient auf moderne Hardware abzubilden, wie für alle anderen dieser Sprachen auch. Eine naive "1:1" Abbildung (soweit es überhaupt sinnvoll ist, von so etwas zu sprechen) von C auf moderne Hardware ist so ungefähr das, was du in GCC oder Clang mit -O0 bekommst. Ich gehe davon aus, dass du vertraut damit bist, wieviel langsamer das auf einer modernen Maschine generell ist, als optimierter Code. C scheint vielleicht näher an der Hardware, weil es nur über sehr rudimentäre Möglichkeiten zur Abstraktion verfügt. Aber nur weil man in C limitiert darin ist, wie weit man sich von der Hardware entfernen kann, heißt nicht, dass man in anderen Sprachen nicht genau gleich nahe dran sein kann, wenn man denn will oder muss. Man kann nur zusätzlich auch viele Dinge, die man in C nicht kann. C deckt lediglich ein geringeres Spektrum ab, der Nullpunkt ist aber exakt der selbe. Wenn du dir eine beliebige moderne Toolchain wie GCC, Clang oder MSVC mal ansiehst, wirst du feststellen, dass es sich bei den "C Compilern" dort in Wirklichkeit nur um C++ Compiler mit einem C Modus handelt. Sowohl C als auch C++ laufen effektiv also sogar durch exakt den selben Compiler…
Nope. Du verwechselst Undefined Behavior (UB) mit Implementation-defined Behavior (IB). Integeroverflow ist UB, nicht IB. UB bedeutet, dass der Compiler eben kein bestimmtes Verhalten garantieren muss. UB repräsentiert die Abwesenheit von jeglichem Behavior. UB erlaubt es dem Compiler, einzugrenzen, welche Fälle der generierte Maschinencode behandeln muss und ist auf diese Weise für die Generierung von effizientem Maschinencode von essenzieller Bedeutung. Was im Falle von UB passiert, hängt nicht davon ab, wie genau der Compiler das UB implementiert, sondern davon, wie der Compiler alle anderen Fälle, die nicht UB sind, implementiert. UB ist nicht nicht etwas, wo der Compiler entscheidet was er in dem Fall machen will. UB ist für Compiler im Allgemeinen nicht detektierbar. Ob, wie, wann, wo und warum UB auftritt ist im Allgemeinen erst zur Laufzeit bekannt. Das ist genau der Grund wieso UB überhaupt erst existiert. Eine Sprache hat entweder UB oder muss überall Laufzeitchecks generieren. Die Designer von C und C++ haben sich für Ersteres entschieden.
Was du dir erwartest und was deiner Meinung nach Sinn macht, ist irrelevant. Die C und C++ Spezifikationen sagen ganz klar und eindeutig was Anderes. Und Compiler implementieren die C und C++ Spezifikation, nicht was du dir erwartest oder als Sinnvoll erachtest.udok hat geschrieben: ↑02.05.2023, 21:04Eine CPU mit 2-er Kompliment soll da einfach einen Wraparound machen. Das erwarte ich mir jedenfalls, weil es das nächstliegende ist, und es am wenigsten Aufwand von Seite des Compilers und von Seiten der Hardware macht. Und eine CPU mit Sättigungsarithmethik soll einfach sättigen,
Das macht Sinn so, auch wenn das in beiden Fällen UB ist.
Was die CPU machen würde ist auch irrelevant. Wenn du C schreibst, dann schreibst du nicht Code für die CPU sondern Code für den C Compiler. Und der C Compiler interpretiert die Bedeutung deines Programmes basierend auf den Regeln der Programmiersprache C, nicht basierend auf den Regeln der Zielarchitektur.
Die Erklärung ist auch ganz einfach: Es ist Aufgabe des Programmierers, Datentypen entsprechend so zu wählen und Code entsprechend so zu schreiben, dass Integer nicht überlaufen.udok hat geschrieben: ↑02.05.2023, 21:04Jedenfalls brauche ich eine klare und ganz einfache Vorstellung, wie integer Addition auf meiner Platform funktioniert.
Und die Erklärung von so fundamentalen Sachen sollte in einem Satz möglich sein.
Alles andere führt zu Spezialfällen, Fehlern und Kopfschmerzen.
Es gibt keinen ungeschriebenen C Standard. Es gibt einen geschriebenen und in dem steht ganz klar, dass Integeroverflow UB ist. Eine Kopie des aktuellen Draft findest du hier, falls du dich selbst davon überzeugen willst. Ich weiß ja nicht, von welchen Tools du da sprichst, die offenbar in 99/100 Fällen nichts finden, aber Tools wie UBSan sollten definitiv detektieren, wenn wo was überläuft. Der Compiler kann im Allgemeinen nicht warnen, wenn ein Überlauf auftritt, weil der Compiler das im Allgemeinen gar nicht erst wissen kann. Um das im Allgemeinen zu wissen, müsste der Compiler das Halteproblem lösen, was rein mathematisch unmöglich ist…udok hat geschrieben: ↑02.05.2023, 21:04Was meiner Meinung ganz schlecht ist: das ein Compiler den ungeschriebenen C Standard einer Platform missachtet, und bestehenden Code zerstört für praktisch keinen Nutzen. Die Tools finden sowieso in 99/100 Fällen nichts, außer der trivialen Erkenntnis das ein Integer überlaufen kann.
Meinetwegen kann der Compiler ja eine Warnung rausschmeißen, wenn er meint dass ein Integer Overflow auftritt, aber mehr braucht es nicht.
- dot
- Establishment
- Beiträge: 1745
- Registriert: 06.03.2004, 18:10
- Echter Name: Michael Kenzel
- Kontaktdaten:
Re: C - Differenzen von zwei unsigned int?
Zig: UB (macht Sinn, ist ja stark Performanceorientiert)
Rust: check + trap in Debug, wrap in Release (unschön weil weder wirklich safe noch maximal effizient, aus Performancegründen aber als notwendiger Kompromiss erachtet)
+ in beiden Sprachen gibt es auch Wege, um je nach Situation explizit bestimmtes Verhalten zu verlangen
afaik
- starcow
- Establishment
- Beiträge: 560
- Registriert: 23.04.2003, 17:42
- Echter Name: Mischa Schaub
- Kontaktdaten:
Re: C - Differenzen von zwei unsigned int?
Danke für eure Beiträge, das war wirklich spannend zu lesen! Auch wenn ich (von der Frage mal abgesehen) ja leider nichts zur Diskussion beitragen kann.
- starcow
- Establishment
- Beiträge: 560
- Registriert: 23.04.2003, 17:42
- Echter Name: Mischa Schaub
- Kontaktdaten:
Re: C - Differenzen von zwei unsigned int?
Ich habe mir Zwecks "Auffrischung" den Thread hier nochmals durchgelesen (danke nochmals an der Stelle für die sehr vielen, wertvollen Details von euch), wobei mir noch folgendes Detail aufgefallen ist:
Da ich ja vor der Zuweisung nach uint8_t wieder durch zweil Teile, kann hier demnach ja eigentlich nichts schiefgehen.
Werden zwei uint8_t Werte miteinander addiert und dann anschliessend durch zwei definiert, kann unmöglich ein Wraparound auftreten, da _Bool, char, unsigned char, short und unsigned short vor jeder arithmetischen Operation zuerst nach int (oder nach uint - falls in int nicht darstellbar) promoted werden (Ganzzahlerweiterung).EyDu hat geschrieben: ↑16.03.2023, 22:30 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.
Da ich ja vor der Zuweisung nach uint8_t wieder durch zweil Teile, kann hier demnach ja eigentlich nichts schiefgehen.
- Krishty
- Establishment
- Beiträge: 8316
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: C - Differenzen von zwei unsigned int?
Has du korrekt verstanden. Das hier rechnet in 32 Bits (oder wie groß auch immer int bei dir ist):
uint8_t a = ...
uint8_t b = ...
uint8_t c = (a + b) / 2; // Promotion zu int bei Addition; Truncation zu uint8_t bei Initialisierung von c
Das hingegen rechnet in 8 Bits und funktioniert nicht mit hellen Pixeln:
uint8_t a = ...
uint8_t b = ...
uint8_t c = a;
c += b; // Überlauf
c /= 2; // falsches Ergebnis
uint8_t a = ...
uint8_t b = ...
uint8_t c = (a + b) / 2; // Promotion zu int bei Addition; Truncation zu uint8_t bei Initialisierung von c
Das hingegen rechnet in 8 Bits und funktioniert nicht mit hellen Pixeln:
uint8_t a = ...
uint8_t b = ...
uint8_t c = a;
c += b; // Überlauf
c /= 2; // falsches Ergebnis
- starcow
- Establishment
- Beiträge: 560
- Registriert: 23.04.2003, 17:42
- Echter Name: Mischa Schaub
- Kontaktdaten:
Re: C - Differenzen von zwei unsigned int?
Ok, Danke Krishty! :-)