Konvertierung von signierten in nicht signierte Dateien in C – ist das immer sicher?

StackOverflow https://stackoverflow.com/questions/50605

  •  09-06-2019
  •  | 
  •  

Frage

Angenommen, ich habe den folgenden C-Code.

unsigned int u = 1234;
int i = -5678;

unsigned int result = u + i;

Welche impliziten Konvertierungen finden hier statt und ist dieser Code für alle Werte von sicher? u Und i?(Sicher in dem Sinne, dass obwohl Ergebnis In diesem Beispiel wird der Überlauf zu einer großen positiven Zahl, ich könnte ihn in eine umwandeln int und erhalten Sie das tatsächliche Ergebnis.)

War es hilfreich?

Lösung

Kurze Antwort

Dein i wird sein umgewandelt zu einer vorzeichenlosen Ganzzahl durch Addition UINT_MAX + 1, dann wird die Addition mit den vorzeichenlosen Werten durchgeführt, was zu einem großen Ergebnis führt result (abhängig von den Werten von u Und i).

Lange Antwort

Gemäß dem C99-Standard:

6.3.1.8 Übliche arithmetische Umrechnungen

  1. Wenn beide Operanden denselben Typ haben, ist keine weitere Konvertierung erforderlich.
  2. Andernfalls, wenn beide Operanden Ganzzahltypen mit Vorzeichen oder beide Ganzzahltypen ohne Vorzeichen haben, wird der Operand mit dem Typ mit dem niedrigeren Ganzzahlkonvertierungsrang in den Typ des Operanden mit dem höheren Rang konvertiert.
  3. Andernfalls, wenn der Rang des Operanden mit dem vorzeichenlosen Ganzzahltyp größer oder gleich dem Rang des Typs des anderen Operanden ist, wird der Operand mit dem vorzeichenbehafteten Ganzzahltyp in den Typ des Operanden mit dem vorzeichenlosen Ganzzahltyp konvertiert.
  4. Andernfalls, wenn der Typ des Operanden mit vorzeichenbehafteter Ganzzahl alle Werte des Typs des Operanden mit vorzeichenlosem Ganzzahltyp darstellen kann, wird der Operand mit vorzeichenlosem Ganzzahltyp in den Typ des Operanden mit vorzeichenbehaftetem Ganzzahltyp konvertiert.
  5. Andernfalls werden beide Operanden in den vorzeichenlosen Ganzzahltyp konvertiert, der dem Typ des Operanden mit vorzeichenbehaftetem Ganzzahltyp entspricht.

In Ihrem Fall haben wir ein unsigned int (u) und signiert int (i).Da beide Operanden den gleichen Rang haben, beziehen Sie sich auf (3) oben i wird sein müssen umgewandelt zu einer vorzeichenlosen Ganzzahl.

6.3.1.3 Vorzeichenbehaftete und vorzeichenlose Ganzzahlen

  1. Wenn ein Wert mit einem ganzzahligen Typ in einen anderen ganzzahligen Typ als _Bool konvertiert wird und der Wert durch den neuen Typ dargestellt werden kann, bleibt er unverändert.
  2. Andernfalls, wenn der neue Typ vorzeichenlos ist, wird der Wert durch wiederholtes Addieren oder Subtrahieren um eins mehr als den Maximalwert, der im neuen Typ dargestellt werden kann, konvertiert, bis der Wert im Bereich des neuen Typs liegt.
  3. Andernfalls ist der neue Typ signiert und der Wert kann darin nicht dargestellt werden;Entweder ist das Ergebnis durch die Implementierung definiert oder es wird ein durch die Implementierung definiertes Signal ausgelöst.

Jetzt müssen wir uns auf (2) oben beziehen.Dein i wird durch Addition in einen vorzeichenlosen Wert umgewandelt UINT_MAX + 1.Das Ergebnis hängt also davon ab, wie UINT_MAX ist in Ihrer Implementierung definiert.Es wird groß sein, aber es wird nicht überlaufen, weil:

6.2.5 (9)

Eine Berechnung mit vorzeichenlosen Operanden kann niemals überlaufen, da ein Ergebnis, das nicht durch den resultierenden vorzeichenlosen Ganzzahltyp dargestellt werden kann, modulo um die Zahl reduziert wird, die um eins größer ist als der größte Wert, der durch den resultierenden Typ dargestellt werden kann.

