Signiert für nicht signierte Konvertierung in C - ist es immer sicher?

135

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 uund sicher i? (Sicher, in dem Sinne, dass ich , obwohl das Ergebnis in diesem Beispiel zu einer riesigen positiven Zahl überläuft, es auf ein int zurücksetzen und das tatsächliche Ergebnis erhalten könnte.)

Cwick
quelle

Antworten:

223

Kurze Antwort

Ihr iwird durch Hinzufügen in eine vorzeichenlose Ganzzahl konvertiertUINT_MAX + 1 , dann wird die Addition mit den vorzeichenlosen Werten ausgeführt, was zu einem großen Wert führt result(abhängig von den Werten von uund i).

Lange Antwort

Nach dem C99-Standard:

6.3.1.8 Übliche arithmetische Umrechnungen

  1. Wenn beide Operanden denselben Typ haben, ist keine weitere Konvertierung erforderlich.
  2. Andernfalls wird der Operand mit dem Typ des Konvertierungsrangs mit geringerer Ganzzahl in den Typ des Operanden mit dem höheren Rang konvertiert, wenn beide Operanden Ganzzahltypen mit Vorzeichen oder beide Ganzzahltypen ohne Vorzeichen haben.
  3. Wenn andernfalls der Operand mit vorzeichenlosem Integer-Typ einen Rang hat, der größer oder gleich dem Rang des Typs des anderen Operanden ist, wird der Operand mit vorzeichenbehaftetem Integer-Typ in den Typ des Operanden mit vorzeichenlosem Integer-Typ konvertiert.
  4. Wenn andernfalls der Typ des Operanden mit vorzeichenbehaftetem Integer-Typ alle Werte des Typs des Operanden mit vorzeichenlosem Integer-Typ darstellen kann, wird der Operand mit vorzeichenlosem Integer-Typ in den Typ des Operanden mit vorzeichenbehaftetem Integer-Typ 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 vorzeichenloses int ( u) und ein signiertes int ( i). Mit Bezug auf (3), da beide Operanden den gleichen Rang haben, Ihr iwerden müssen umgewandelt in eine ganze Zahl ohne Vorzeichen.

6.3.1.3 Ganzzahlen mit und ohne Vorzeichen

  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. Wenn der neue Typ nicht signiert ist, wird der Wert konvertiert, indem wiederholt ein Wert mehr als der Maximalwert addiert oder subtrahiert wird, der im neuen Typ dargestellt werden kann, bis der Wert im Bereich des neuen Typs liegt.
  3. Andernfalls wird der neue Typ signiert und der Wert kann nicht darin dargestellt werden. Entweder ist das Ergebnis implementierungsdefiniert oder es wird ein implementierungsdefiniertes Signal ausgelöst.

Nun müssen wir uns auf (2) oben beziehen. Ihr iwird durch Hinzufügen in einen vorzeichenlosen Wert umgewandelt UINT_MAX + 1. Das Ergebnis hängt also davon ab, wie UINT_MAXes in Ihrer Implementierung definiert ist. 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 eins größer ist als der größte Wert, der durch den resultierenden Typ dargestellt werden kann.

Bonus: Arithmetische Umrechnung 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 Umwandlung

Arithmetische Konvertierungsregeln können verwendet werden, um den Wert von zu erhalten, UINT_MAXindem ein vorzeichenloser Wert initialisiert wird -1, dh:

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

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

Ozgur Ozcitak
quelle
Ich verstehe nicht, warum es nicht einfach einen absoluten Wert geben kann und dann als vorzeichenlos behandelt wird, genau wie bei positiven Zahlen?
Jose Salvatierra
7
@ D.Singh kannst du bitte auf die falschen Stellen in der Antwort verweisen?
Shmil The Cat
Für die Konvertierung von vorzeichenlosen in vorzeichenlose Werte addieren wir den Maximalwert des vorzeichenlosen Werts (UINT_MAX +1). Was ist der einfache Weg, um von nicht signiert zu signiert zu konvertieren? Müssen wir die angegebene Zahl vom Maximalwert subtrahieren (256 bei vorzeichenlosem Zeichen)? Beispiel: 140 wird bei Konvertierung in eine vorzeichenbehaftete Nummer zu -116. Aber 20 wird selbst 20. Also ein einfacher Trick hier?
Jon Wheelock
@ JonWheelock siehe: stackoverflow.com/questions/8317295/…
Ozgur Ozcitak
24

