Signierte zu unsignierte Konvertierung in C – ist es immer sicher?

Lesezeit: 8 Minuten

Benutzeravatar von cwick
schlau

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 sicher für alle Werte von u und i? (Sicher in dem Sinne, dass obwohl Ergebnis in diesem Beispiel zu einer riesigen positiven Zahl überlaufen würde, könnte ich sie zurück zu einer umwandeln int und erhalten Sie das tatsächliche Ergebnis.)

Benutzeravatar von Ozgur Ozcitak
Özgür Ozcitak

Kurze Antwort

Dein i wird sein umgewandelt zu einer vorzeichenlosen Ganzzahl durch Hinzufügen UINT_MAX + 1dann wird die Addition mit den vorzeichenlosen Werten durchgeführt, was zu einem großen 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 vorzeichenbehaftete Integer-Typen oder beide vorzeichenlose Integer-Typen haben, wird der Operand mit dem Typ des kleineren ganzzahligen Konvertierungsrangs in den Typ des Operanden mit dem höheren Rang konvertiert.
  3. Andernfalls, wenn der Rang des Operanden vom Typ Ganzzahl ohne Vorzeichen größer oder gleich dem Rang des Typs des anderen Operanden ist, wird der Operand vom Typ Ganzzahl mit Vorzeichen in den Typ des Operanden vom Typ Ganzzahl ohne Vorzeichen konvertiert.
  4. Andernfalls, wenn der Typ des Operanden mit vorzeichenbehafteter Ganzzahl alle Werte des Typs des Operanden mit vorzeichenloser Ganzzahl darstellen kann, wird der Operand mit vorzeichenloser Ganzzahl in den Typ des Operanden mit vorzeichenbehafteter Ganzzahl konvertiert.
  5. Andernfalls werden beide Operanden in den vorzeichenlosen Integer-Typ konvertiert, der dem Typ des Operanden mit vorzeichenbehaftetem Integer-Typ entspricht.

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

6.3.1.3 Ganzzahlen mit und ohne Vorzeichen

  1. Wenn ein Wert mit ganzzahligem 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 konvertiert, indem wiederholt eins mehr als der maximale Wert, der im neuen Typ dargestellt werden kann, addiert oder subtrahiert wird, 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 implementierungsdefiniert oder es wird ein implementierungsdefiniertes Signal ausgelöst.

Nun 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 wird 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 ganzzahligen Typ 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;
}

Sie können diesen Link verwenden, um dies online zu versuchen: https://repl.it/repls/QuickWhimsicalBytes

Bonus: Nebeneffekt der arithmetischen Konvertierung

Arithmetische Konvertierungsregeln können verwendet werden, um den Wert von zu erhalten UINT_MAX durch Initialisieren eines vorzeichenlosen Werts auf -1dh:

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

Dies ist aufgrund der oben beschriebenen Umwandlungsregeln unabhängig von der vorzeichenbehafteten Zahlendarstellung des Systems garantiert portierbar. Weitere Informationen finden Sie in dieser SO-Frage: Ist es sicher, -1 zu verwenden, um alle Bits auf wahr zu setzen?

  • Ich verstehe nicht, warum es nicht einfach einen absoluten Wert machen und dann als vorzeichenlos behandeln kann, genau wie bei positiven Zahlen?

    – José Salvatierra

    22. April 2013 um 23:22 Uhr

  • @D.Singh kannst du freundlicherweise auf die falschen Teile in der Antwort zeigen?

    – Shmil die Katze

    3. September 2014 um 10:27 Uhr

  • Zum Konvertieren von signiert in unsigned addieren wir den maximalen Wert des unsigned-Werts (UINT_MAX +1). Wie kann man auf einfache Weise von unsigniert in signiert konvertieren? Müssen wir die angegebene Zahl vom Maximalwert (256 bei unsigned char) subtrahieren? Beispiel: 140 wird bei Umwandlung in eine vorzeichenbehaftete Zahl zu -116. Aber 20 wird selbst 20. Gibt es hier einen einfachen Trick?

    – Jon Wheelock

    18. Oktober 2015 um 14:01 Uhr

  • @JonWheelock siehe: stackoverflow.com/questions/8317295/…

    – Özgur Ozcitak

    19. Oktober 2015 um 16:55 Uhr

Konvertierung von vorzeichenbehafteten zu unsignierten tut nicht kopieren oder interpretieren Sie einfach die Darstellung des signierten Werts. Zitieren des C-Standards (C99 6.3.1.3):

Wenn ein Wert mit ganzzahligem Typ in einen anderen ganzzahligen Typ als _Bool konvertiert wird und der Wert durch den neuen Typ dargestellt werden kann, bleibt er unverändert.

