C - Differenzen von zwei unsigned int?

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Antworten
Benutzeravatar
starcow
Establishment
Beiträge: 560
Registriert: 23.04.2003, 17:42
Echter Name: Mischa Schaub
Kontaktdaten:

C - Differenzen von zwei unsigned int?

Beitrag von starcow »

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?
Freelancer 3D- und 2D-Grafik
mischaschaub.com
Benutzeravatar
Krishty
Establishment
Beiträge: 8316
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: C - Differenzen von zwei unsigned int?

Beitrag von Krishty »

starcow hat geschrieben: 12.03.2023, 22:19Anschliessen 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.
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 :)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Schrompf
Moderator
Beiträge: 5040
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas
Wohnort: Dresden
Kontaktdaten:

Re: C - Differenzen von zwei unsigned int?

Beitrag von Schrompf »

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.
Benutzeravatar
Jonathan
Establishment
Beiträge: 2543
Registriert: 04.08.2004, 20:06
Kontaktdaten:

Re: C - Differenzen von zwei unsigned int?

Beitrag von Jonathan »

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/
Benutzeravatar
Krishty
Establishment
Beiträge: 8316
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: C - Differenzen von zwei unsigned int?

Beitrag von Krishty »

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.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
dot
Establishment
Beiträge: 1745
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: C - Differenzen von zwei unsigned int?

Beitrag von dot »

starcow hat geschrieben: 12.03.2023, 22:19Jede Variable für sich kann natürlich nur 0 oder positiv sein, die Differenz jedoch kann bekanntermassen ja negativ ausfallen.
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).
Benutzeravatar
Jonathan
Establishment
Beiträge: 2543
Registriert: 04.08.2004, 20:06
Kontaktdaten:

Re: C - Differenzen von zwei unsigned int?

Beitrag von Jonathan »

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".
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
Benutzeravatar
starcow
Establishment
Beiträge: 560
Registriert: 23.04.2003, 17:42
Echter Name: Mischa Schaub
Kontaktdaten:

Re: C - Differenzen von zwei unsigned int?

Beitrag von starcow »

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
Freelancer 3D- und 2D-Grafik
mischaschaub.com
Benutzeravatar
dot
Establishment
Beiträge: 1745
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: C - Differenzen von zwei unsigned int?

Beitrag von dot »

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".
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…
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
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.

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.
Benutzeravatar
Schrompf
Moderator
Beiträge: 5040
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas
Wohnort: Dresden
Kontaktdaten:

Re: C - Differenzen von zwei unsigned int?

Beitrag von Schrompf »

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.
Benutzeravatar
dot
Establishment
Beiträge: 1745
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: C - Differenzen von zwei unsigned int?

Beitrag von dot »

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.
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.
Benutzeravatar
Jonathan
Establishment
Beiträge: 2543
Registriert: 04.08.2004, 20:06
Kontaktdaten:

Re: C - Differenzen von zwei unsigned int?

Beitrag von Jonathan »

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…
Danke für die Erklärung. UB war mir im Wesentlichen bekannt, habs aber wegen der Details trotzdem gerne nochmal gelesen.

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/
Benutzeravatar
dot
Establishment
Beiträge: 1745
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: C - Differenzen von zwei unsigned int?

Beitrag von dot »

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?
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 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.
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.

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…
Benutzeravatar
starcow
Establishment
Beiträge: 560
Registriert: 23.04.2003, 17:42
Echter Name: Mischa Schaub
Kontaktdaten:

Re: C - Differenzen von zwei unsigned int?

Beitrag von starcow »

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?

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;
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
Freelancer 3D- und 2D-Grafik
mischaschaub.com
EyDu
Establishment
Beiträge: 102
Registriert: 24.08.2002, 18:52
Wohnort: Berlin
Kontaktdaten:

Re: C - Differenzen von zwei unsigned int?

Beitrag von EyDu »

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.
Benutzeravatar
Schrompf
Moderator
Beiträge: 5040
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas
Wohnort: Dresden
Kontaktdaten:

Re: C - Differenzen von zwei unsigned int?

Beitrag von Schrompf »

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.
Benutzeravatar
starcow
Establishment
Beiträge: 560
Registriert: 23.04.2003, 17:42
Echter Name: Mischa Schaub
Kontaktdaten:

Re: C - Differenzen von zwei unsigned int?

Beitrag von starcow »

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.
Danke EyDu, das leuchtet ein! (Du meintest wohl -128 bis 127 - "Zweierkomplement", richtig?)
Schrompf hat geschrieben: 16.03.2023, 22:38 Vorsicht: einzelne Bits setzen heißt meist auch Bitshift. Und der ist für signed Ints nicht definiert, mindestens in eine Richtung.
Uff! X-) Das hatte ich jetzt nicht auf dem Schirm! Danke Schrompf!
Freelancer 3D- und 2D-Grafik
mischaschaub.com
Benutzeravatar
dot
Establishment
Beiträge: 1745
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: C - Differenzen von zwei unsigned int?

Beitrag von dot »

starcow hat geschrieben: 16.03.2023, 21:37 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.
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…
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?
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 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?
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…
Benutzeravatar
starcow
Establishment
Beiträge: 560
Registriert: 23.04.2003, 17:42
Echter Name: Mischa Schaub
Kontaktdaten:

Re: C - Differenzen von zwei unsigned int?

Beitrag von starcow »

dot hat geschrieben: 22.03.2023, 23:10 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?
Stimmt, das ginge natürlich auch (dann mit Verzicht auf Bitshift).
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…
Gut zu wissen, danke für die Ausführung dot!
Freelancer 3D- und 2D-Grafik
mischaschaub.com
Benutzeravatar
starcow
Establishment
Beiträge: 560
Registriert: 23.04.2003, 17:42
Echter Name: Mischa Schaub
Kontaktdaten:

Re: C - Differenzen von zwei unsigned int?

Beitrag von starcow »

dot hat geschrieben: 13.03.2023, 19:56 Overflow (integer or otherwise) ist UB.
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).
Foglich kann bei einer unsigned Variable ein Overflow eigentlich gar nicht zustande kommen. Lediglich kann ein Wraparound auftreten, der zwar möglicherweise ungewollt, jedoch NICHT UB ist.
Freelancer 3D- und 2D-Grafik
mischaschaub.com
udok
Beiträge: 40
Registriert: 01.02.2022, 17:34

Re: C - Differenzen von zwei unsigned int?

Beitrag von udok »

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...
Benutzeravatar
dot
Establishment
Beiträge: 1745
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: C - Differenzen von zwei unsigned int?

Beitrag von dot »

udok hat geschrieben: 29.04.2023, 09:48Du kannst also INT und UINT kunterbunt mischen und zuweisen, der Unterschied tritt nur bei einer Abfragen auf < 0 auf!
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…
udok hat geschrieben: 29.04.2023, 09:48Wenn der GCC Integer Code entdeckt, der überläuft, dann ist das seit neuesten wirklich UB, früher war es einfach Wraparound.
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.
udok hat geschrieben: 29.04.2023, 09:48Das bringt natürlich in der Praxis keine Performance (der Code steht ja nicht umsonst da), sondern einfach nur massiven Ärger.
Doch, das bringt in der Praxis Performance.
udok hat geschrieben: 29.04.2023, 09:48Diese 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?).
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]
udok hat geschrieben: 29.04.2023, 09:48Aber was soll die Anwendung dann mit der Boing 737 machen, wenn ein INT Overflow auftritt? Abstürzen und neu booten?
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…
udok
Beiträge: 40
Registriert: 01.02.2022, 17:34

Re: C - Differenzen von zwei unsigned int?

Beitrag von udok »

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.
Benutzeravatar
Krishty
Establishment
Beiträge: 8316
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: C - Differenzen von zwei unsigned int?

Beitrag von Krishty »

udok hat geschrieben: 02.05.2023, 21:04Ich glaube du verwechselst C und C++.
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?!
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
dot
Establishment
Beiträge: 1745
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: C - Differenzen von zwei unsigned int?

Beitrag von dot »

udok hat geschrieben: 02.05.2023, 21:04Ich glaube du verwechselst C und C++.
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.
udok hat geschrieben: 02.05.2023, 21:04C war schon immer ganz nahe dran an der Hardware […]
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…
udok hat geschrieben: 02.05.2023, 21:04[…] UB bedeutet das der Compiler doch bitte für die Platform etwas Sinnvolles machen soll.
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.
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 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.

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.
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.
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: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.
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…
Benutzeravatar
dot
Establishment
Beiträge: 1745
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: C - Differenzen von zwei unsigned int?

Beitrag von dot »

Krishty hat geschrieben: 02.05.2023, 21:11Wie sieht das mit Rust und Zig aus?
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
Benutzeravatar
starcow
Establishment
Beiträge: 560
Registriert: 23.04.2003, 17:42
Echter Name: Mischa Schaub
Kontaktdaten:

Re: C - Differenzen von zwei unsigned int?

Beitrag von starcow »

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.
Freelancer 3D- und 2D-Grafik
mischaschaub.com
Benutzeravatar
starcow
Establishment
Beiträge: 560
Registriert: 23.04.2003, 17:42
Echter Name: Mischa Schaub
Kontaktdaten:

Re: C - Differenzen von zwei unsigned int?

Beitrag von starcow »

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:
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.
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).
Da ich ja vor der Zuweisung nach uint8_t wieder durch zweil Teile, kann hier demnach ja eigentlich nichts schiefgehen.
Freelancer 3D- und 2D-Grafik
mischaschaub.com
Benutzeravatar
Krishty
Establishment
Beiträge: 8316
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: C - Differenzen von zwei unsigned int?

Beitrag von Krishty »

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
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
starcow
Establishment
Beiträge: 560
Registriert: 23.04.2003, 17:42
Echter Name: Mischa Schaub
Kontaktdaten:

Re: C - Differenzen von zwei unsigned int?

Beitrag von starcow »

Ok, Danke Krishty! :-)
Freelancer 3D- und 2D-Grafik
mischaschaub.com
Antworten