Zitat von
Lehona
[...]
Nein, das sollte keine Kritik an dir sein, sondern an deinem Ausgangsmaterial! Ich hatte, was mich jetzt sehr verwundert, den linken und den rechten Operanden der Verschiebeoperation miteinander verwechselt. Anscheinend war ich echt übermüdet, sorry. Dann ist dein Einwand absolut angebracht. Den Mist mache ich mal lieber weg, bevor er für weitere Konfusion sorgt. Danke für deinen freundlichen Hinweis! Und danke auch für das Zitat aus dem C99-Standard.
Edit:
Ich glaube aber, dass unter C sowie C++ bei vorzeichenlosen Typen (wenn sie es nach den Promotions noch sind, s.u.) Overflows sicher sind (in dem Sinne, dass definierte Werte herauskommen, was nicht heißen soll, dass man solche Situationen nicht manchmal abfangen müsste). Jedenfalls kenne ich einigen Code, welcher darauf aufbaut und habe, auch bei mir selbst, noch nie erlebt, dass es nicht klappt (was natürlich nichts heißen muss). Kann das gerne später mal aus dem Standard heraussuchen, aber den hast du wohl selbst, wie es aussieht. Bei vorzeichenbehafteten Typen habe ich beim Refactoring in ähnlichen Situationen immer vorsichtshalber auf unsigned abgeändert.
C99 §6.2.5 Absatz 9:
The range of nonnegative values of a signed integer type is a subrange of the corresponding unsigned integer type, and the representation of the same value in each type is the same.31)
A computation involving unsigned operands can never overflow, because a result that cannot be represented by the resulting unsigned integer type is reduced modulo the number that is one greater than the largest value that can be represented by the resulting type.
31) The same representation and alignment requirements are meant to imply interchangeability as arguments to functions, return values from functions, and members of unions.
Für mich ist das bei unsigned das natürliche Verhalten von üblicher Hardware, indem die nicht darstellbaren Stellen fehlen und alles andere bleibt (wer schon mal mit Flipflops Register gebastelt hat, weiß, was ich meine). So kann ich es mir am besten merken. Aber der Standard ist korrekt, indem er sich von der technischen Repräsentation unabhängig macht und das rein numerisch fasst, weshalb das "natürliche Verhalten" nur als eine Eselsbrücke gelten darf, denn theoretisch könnte jemand auf die Idee kommen und UINT_MAX auf einen unnatürlichen Wert legen. Dann greift die Definition immer noch, und meine ist falsch. Sicher wurde die Definition so gewählt, damit sie dem natürlichen Verhalten real existierender Hardware bestmöglich entgegenkommt, aber sie setzt solche nicht voraus.
Um auf deine Frage zurückzukommen: Ich wüsste jedoch nicht, dass der Standard sagt, dass 0u-1u unbedingt ein Muster der Art ...1111111 ergeben müsste, weshalb das zwar nicht portabel, aber trotzdem nicht weniger definiert sein sollte. Aus dem starren Korsett des Standards sollte sich jedoch ergeben, dass alles andere ziemlich schwer darstellbar ist. Dass etwas anderes herauskommt, habe ich noch nicht gesehen, aber wenn wir schon bei Standard-Pedanterie sind, wird es langsam schwierig, die verstreuten Stellen aufzufinden, um einen einzigen logischen Ausdruck daraus zu fabrizieren, um ihn auf seine Lücken abzuklopfen, denn man sieht immer nur, was man schon gefunden hat.
Wenn die 1 in deiner Subtraktion ein int ist und der andere Operand ein unsigned(!), dürfte sich deswegen kein Problem ergeben, da zu erwarten ist, dass bei der 1 ein impliziter Cast zu unsigned erfolgt. Irgendwo sollte auch das im Standard stehen, ich weiß gerade nur nicht, wo...*1
Letztendlich ist hundertprozentig portabler Code, so erstrebenswert er auch ist, in der realen Welt ein Mythos. Wenn man sich hier dem Ideal etwas annähern möchte, dann könnte man z.B. versuchen, mehr mit den gewöhnlichen Operationen als mit Bitmasken zu arbeiten, Subtraktion, Modulo-Operator usw.
*1: Also weiter:
C99 §6.3.1.8 Absatz 1
[...]
Otherwise, the integer promotions are performed on both operands. Then the following rules are applied to the promoted operands:
If both operands have the same type, then no further conversion is needed. Otherwise, if both operands have signed integer types or both have unsigned integer types, the operand with the type of lesser integer conversion rank is converted to the type of the operand with greater rank.
Otherwise, if the operand that has unsigned integer type has rank greater or equal to the rank of the type of the other operand, then the operand with signed integer type is converted to the type of the operand with unsigned integer type.
Otherwise, if the type of the operand with signed integer type can represent all of the values of the type of the operand with unsigned integer type, then the operand with unsigned integer type is converted to the type of the operand with signed integer type.
Otherwise, both operands are converted to the unsigned integer type corresponding to the type of the operand with signed integer type.
Der grün markierte Satz sollte belegen, was ich oben schrieb, dass z.B. implizit von int nach unsigned umgewandelt wird, wenn ein Operand int und der andere unsigned int ist.
Im roten Satz ist der Integer-Typ aber so groß, dass er auch alle Werte des Unsigned-Typs innerhalb seines vorzeichenlosen Teils abbilden kann, weshalb letzterer zum Integer-Typ umgewandelt wird, was den Erhalt des Vorzeichens garantiert.
Nun gibt es manchmal noch eine weitere Sache zu berücksichtigen, indem, wie der Standard weiter oben sagt (gelb markiert, das "Otherwise" meint, dass es sich nicht um Fließkommatypen handelt), zuvor die Integer Promotions vorgenommen werden, wozu unter §6.3.1.1 u.a. Folgendes ausgeführt ist:
The following may be used in an expression wherever an int or unsigned int may be used:
— An object or expression with an integer type whose integer conversion rank is less than the rank of int and unsigned int.
— A bit-field of type _Bool, int, signed int, or unsigned int.
If an int can represent all values of the original type, the value is converted to an int;
otherwise, it is converted to an unsigned int. These are called the integer promotions.
All other types are unchanged by the integer promotions.
The integer promotions preserve value including sign. As discussed earlier, whether a "plain" char is treated as signed is implementation-defined.
Die Integer Promotions ergeben sich schon ganz pragmatisch aus der Breite eines Registers, obwohl man das aus Sicht des Standards so nicht formulieren sollte, aber daher stammt es. Deswegen werden kleinere Typen auf diese Breite geweitet und auf dieser Ebene verrechnet, was allerdings zu Fehlern führen kann, wenn man es unberücksichtigt lässt, siehe z.B. hier. Nur mal angenommen, es ginge: Wäre in dem verlinkten Beispiel ein int so klein wie ein char, dann würde zu unsigned umgewandelt! Ein unsigned char passt aber problemlos in den vorzeichenlosen Teil von int hinein, weshalb er in einen int umgewandelt wird. Größere Typen als int unterliegen nicht den Integer Promotions, wie der zitierte Abschnitt impliziert. Aber es greifen, wie sonst auch, die allgemeinen Regeln für arithmetische Ausdrücke, siehe §6.3.1.8.
In der Praxis würde man sich zuerst geeignete Typen aussuchen, was hier gewiss keine vorzeichenbehafteten sind, damit es erst gar nicht zu solchen oder ähnlichen Schwierigkeiten kommt. Beipielsweise ist eine IP-Nr. natürlicherweise immer vorzeichenlos, ebenso wie Bitmasken und Bitshifting-Kram. Dann kommt man auch gar nicht erst in Versuchung, wegen eines tatsächlich oder vermeintlich benötigten Vorzeichens Operationen mit gleich großen Integers durchzuführen, wo dann irgendwo obskure Nebenwirkungen auftreten. Stellen, an denen solche Umwandlungen erfolgen, sollten besonders gut durchdacht und wohldokumentiert sein. Wenn sie über den Code verstreut sind, bekommt man das später kaum noch entflochten, weil der Code gerade systematisch darauf aufbaut, Vorzeichen an der Hand zu haben, was neben den zu erwartenden Bugs auch noch Unwartbarkeit mit sich bringt.