Kann ein C-Compiler die Bit-Repräsentation ändern, wenn das Vorzeichen in Unsigned umgewandelt wird?

8

Ist es für eine explizite Umwandlung von, sagen wir, int32_t zu uint32_t möglich, die Bitdarstellung des Wertes zu ändern?

Zum Beispiel, wenn ich die folgende Vereinigung habe:

%Vor%

Werden diese Codesegmente durch die Spezifikation garantiert, um das gleiche Verhalten zu haben?

%Vor%

und

%Vor%

Ich denke über C99 nach. Ich habe ein paar ähnliche Fragen gesehen, aber sie alle schienen C ++ zu diskutieren, nicht C.

    
Alexandre Araujo Moreira 21.09.2013, 02:12
quelle

3 Antworten

8

Wenn ein vorzeichenbehafteter Integer-Typ in einen vorzeichenlosen Integer-Typ mit derselben Breite umgewandelt wird, kann die Darstellung geändert werden, wenn Sie eine Maschine mit vorzeichenbehafteten Darstellungen mit Vorzeichen- oder Einsen-Komplement-Werten finden. Aber die Typen int32_t und uint32_t sind garantiert Zweierkomplementdarstellungen, so dass sich die Darstellung in diesem speziellen Fall nicht ändern kann.

Die Umwandlung von vorzeichenbehafteten Ganzzahlen in vorzeichenlose Ganzzahlen ist im Standard, Abschnitt 6.3.1.3, gut definiert. Der relevante Algorithmus ist der zweite Absatz:

  
  1. Wenn ein Wert mit Integer-Typ in einen anderen Integer-Typ als _Bool konvertiert wird, wenn   Der Wert kann durch den neuen Typ dargestellt werden, er ist unverändert.
  2.   
  3. Andernfalls, wenn der neue Typ unsigniert ist, wird der Wert durch wiederholtes Hinzufügen von oder konvertiert   Subtrahieren von mehr als dem maximalen Wert, der in dem neuen Typ dargestellt werden kann   bis der Wert im Bereich des neuen Typs liegt.
  4.   
  5. ...
  6.   

Also muss das Ergebnis in Wirklichkeit sein, was eine Bit-für-Bit-Kopie ergeben hätte, wenn die negative Zahl im 2er-Komplement gespeichert worden wäre. Eine konforme Implementierung darf Zeichen-Magnitude oder Einerkomplement verwenden; In beiden Fällen muss die Darstellung negativer Ganzzahlen so geändert werden, dass sie ohne Vorzeichen erfolgt.

Fassen Sie eine lange und interessante Diskussion in den Kommentaren zusammen:

  • Im exakten Beispiel im OP, das int32_t und uint32_t verwendet, müssen die Repräsentationen gleich sein, wenn das Programm kompiliert, weil C99 int32_t und% co_de erfordert % ist genau 32 Bit lang ohne Auffüllung und erfordert uint32_t , um die Zweierkomplementdarstellung zu verwenden. Es erfordert jedoch nicht, dass diese Typen existieren; Eine Einerkomplement-Implementierung könnte einfach nicht int32_t definieren und trotzdem konform sein.

  • Meine Interpretation von Typ-Punning liegt unter der horizontalen Regel. @R .. wies uns auf einen Fehlerbericht von 2004 hin, der scheint zu sagen, dass type-punning entweder in Ordnung ist oder einen Trap auslöst, der näher am implementierungsdefinierten Verhalten liegt als nicht definiertes Verhalten. Auf der anderen Seite scheint die vorgeschlagene Lösung dieser DR nicht in dem C11-Dokument enthalten zu sein, das besagt (6.2.6.1 (5)):

  

Bestimmte Objektdarstellungen müssen keinen Wert des Objekttyps darstellen. Wenn der gespeicherte Wert eines Objekts eine solche Repräsentation hat und von einem Lvalue-Ausdruck gelesen wird, der keinen Zeichentyp hat, ist das Verhalten nicht definiert.

Das scheint mir zu sagen, dass Typ-Punning ein undefiniertes Verhalten ist, wenn einer der beteiligten Typen eine Trap-Repräsentation hat (und folglich nicht undefiniertes Verhalten ist, wenn der Typ keine Trap hat) Darstellung). Auf der anderen Seite ist kein Typ erforderlich, um eine Trap-Repräsentation zu haben, und nur wenigen Typen ist es verboten, einen zu haben: int32_t und char types - aber nicht Mitglieder von Union-Typen - sowie welches auch immer Die union -Typen sind implementiert.

Meine vorherige Aussage zu type-punning folgt:

Die Speicher-Punning-Union hat ein undefiniertes Verhalten. Aber ohne den Aufruf von lagados voladores wird erwartet, dass Zeichen-Magnitude- oder Einerkomplement-Maschinen eine Hardware-Ausnahme auslösen können, wenn ein bestimmter Wert als vorzeichenlos gespeichert und dann als signiert abgerufen wird.

Sowohl Einerkomplement als auch Vorzeichengröße haben zwei mögliche Darstellungen von [u]int*K_t , eine mit jedem populären Vorzeichenbit. Derjenige mit einem negativen Vorzeichenbit, "negativer Nullwert", darf ein "Einfangwert" sein; folglich kann der Zugriff auf den Wert (auch nur um ihn zu kopieren) als eine ganze Zahl mit Vorzeichen den Trap auslösen.

Obwohl der C-Compiler innerhalb seiner Rechte ist, den Trap zu unterdrücken, indem er den Wert mit memcpy oder einem unsignierten Opcode kopiert, ist es unwahrscheinlich, dass dies für einen Programmierer überraschend wäre, der wusste, dass ihr Programm lief auf einer Maschine mit negativen Nullen und erwartet, dass die Falle bei einem ungültigen Wert ausgelöst wird.

    
rici 21.09.2013, 02:16
quelle
4

In dem speziellen Fall, in dem Sie eine Konvertierung von int32_t nach uint32_t erwähnen, ist die Bitdarstellung identisch.

Der Standard erfordert insbesondere intN_t als "einen vorzeichenbehafteten Integer-Typ mit der Breite N , keine Füllbits und eine Zweierkomplementdarstellung". Darüber hinaus müssen entsprechende vorzeichenbehaftete und vorzeichenlose Typen für Werte innerhalb ihres gemeinsamen Bereichs dieselbe Darstellung aufweisen:

  

Eine gültige (Nicht-Trap) Objektdarstellung eines Ganzzahl-Typs mit Vorzeichen   wo das Vorzeichenbit Null ist, ist eine gültige Objektdarstellung der   entsprechenden vorzeichenlosen Typ, und soll den gleichen Wert darstellen.

Es gibt eine sehr kleine mögliche Lücke: Im Prinzip könnte eine Implementierung beispielsweise int32_t zu typedef für int machen, und uint32_t a typedef für unsigned long , wobei int and long beide 32 Bits sind, aber unterschiedliche Byte-Ordnungen haben. Aber das würde nur in einer absichtlich perversen Implementierung passieren. Korrektur : Dies ist für eine konforme Implementierung nicht möglich. int32_t und uint32_t müssen entsprechende vorzeichenbehaftete und vorzeichenlose Typen bezeichnen.

Das obige gilt nur, weil Sie zufällig int32_t und uint32_t für Ihr Beispiel gewählt haben, und der Standard legt sehr spezifische Einschränkungen für deren Darstellung fest. (Und wenn eine Implementierung diese Einschränkungen nicht erfüllen kann, wird% ce_de% oder int32_t nicht definiert.)

Generell können signierte Typen jedoch eine von drei Darstellungen haben:

  • Zeichen und Betrag , wenn das Vorzeichenbit auf 1 gesetzt wird, negiert eine Zahl;
  • Zweierkomplement , wobei die Negation äquivalent zu einem bitweisen Komplement ist, gefolgt von der Addition von 1; und
  • Einerkomplement , wobei die Negation einem bitweisen Komplement entspricht.

Die meisten modernen Systeme verwenden Zweierkomplemente (und haben keine Padding-Bits). Bei solchen Systemen ändert die Umwandlung von vorzeichenbehaftet nach nicht signiert mit Typen gleicher Größe im allgemeinen nicht die Bitdarstellung. (Die Semantik von Typumwandlungen ist in Werten definiert, aber für Zweierkomplementsysteme geeignet.)

Aber für ein System, das entweder Vorzeichen und Betrag oder ein Komplement verwendet, muss die Konvertierung von vorzeichenbehaftet nach vorzeichenlos den Wert beibehalten, was bedeutet, dass die Konvertierung eines negativen Werts die Darstellung ändern muss.

    
Keith Thompson 21.09.2013 03:05
quelle
2

Wenn der Wert im Bereich sowohl der vorzeichenbehafteten als auch der vorzeichenlosen Typen liegt, werden sowohl der Wert als auch die Darstellung durch Konvertierungen nicht geändert.

Andernfalls darf die Konvertierung von vorzeichenbehaftet nach vorzeichenlos nur die Bitdarstellung beibehalten, wenn die Darstellung negativer Werte für den Typ der Implementierung zweierkomplementär ist. Für ein Komplement oder eine Zeichengröße muss die -Konvertierung die Darstellung ändern. Die Konvertierung in die andere Richtung ist implementationsdefiniert, sodass die Repräsentation geändert werden kann oder nicht.

    
R.. 21.09.2013 02:21
quelle

Tags und Links