Die Konvertierung von signiert in nicht signiert kopiert oder interpretiert nicht unbedingt nur die Darstellung des signierten Werts. Zitieren des C-Standards (C99 6.3.1.3):

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.

Wenn der neue Typ nicht signiert ist, wird der Wert konvertiert, indem wiederholt ein Wert mehr als der Maximalwert addiert oder subtrahiert wird, der im neuen Typ dargestellt werden kann, bis der Wert im Bereich des neuen Typs liegt.

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

Für die Komplementdarstellung der beiden, die heutzutage nahezu universell ist, entsprechen die Regeln der Neuinterpretation der Bits. Bei anderen Darstellungen (Vorzeichen und Größe oder Einsenkomplement) muss die C-Implementierung dennoch das gleiche Ergebnis erzielen, was bedeutet, dass die Konvertierung nicht nur die Bits kopieren kann. Zum Beispiel (ohne Vorzeichen) -1 == UINT_MAX, unabhängig von der Darstellung.

Im Allgemeinen werden Konvertierungen in C so definiert, dass sie mit Werten und nicht mit Darstellungen arbeiten.

So beantworten Sie die ursprüngliche Frage:

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

unsigned int result = u + i;

Der Wert von i wird in int ohne Vorzeichen umgewandelt, was ergibt UINT_MAX + 1 - 5678. Dieser Wert wird dann zu dem vorzeichenlosen Wert 1234 addiert, was ergibt UINT_MAX + 1 - 4444.

(Im Gegensatz zu einem nicht signierten Überlauf ruft ein signierter Überlauf ein undefiniertes Verhalten hervor. Wraparound ist häufig, wird jedoch vom C-Standard nicht garantiert. Compiler-Optimierungen können den Code zerstören, der ungerechtfertigte Annahmen trifft.)


quelle
5

Bezugnehmend 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 vorzeichenlosem int in signiertes 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 nicht signierte unterschiedliche Größen kombiniert werden.
smh
quelle
3

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

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

Mats Fredriksson
quelle
Nicht wahr. 6.3.1.8 Übliche arithmetische Konvertierungen Wenn Sie ein int und ein vorzeichenloses Zeichen summieren, wird letzteres in int konvertiert. Wenn Sie zwei vorzeichenlose Zeichen summieren, werden sie in int konvertiert.
2501
3

Bei der Konvertierung von vorzeichenbehaftet in vorzeichenlos gibt es zwei Möglichkeiten. Ursprünglich positive Zahlen bleiben (oder werden als solche interpretiert) der gleiche Wert. Ursprünglich negative Zahlen werden nun als größere positive Zahlen interpretiert.

Tim Ring
quelle
1

Wie bereits beantwortet, können Sie problemlos zwischen signiert und nicht signiert wechseln. Der Grenzfall für vorzeichenbehaftete Ganzzahlen ist -1 (0xFFFFFFFF). Wenn Sie versuchen, das zu addieren und davon zu subtrahieren, werden Sie feststellen, dass Sie zurückwerfen können und es korrekt haben.

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

int iValue, iResult;
unsigned int uValue, uResult;

Es ist viel zu leicht, sich von wichtigeren Themen ablenken zu lassen und zu vergessen, welche Variable welcher Typ ist, wenn sie ohne Hinweis benannt werden. Sie möchten nicht in ein vorzeichenloses Zeichen umwandeln und dieses dann als Array-Index verwenden.