Bonus:Arithmetische Konvertierung Semi-WTF

#include <stdio.h>

int main(void)
{
  unsigned int plus_one = 1;
  int minus_one = -1;

  if(plus_one < minus_one)
    printf("1 < -1");
  else
    printf("boring");

  return 0;
}

Über diesen Link können Sie es online ausprobieren: https://repl.it/repls/QuickWhimsicalBytes

Bonus:Nebeneffekt der arithmetischen Konvertierung

Um den Wert von zu ermitteln, können arithmetische Umrechnungsregeln verwendet werden UINT_MAX durch Initialisierung eines vorzeichenlosen Werts auf -1, also:

unsigned int umax = -1; // umax set to UINT_MAX

Aufgrund der oben beschriebenen Konvertierungsregeln ist die Portabilität unabhängig von der vorzeichenbehafteten Zahlendarstellung des Systems garantiert.Weitere Informationen finden Sie in dieser SO-Frage: Ist es sicher, -1 zu verwenden, um alle Bits auf true zu setzen?

Andere Tipps

Die Konvertierung von signiert nach unsigniert funktioniert nicht unbedingt einfach die Darstellung des vorzeichenbehafteten Werts kopieren oder neu interpretieren.Zitieren des C-Standards (C99 6.3.1.3):

Wenn ein Wert mit ganzzahliger Typ in einen anderen ganzzahligen Typ als _Bool konvertiert wird, ist der Wert durch den neuen Typ unverändert.

Andernfalls wird der Wert, wenn der neue Typ nicht signiert ist, durch wiederholtes Hinzufügen oder Subtrahieren von mehr als den Maximalwert, der im neuen Typ dargestellt werden kann, bis der Wert im Bereich des neuen Typs liegt.

Andernfalls ist der neue Typ signiert und der Wert kann darin nicht dargestellt werden;Entweder ist das Ergebnis implementierungsdefiniert oder es wird ein implementierungsdefiniertes Signal erhöht.

Für die Zweierkomplementdarstellung, die heutzutage nahezu universell ist, entsprechen die Regeln einer Neuinterpretation der Bits.Aber für andere Darstellungen (Vorzeichen und Betrag oder Einerkomplement) muss die C-Implementierung immer noch für das gleiche Ergebnis sorgen, was bedeutet, dass die Konvertierung nicht einfach die Bits kopieren kann.Beispiel: (unsigned)-1 == UINT_MAX, unabhängig von der Darstellung.

Im Allgemeinen sind Konvertierungen in C so definiert, dass sie auf Werte und nicht auf Darstellungen angewendet werden.

Um die ursprüngliche Frage zu beantworten:

unsigned int u = 1234;
int i = -5678;

unsigned int result = u + i;

Der Wert von i wird in ein vorzeichenloses int umgewandelt, was ergibt UINT_MAX + 1 - 5678.Dieser Wert wird dann zum vorzeichenlosen Wert 1234 addiert, was ergibt UINT_MAX + 1 - 4444.

(Im Gegensatz zum vorzeichenlosen Überlauf ruft der vorzeichenbehaftete Überlauf undefiniertes Verhalten hervor.Wraparound ist üblich, wird aber vom C-Standard nicht garantiert – und Compiler-Optimierungen können verheerende Auswirkungen auf Code haben, der ungerechtfertigte Annahmen trifft.)

In Bezug auf die Bibel:

  • Ihre Additionsoperation bewirkt, dass das int in ein vorzeichenloses int konvertiert wird.
  • Unter der Annahme einer Zweierkomplementdarstellung und gleich großer Typen ändert sich das Bitmuster nicht.
  • Die Konvertierung von „unsigned int“ in „signed int“ ist von der Implementierung abhängig.(Aber es funktioniert heutzutage wahrscheinlich auf den meisten Plattformen so, wie Sie es erwarten.)
  • Bei der Kombination von signierten und nicht signierten Zeichen unterschiedlicher Größe sind die Regeln etwas komplizierter.

