8

Ich habe den folgenden Code in dem Buch Computer Systems gesehen: Eine Perspektive des Programmierers, 2/E. Dies funktioniert gut und erzeugt die gewünschte Ausgabe. Die Ausgabe kann durch die Differenz von Vorzeichen und Vorzeichen erklärt werden.Was ist der Unterschied zwischen Literalen und Variablen in C (signed vs unsigned short ints)?

#include<stdio.h> 
int main() { 
    if (-1 < 0u) { 
     printf("-1 < 0u\n"); 
    } 
    else { 
     printf("-1 >= 0u\n"); 
    } 
    return 0; 
} 

Der obige Code Ausbeuten -1 >= 0u, der folgende Code jedoch, die die gleichen wie oben sein soll, nicht! Mit anderen Worten,

#include <stdio.h> 

int main() { 

    unsigned short u = 0u; 
    short x = -1; 
    if (x < u) 
     printf("-1 < 0u\n"); 
    else 
     printf("-1 >= 0u\n"); 
    return 0; 
} 

ergibt -1 < 0u. Warum ist das passiert? Ich kann das nicht erklären.

Beachten Sie, dass ich ähnliche Fragen wie this gesehen habe, aber sie helfen nicht.

PS. Wie @Abhineet sagte, kann das Dilemma gelöst werden, indem man short zu int ändert. Wie aber kann man dieses Phänomen erklären? Mit anderen Worten, -1 in 4 Bytes ist 0xff ff ff ff und in 2 Bytes ist 0xff ff. Sind sie als 2s-Komplement angegeben, die als unsigned interpretiert werden, haben sie entsprechende Werte von 4294967295 und 65535. Sie beide sind nicht weniger als 0 und ich denke, in beiden Fällen muss die Ausgabe -1 >= 0u sein, d. H. x >= u.

Eine Beispielausgabe für sie auf ein Little-Endian-Intel-System:

kurz:

-1 < 0u 
u = 
00 00 
x = 
ff ff 

Für int:

-1 >= 0u 
u = 
00 00 00 00 
x = 
ff ff ff ff 
+1

[Ähnliche Frage] (http://stackoverflow.com/questions/17312545/type-conversion-unsigned-to-signed-int-char). – Lundin

+4

C verhält sich in Bezug auf * Werte *, nicht Darstellungen. All das Zeug über das Zweierkomplement und ffff und 65535 usw. ist irrelevant. –

+0

Verwenden Sie keine Codeformatierung für Text, der kein Code ist. – EJP

Antwort

10

Der obige Code Ausbeuten -1> = 0U

Alle Ganzzahlliterale (numerisch constansts) eine Art und damit auch eine Signedness. Standardmäßig sind sie vom Typ int, der signiert ist. Wenn Sie das Suffix u anhängen, drehen Sie das Literal in unsigned int.

Für jeden C-Ausdruck, in dem Sie einen signierten und einen ungesicherten Operanden haben, konvertiert die Regel für den Ausgleich (formal: the usual arithmetic conversions) den signierten Typ implizit in unsigned.

Umwandlung von in unsigned unterzeichnet ist wohldefiniert (6.3.1.3):

Andernfalls, wenn der neue Typ unsigned ist, wird der Wert durch wiederholtes Addieren oder umgewandelt eine mehr als der Maximalwert subtrahiert Dies kann im neuen Typ dargestellt werden, bis der Wert im Bereich des neuen Typs liegt.

beispielsweise für 32-Bit-Zahlen auf einem Komplementsystems Standard zwei, der Max-Wert einer ganzen Zahl ohne Vorzeichen ist 2^32 - 1 (4294967295, UINT_MAX in limits.h). Ein mehr als der maximale Wert ist 2^32. Und -1 + 2^32 = 4294967295, so wird das Literal -1 zu einem vorzeichenlosen int mit dem Wert 4294967295 konvertiert. Die größer ist als 0.


ist Wenn Sie Typen kurz aber wechseln, erhalten Sie mit einem kleinen Integer-Typ auf. Dies ist der Unterschied zwischen den beiden Beispielen. Jedes Mal, wenn ein kleiner Integer-Typ Teil eines Ausdruck ist, die integer Förderung Regel implizit konvertiert es in eine größeren int (6.3.1.1):

Wenn ein int alle Werte des ursprünglichen Typs darstellen kann (wie eingeschränkt durch die Breite, für ein Bit-Feld), wird der Wert in ein int konvertiert; Andernfalls wird es in einen unsigned int konvertiert. Diese werden Integer-Promotions genannt. Alle anderen Typen sind unverändert durch die Ganzzahl Promotionen.

Wenn short kleiner als int auf der jeweiligen Plattform (wie im Fall von 32 und 64 Bit-Systeme), jede short oder unsigned short wird daher immer auf int umgewandelt werden, weil sie in einem passen.

Also für den Ausdruck if (x < u), enden Sie tatsächlich mit if((int)x < (int)u), die sich wie erwartet verhält (-1 ist kleiner als 0).

+0

Danke. Dies erklärt den Fall. Ich frage mich jedoch, warum die Designer auf diese Weise entschieden haben? Hast du eine Idee? –

+2

@AliShakiba: Wenn Sie auf verschiedene Operanden treffen, können Sie entweder entscheiden, dass Sie beide Operanden in "signed" oder beide Operanden in "unsigned" konvertieren, bevor Sie mit dem Vergleich fortfahren. Da 'signed int' der Standardtyp ist, bedeutet die Verwendung eines' unsigned'-Literals für einen der Operanden einen Grund, dieses zusätzliche Qualifikationsmerkmal anzugeben. Und da C so entwickelt wurde, dass es "nah an der Hardware" ist, ist es natürlich, alles in die native Wortgröße einer Plattform einzupassen, um entsprechende Anweisungen verwenden zu können (die Operandentypen nicht "mischen" und typischerweise Wort- Größenoperanden). – Groo

+2

@AliShakiba Das ursprüngliche Grundprinzip hinter Integer-Promotion war etwas wie: Wenn Sie zum Beispiel 'char x = 200, char y = 200;' und dann 'x + y' haben, dann würde der Ausdruck nicht überlaufen. Integerpromotion ist eine Typinkonsistenz in der C-Sprache und hat über die Jahre weit mehr Schaden als Nutzen angerichtet, da stille implizite Promotion-Bugs _much_ schwerer zu finden sind als einfache Integer-Overflow-Bugs. Auch die impliziten Typ-Promotion-Regeln sind etwas komplex und es gibt daher viele C-Programmierer, die nicht wissen, wie sie arbeiten, was bedauerlich ist. – Lundin

0

0u ist nicht unsigned short, dann ist es unsigned int.

Bearbeiten :: Die Erklärung für das Verhalten, How comparison is performed ?

Wie von Jens Gustedt antwortete

Dies wird als "üblichen arithmetischen Umwandlungen" von der Norm genannt und gilt immer dann, wenn zwei verschiedene Integer-Typen treten als Operanden des gleichen Operators auf.

Im Wesentlichen was tut

wenn die Typen unterschiedliche Breite aufweisen (genauer gesagt, was der Standard- Anrufe Umwandlung Rang), dann wandelt sie in den breiteren Typ, wenn beide Arten von gleicher Breite sind, neben wirklich seltsam Architekturen , die unsigned von ihnen gewinnt Signed to vorzeichenlose Konvertierung des Werts -1 mit welchem ​​Typ immer immer den höchsten darstellbaren Wert des unsigned Typ.

Der mehr erklärende Blog von ihm geschrieben könnte here gefunden werden.

+0

Danke @Abhinet. Stimmt. Ich bin jedoch neugierig, warum das passiert ist? '-1' in 4 Bytes ist' 0xff ffff ff' und in 2 Bytes '0xff ff'. Wenn sie als 2s-Komplement angegeben werden, die als "unsigned" interpretiert werden, haben sie entsprechende Werte von "4294967295" und "65535". Beide sind nicht kleiner als "0" und ich denke, in beiden Fällen muss die Ausgabe "-1> = 0u" sein, d. H. "X> = u". –

+1

Dies beantwortet die Frage nicht und erklärt nicht, warum 'int'-Variablen ein anderes Ergebnis als' short'-Variablen ergeben. –

3

Sie stoßen auf C's Integer-Promotion-Regeln.

Operatoren, die kleiner als int sind, befördern ihre Operanden automatisch auf int oder unsigned int. Siehe Kommentare für detailliertere Erklärungen. Es gibt einen weiteren Schritt für binäre Operatoren (zwei Operanden), wenn die Typen danach immer noch nicht übereinstimmen (z.B. unsigned int vs. int). Ich werde nicht versuchen, die Regeln detaillierter zusammenzufassen. Siehe Lundins Antwort.

This blog post deckt dies ausführlicher ab, mit einem ähnlichen Beispiel wie Ihres: signed und unsigned char. Er zitiert die C99-Spezifikation:

Wenn ein int alle Werte des ursprünglichen Typs darstellen kann, wird der Wert in ein int konvertiert; Andernfalls wird es in einen unsigned int konvertiert. Diese werden als ganzzahlige Werbeaktionen bezeichnet. Alle anderen Typen sind unverändert durch die Ganzzahlspromotions.


Sie können mit dieser leichter auf so etwas wie Godbolt rumspielen, with a function that returns one or zero. Sehen Sie sich die Compiler-Ausgabe an, um zu sehen, was passiert.

#define mytype short 

int main() { 
    unsigned mytype u = 0u; 
    mytype x = -1; 
    return (x < u); 
} 
+0

Das ist ein guter Punkt. Wie im Beispiel am Ende der Frage gezeigt, sind "kurz" und "unsigned short" jeweils zwei Bytes, aber mit unterschiedlichen Interpretationen. Danke für den Link. –

+0

@AliShakiba: Die Regeln für die Integer-Promotion können nicht intuitiv sein. Es * ist * der Grund, warum Sie unterschiedliche Ergebnisse mit kurzen vars erhalten (beide werden zu int hochgestuft) vs. int vars (int kann nicht alle möglichen vorzeichenlosen Ints darstellen). –

+3

Es gibt hier eigentlich zwei Arten von Regeln: Die "Integer-Promotions" fördern sowohl "short" als auch "unsigned short" zu "int" (auf dieser Plattform), und die "üblichen arithmetischen Konvertierungen" propagieren "int" zu "unsigned" wenn es mit 'unsigned' verglichen wird.Die meisten Operatoren führen die "ganzzahligen Promotionen" und dann die "üblichen arithmetischen Konvertierungen" durch. Die bemerkenswerten Ausnahmen sind die Bit-Shift-Operatoren, die nur Integer-Promotions ausführen. –

2

Andere als das, was Sie zu übernehmen scheinen, ist dies nicht eine Eigenschaft der jeweiligen Breite der Typen, hier 2 Byte im Vergleich zu 4 Bytes, sondern eine Frage der Regeln, die angewandt werden sollen. Die Ganzzahl-Werberichtlinien geben an, dass short und unsigned short auf allen Plattformen, auf denen der entsprechende Wertebereich in int passt, in int konvertiert werden. Da dies hier der Fall ist, bleiben beide Werte erhalten und erhalten den Typ int. -1 ist perfekt in int wie 0 darstellbar. Daher sind die Testergebnisse in -1 kleiner als 0.

Bei der Prüfung -1 gegen 0u wird bei der allgemeinen Konvertierung der Typ unsigned als gemeinsamer Typ gewählt, in den beide konvertiert werden. -1 konvertiert in unsigned ist der Wert UINT_MAX, der größer als 0u ist.

Dies ist ein gutes Beispiel, warum Sie niemals "schmale" Typen verwenden sollten, um Arithmetik oder Vergleich durchzuführen. Verwenden Sie sie nur, wenn Sie eine Servergrößenbeschränkung haben. Dies wird für einfache Variablen selten der Fall sein, aber meistens für große Arrays, bei denen Sie wirklich von einem Speichern in einem engen Typ profitieren können.

+0

Speichern von Daten in engen Typen in Arrays ist großartig. Das Laden von Werten aus dem Array in enge lokale Variablen ist normalerweise nicht gut. Das Laden von Array-Werten in lokale 'int'-Variablen verhindert, dass man sich um ganzzahlige Promotion-Regeln kümmern muss; nur die üblichen Regeln für signed vs. unsigned int. x86 hat zumindest effiziente Anweisungen, die ein int8_t oder int16_t während des Ladens aus dem Speicher in ein Register sign-extendieren. IDK über ARM oder andere wichtige Architekturen. –

Verwandte Themen