Taylor Price
quelle
0

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 einer guten Definition ja (siehe https://stackoverflow.com/a/50632/5083516 ).

Die Regeln sind in der Regel schwer lesbar geschrieben, aber im Wesentlichen enthält die vorzeichenlose Ganzzahl, unabhängig davon, welche Darstellung in der vorzeichenbehafteten Ganzzahl verwendet wurde, eine 2er-Komplementdarstellung 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 Casting in größere vorzeichenlose Ganzzahltypen haben genau definierte Ergebnisse, aber diese Ergebnisse sind keine 2er-Komplementdarstellungen des "realen Ergebnisses".

(Sicher, in dem Sinne, dass ich, obwohl das Ergebnis in diesem Beispiel zu einer riesigen positiven Zahl überläuft, es auf ein int zurücksetzen und das tatsächliche Ergebnis erhalten könnte.)

Während Konvertierungen von vorzeichenbehafteten in vorzeichenlose durch den Standard definiert sind, ist die Umkehrung implementierungsdefiniert. Sowohl gcc als auch msvc definieren die Konvertierung so, dass Sie das "echte Ergebnis" erhalten, wenn Sie die in einer vorzeichenlosen Ganzzahl gespeicherte Zweierkomplementzahl zurück in eine vorzeichenbehaftete Ganzzahl konvertieren . Ich gehe davon aus, dass Sie nur auf obskuren Systemen ein anderes Verhalten finden, die das 2er-Komplement nicht für vorzeichenbehaftete Ganzzahlen verwenden.

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

Plugwash
quelle
-17

Schreckliche Antworten in Hülle und Fülle

Ozgur Ozcitak

Wenn Sie von signiert zu nicht signiert (und umgekehrt) wechseln, ändert sich die interne Darstellung der Nummer nicht. Was sich ändert, ist, wie der Compiler das Vorzeichenbit interpretiert.

Das ist völlig falsch.

Mats Fredriksson

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

Das ist auch falsch. Vorzeichenlose Ints können zu Ints heraufgestuft werden, wenn sie aufgrund von Füllbits im vorzeichenlosen Typ die gleiche Genauigkeit haben.

smh

Ihre Additionsoperation bewirkt, dass das int in ein vorzeichenloses int konvertiert wird.

Falsch. Vielleicht tut es und vielleicht tut es nicht.

Die Konvertierung von vorzeichenlosem int in signiertes int ist implementierungsabhängig. (Aber es funktioniert wahrscheinlich so, wie Sie es heutzutage auf den meisten Plattformen erwarten.)

Falsch. Es ist entweder ein undefiniertes Verhalten, wenn es einen Überlauf verursacht, oder der Wert bleibt erhalten.

Anonym

Der Wert von i wird in ...

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

Taylor Price

Wie bereits beantwortet, können Sie problemlos zwischen signiert und nicht signiert wechseln.

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

Jetzt kann ich endlich die Frage beantworten.

Sollte die Genauigkeit von int gleich int ohne Vorzeichen sein, wird u zu einem vorzeichenbehafteten int heraufgestuft und Sie erhalten den Wert -4444 aus dem Ausdruck (u + i). Wenn u und ich andere Werte haben, kann es zu einem Überlauf und undefiniertem Verhalten kommen, aber mit diesen genauen Zahlen erhalten Sie -4444 [1]. . Dieser Wert hat den Typ int. Sie versuchen jedoch, diesen Wert in einem vorzeichenlosen Int zu speichern, sodass er dann in ein vorzeichenloses Int umgewandelt wird und der Wert, den das Ergebnis ergibt, (UINT_MAX + 1) - 4444 lautet.

Sollte die Genauigkeit des vorzeichenlosen int größer sein als die eines int, wird das vorzeichenbehaftete int zu einem vorzeichenlosen int heraufgestuft, was den Wert (UINT_MAX + 1) - 5678 ergibt, der zu dem anderen vorzeichenlosen int 1234 addiert wird. Sollten u und ich haben Bei anderen Werten, bei denen der Ausdruck außerhalb des Bereichs {0..UINT_MAX} liegt, wird der Wert (UINT_MAX + 1) entweder addiert oder subtrahiert, bis das Ergebnis innerhalb des Bereichs {0..UINT_MAX) liegt und kein undefiniertes Verhalten auftritt .

Was ist Präzision?

Ganzzahlen haben Füllbits, Vorzeichenbits und Wertbits. Ganzzahlen ohne Vorzeichen haben offensichtlich kein Vorzeichenbit. Es ist weiterhin garantiert, dass vorzeichenloses Zeichen keine Füllbits enthält. Die Anzahl der Wertebits einer Ganzzahl gibt an, wie genau sie ist.

[Fallstricke]

Die Makrogröße des Makros allein kann nicht verwendet werden, um die Genauigkeit einer Ganzzahl zu bestimmen, wenn Füllbits vorhanden sind. Und die Größe eines Bytes muss kein 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 Promotion) - wenn Sie ein vorzeichenloses int haben, das zu groß ist, um in ein int zu passen. Der Überlauf kann auch nach dem Hinzufügen auftreten, selbst wenn das vorzeichenlose int im Bereich eines int lag. Nach dem Hinzufügen kann das Ergebnis dennoch überlaufen.

Elite Mx
quelle
6
"Nicht signierte Ints können zu Ints befördert werden". Nicht wahr. Es findet keine Ganzzahl- Heraufstufung statt, da die Typen bereits Rang> = int sind. 6.3.1.1: "Der Rang eines vorzeichenlosen Integer-Typs muss dem Rang des entsprechenden vorzeichenbehafteten Integer-Typs entsprechen, falls vorhanden." und 6.3.1.8: "Andernfalls wird der Operand mit vorzeichenbehaftetem Integer-Typ in den Typ des Operanden mit vorzeichenloser Ganzzahl konvertiert , wenn der Operand mit vorzeichenlosem Integer-Typ einen Rang hat, der größer oder gleich dem Rang des Typs des anderen Operanden ist Art." beide garantieren, dass intkonvertiert wird, unsigned intwenn die üblichen arithmetischen Konvertierungen gelten.
CB Bailey
1
6.3.1.8 Tritt nur nach einer ganzzahligen Heraufstufung auf. Im ersten Absatz heißt es: "Andernfalls werden die ganzzahligen Heraufstufungen für beide Operanden ausgeführt. DANN werden die folgenden Regeln auf die heraufgestuften Operanden angewendet." Lesen Sie also die Heraufstufungsregeln 6.3.1.1 ... "Ein Objekt oder Ausdruck mit einem ganzzahligen Typ, dessen ganzzahliger Konvertierungsrang kleiner oder gleich dem Rang von int und unsigned int ist" und "Wenn ein int alle Werte von int darstellen kann Originaltyp, der Wert wird in ein int "konvertiert.
Elite Mx
1
6.3.1.1 Ganzzahlige Heraufstufung, die verwendet wird, um einige Ganzzahltypen zu konvertieren, die nicht intoder unsigned intin einen der Typen sind, bei denen etwas vom Typ unsigned intoder interwartet wird. Das "oder gleich" wurde in TC2 hinzugefügt, um aufgezählte Arten von Konvertierungsrang zu ermöglichen, die gleich intoder unsigned intin einen dieser Typen konvertiert werden. Es war nie beabsichtigt, dass die beschriebene Aktion zwischen unsigned intund konvertiert int. Die gemeinsame Typbestimmung zwischen unsigned intund intwird auch nach TC2 durch 6.3.1.8 geregelt.
CB Bailey
19
Falsche Antworten zu posten und gleichzeitig die falschen Antworten anderer zu kritisieren, klingt nicht nach einer guten Strategie, um Arbeit zu bekommen ... ;-)
R .. GitHub STOP HELPING ICE
6
Ich stimme nicht zum Löschen, da dieses Maß an Unrecht in Kombination mit Arroganz zu unterhaltsam ist
MM