Wenn eine vorzeichenlose und eine vorzeichenbehaftete Variable hinzugefügt werden (oder eine binäre Operation), werden beide implizit in vorzeichenlose umgewandelt, was in diesem Fall zu einem riesigen Ergebnis führen würde.

Es ist also sicher in dem Sinne, dass das Ergebnis zwar groß und falsch sein kann, es aber nie zum Absturz kommt.

Bei der Konvertierung von signiert nach unsigniert gibt es zwei Möglichkeiten.Zahlen, die ursprünglich positiv waren, behalten den gleichen Wert (oder werden als solcher interpretiert).Ursprünglich negative Zahlen werden nun als größere positive Zahlen interpretiert.

Wie bereits beantwortet, können Sie problemlos zwischen signierten und nicht signierten Dateien hin und her wechseln.Der Grenzfall für vorzeichenbehaftete Ganzzahlen ist -1 (0xFFFFFFFF).Versuchen Sie, davon zu addieren und zu subtrahieren, und Sie werden feststellen, dass Sie es zurückrechnen können, um sicherzustellen, dass es korrekt ist.

Wenn Sie jedoch hin und her konvertieren möchten, würde ich Ihnen dringend empfehlen, Ihre Variablen so zu benennen, dass klar ist, um welchen Typ es sich handelt, z. B.:

int iValue, iResult;
unsigned int uValue, uResult;

Es ist viel zu leicht, sich von wichtigeren Themen ablenken zu lassen und zu vergessen, welche Variable welchen Typ hat, wenn sie ohne Hinweis benannt werden.Sie möchten nicht in einen vorzeichenlosen Wert umwandeln und diesen dann als Array-Index verwenden.

Welche impliziten Konvertierungen finden hier statt?

Ich werde in eine vorzeichenlose Ganzzahl konvertiert.

und ist dieser Code für alle Werte von u und i sicher?

Sicher im Sinne von wohldefiniert ja (siehe https://stackoverflow.com/a/50632/5083516 ).

Die Regeln sind normalerweise in schwer lesbarer Standardsprache geschrieben, aber im Wesentlichen enthält die vorzeichenlose Ganzzahl unabhängig von der Darstellung, die in der vorzeichenbehafteten Ganzzahl verwendet wurde, eine Zweierkomplementdarstellung der Zahl.

Addition, Subtraktion und Multiplikation funktionieren bei diesen Zahlen korrekt, was zu einer weiteren vorzeichenlosen Ganzzahl führt, die eine Zweierkomplementzahl enthält, die das „echte Ergebnis“ darstellt.

Division und Umwandlung in größere vorzeichenlose Ganzzahltypen führen zu wohldefinierten Ergebnissen, aber diese Ergebnisse sind keine Zweierkomplementdarstellungen des „realen Ergebnisses“.

(Sicher, in dem Sinne, dass ich das Ergebnis in diesem Beispiel zwar zu einer sehr großen positiven Zahl überlaufen würde, es aber wieder in einen int-Wert umwandeln und das tatsächliche Ergebnis erhalten könnte.)

Während Konvertierungen von vorzeichenbehafteten zu vorzeichenlosen durch den Standard definiert werden, ist die Umkehrung durch die Implementierung definiert. Sowohl gcc als auch msvc definieren die Konvertierung so, dass Sie das „echte Ergebnis“ erhalten, wenn Sie eine in einer vorzeichenlosen Ganzzahl gespeicherte Zweierkomplementzahl zurück in eine vorzeichenbehaftete Ganzzahl umwandeln .Ich gehe davon aus, dass Sie nur auf unbekannten Systemen, die kein Zweierkomplement für vorzeichenbehaftete Ganzzahlen verwenden, ein anderes Verhalten feststellen werden.

https://gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html#Integers-implementation https://msdn.microsoft.com/en-us/library/0eex498h.aspx

Schreckliche Antworten in Hülle und Fülle

Ozgur Ozcitak

Wenn Sie von unterschrieben zu unsigniertem (und umgekehrt) gegossen werden, ändert sich die interne Darstellung der Zahl nicht.Was sich ändert, ist, wie der Compiler das Zeichenbit interpretiert.

Das ist völlig falsch.

Mats Fredriksson

Wenn eine unsignierte und eine signierte Variable hinzugefügt wird (oder eine Binäroperation), werden beide implizit in Unsigned konvertiert, was in diesem Fall zu einem großen Ergebnis führen würde.

Auch das ist falsch.Ints ohne Vorzeichen können zu Ints heraufgestuft werden, wenn sie aufgrund der Füllbits im vorzeichenlosen Typ die gleiche Genauigkeit haben.

smh

Ihr Additionsvorgang führt dazu, dass der INT in einen nicht signierten int umgewandelt wird.

Falsch.Vielleicht tut es das, vielleicht auch nicht.

Die Konvertierung von nicht signiertem int zu signierten INT ist die Implementierung abhängig.(Aber es funktioniert wahrscheinlich so, wie Sie es heutzutage auf den meisten Plattformen erwarten.)

Falsch.Es handelt sich entweder um undefiniertes Verhalten, wenn es einen Überlauf verursacht, oder der Wert bleibt erhalten.

Anonym

Der Wert von I wird in nicht signierte int ... umgewandelt ...

Falsch.Hängt von der Genauigkeit eines int relativ zu einem vorzeichenlosen int ab.

Taylor Price

Wie bereits beantwortet, können Sie ohne Problem zwischen unterschrieben und unsigniert wechseln.

Falsch.Der Versuch, einen Wert außerhalb des Bereichs einer vorzeichenbehafteten Ganzzahl zu speichern, führt zu undefiniertem Verhalten.

Jetzt kann ich die Frage endlich beantworten.

Sollte die Genauigkeit von int gleich unsigned int sein, wird u zu einem vorzeichenbehafteten int heraufgestuft und Sie erhalten den Wert -4444 aus dem Ausdruck (u+i).Sollten u und ich nun andere Werte haben, könnte es zu einem Überlauf und undefiniertem Verhalten kommen, aber mit diesen genauen Zahlen erhalten Sie -4444 [1].Dieser Wert wird vom Typ int sein.Sie versuchen jedoch, diesen Wert in einem vorzeichenlosen Ganzzahlwert zu speichern, damit er dann in einen vorzeichenlosen Ganzzahlwert umgewandelt wird und der Wert des Ergebnisses am Ende (UINT_MAX+1) - 4444 wäre.

Sollte die Genauigkeit eines vorzeichenlosen Ganzzahlwerts größer als die eines Ganzzahlwerts sein, wird der vorzeichenlose Ganzzahlwert zu einem vorzeichenlosen Ganzzahlwert heraufgestuft, was den Wert (UINT_MAX+1) - 5678 ergibt, der zum anderen vorzeichenlosen Ganzzahlwert 1234 addiert wird.Sollten u und i andere Werte haben, die dazu führen, dass der Ausdruck außerhalb des Bereichs {0..UINT_MAX} liegt, wird der Wert (UINT_MAX+1) entweder addiert oder subtrahiert, bis das Ergebnis tatsächlich in den Bereich {0..UINT_MAX) fällt Es wird kein undefiniertes Verhalten auftreten.

Was ist Präzision?

Ganzzahlen haben Füllbits, Vorzeichenbits und Wertbits.Ganzzahlen ohne Vorzeichen haben offensichtlich kein Vorzeichenbit.Es wird außerdem garantiert, dass vorzeichenlose Zeichen keine Füllbits enthalten.Die Anzahl der Wertbits einer Ganzzahl gibt an, wie genau sie ist.

[Fallstricke]

Die Makrogröße des Makros allein kann nicht zur Bestimmung der Genauigkeit einer Ganzzahl verwendet werden, wenn Füllbits vorhanden sind.Und die Größe eines Bytes muss nicht unbedingt ein Oktett (acht Bits) sein, wie in C99 definiert.

[1] Der Überlauf kann an einem von zwei Punkten auftreten.Entweder vor dem Hinzufügen (während der Heraufstufung) – wenn Sie ein vorzeichenloses int haben, das zu groß ist, um in ein int zu passen.Der Überlauf kann auch nach der Addition auftreten, selbst wenn das vorzeichenlose int im Bereich eines int lag. Nach der Addition kann das Ergebnis immer noch überlaufen.


Unabhängig davon bin ich ein frischgebackener Doktorand, der auf der Suche nach einem Job ist ;)

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top