Andernfalls, wenn der neue Typ vorzeichenlos ist, wird der Wert konvertiert, indem wiederholt eins mehr als der maximale Wert, der im neuen Typ dargestellt werden kann, addiert oder subtrahiert wird, 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 ausgelöst.

Für die heutzutage fast universelle Darstellung des Zweierkomplements entsprechen die Regeln der Neuinterpretation der Bits. Aber für andere Darstellungen (Vorzeichen und Betrag oder Einerkomplement) muss die C-Implementierung immer noch für dasselbe 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 mit Werten arbeiten, nicht mit Repräsentationen.

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 unsigned int konvertiert, was ergibt UINT_MAX + 1 - 5678. Dieser Wert wird dann zum vorzeichenlosen Wert 1234 addiert, was ergibt UINT_MAX + 1 - 4444.

(Im Gegensatz zu einem unsignierten Überlauf ruft ein signierter Überlauf ein undefiniertes Verhalten auf. Wraparound ist üblich, wird aber nicht vom C-Standard garantiert – und Compiler-Optimierungen können verheerenden Schaden an Code anrichten, der ungerechtfertigte Annahmen macht.)

Benutzeravatar von smh
smh

Bezugnehmend auf The C Programming Language, Second Edition (ISBN 0131103628),

  • Ihre Additionsoperation bewirkt, dass int in ein unsigned int konvertiert wird.
  • Unter der Annahme einer Zweierkomplementdarstellung und gleichgroßer Typen ändert sich das Bitmuster nicht.
  • Die Konvertierung von unsigned int zu signed int ist implementierungsabhängig. (Aber es funktioniert wahrscheinlich so, wie Sie es heutzutage auf den meisten Plattformen erwarten.)
  • Die Regeln sind etwas komplizierter, wenn signierte und unsignierte Zeichen unterschiedlicher Größe kombiniert werden.

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

Es ist also sicher in dem Sinne, dass das Ergebnis möglicherweise riesig und falsch ist, aber es wird niemals abstürzen.

Bei der Konvertierung von signiert zu unsigned gibt es zwei Möglichkeiten. Zahlen, die ursprünglich positiv waren, behalten denselben Wert (oder werden als solcher interpretiert). Zahlen, die ursprünglich negativ waren, werden jetzt als größere positive Zahlen interpretiert.

Benutzeravatar von Taylor Price
Taylor Price

Wie bereits beantwortet, können Sie problemlos zwischen signiert und unsigniert hin und her werfen. Der Grenzfall für vorzeichenbehaftete Ganzzahlen ist -1 (0xFFFFFFFF). Versuchen Sie, davon zu addieren und zu subtrahieren, und Sie werden feststellen, dass Sie zurückrechnen können und es richtig ist.

Wenn Sie jedoch hin und her casten, würde ich dringend dazu raten, 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 Dingen ablenken zu lassen und zu vergessen, welche Variable welcher Typ ist, wenn sie ohne Hinweis benannt werden. Sie möchten nicht in einen unsigned umwandeln und diesen dann als Array-Index verwenden.

Benutzeravatar der Community
Gemeinschaft

Welche impliziten Konvertierungen finden hier statt,

i wird in eine vorzeichenlose Ganzzahl umgewandelt.

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 in normalerweise schwer lesbarer Standardsprache geschrieben, aber im Wesentlichen enthält die vorzeichenlose Ganzzahl, unabhängig davon, welche Darstellung in der vorzeichenbehafteten Ganzzahl verwendet wurde, eine 2er-Komplement-Darstellung 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 “reale Ergebnis” darstellt.

Division und Umwandlung in größere vorzeichenlose Integer-Typen haben wohldefinierte Ergebnisse, aber diese Ergebnisse sind keine Zweierkomplementdarstellungen des “echten Ergebnisses”.

(Sicher in dem Sinne, dass, obwohl das Ergebnis in diesem Beispiel zu einer großen positiven Zahl überläuft, ich es in ein int zurückverwandeln und das echte Ergebnis erhalten könnte.)

Während Konvertierungen von vorzeichenbehaftet zu vorzeichenlos durch den Standard definiert sind, ist das Gegenteil implementierungsdefiniert, 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 konvertieren . Ich gehe davon aus, dass Sie nur auf obskuren Systemen, die kein Zweierkomplement für vorzeichenbehaftete Ganzzahlen verwenden, ein anderes Verhalten finden werden.

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

1424390cookie-checkSignierte zu unsignierte Konvertierung in C – ist es immer sicher?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy