Frage

Hier sind zwei sehr einfache Programme. Ich würde erwarten, die gleiche Ausgabe zu bekommen, aber ich nicht. Ich kann nicht herausfinden, warum. Der erste gibt 251 aus. Die zweite gibt -5 aus. Ich kann verstehen, warum der 251. Ich verstehe jedoch nicht, warum das zweite Programm mir eine -5 gibt.

Programm 1:

#include <stdio.h>

int main()
{

unsigned char  a;
unsigned char  b;
unsigned int  c;

a = 0;
b= -5;

c =  (a + b);

printf("c hex: %x\n", c);
printf("c dec: %d\n",c);

}

Ausgabe:

c hex: fb
c dec: 251

Programm 2:

#include <stdio.h>

int main()
{

unsigned char  a;
unsigned char  b;
unsigned int  c;

a = 0;
b=  5;

c =  (a - b);

printf("c hex: %x\n", c);
printf("c dec: %d\n",c);

}

Ausgabe:

c hex: fffffffb
c dec: -5
War es hilfreich?

Lösung

Hier gibt es zwei getrennte Probleme. Die erste ist die Tatsache, dass Sie unterschiedliche Hex -Werte für die gleichen Operationen erhalten. Die zugrunde liegende Tatsache, dass Sie fehlen, ist das chars werden zu befördert zu ints (wie sind shorts) Arithmetik zu machen. Hier ist der Unterschied:

a = 0  //0x00
b = -5 //0xfb
c = (int)a + (int)b

Hier, a wird erweitert auf 0x00000000 und b wird erweitert auf 0x000000fb (nicht Zeichen erweitert, weil es ein ist ohne Vorzeichen verkohlen). Dann wird die Ergänzung durchgeführt, und wir bekommen wir 0x000000fb.

a = 0  //0x00
b = 5  //0x05
c = (int)a - (int)b

Hier, a wird erweitert auf 0x00000000 und b wird erweitert auf 0x00000005. Dann wird die Subtraktion durchgeführt, und wir bekommen wir 0xfffffffb.

Die Lösung? Bleiben bei chars oder ints; Wenn Sie sie mischen, können Sie Dinge verursachen, die Sie nicht erwarten werden.

Das zweite Problem ist, dass eine unsigned int wird als gedruckt als -5, eindeutig ein signierter Wert. In der Saite haben Sie es jedoch gesagt printf Um sein zweites Argument zu drucken, interpretiert als signiertes int (das ist was "%d" meint). Der Trick hier ist das printf weiß nicht, in welchen Arten der Variablen, in denen Sie bestanden haben, interpretiert sie lediglich so, wie die Zeichenfolge sie sagt. Hier ist ein Beispiel, das wir erzählen printf Um einen Zeiger als int zu drucken:

int main()
{
    int a = 0;
    int *p = &a;
    printf("%d\n", p);
}

Wenn ich dieses Programm ausführe, bekomme ich jedes Mal einen anderen Wert, was der Speicherort von ist a, konvertiert in Basis 10. Sie können feststellen, dass so etwas eine Warnung verursacht. Sie sollten alle Warnungen lesen, die Ihr Compiler Ihnen gibt, und sie nur ignorieren, wenn Sie sich ganz sicher sind, dass Sie das tun, was Sie beabsichtigen.

Andere Tipps

Im ersten Programm, b=-5; weist 251 zu b. (Konvertierungen in einen nicht signierten Typ reduzieren immer den Wertmodulo plus den maximalen Wert des Zieltyps.)

Im zweiten Programm, b=5; Zuweist einfach 5 zu 5 zu b, dann c = (a - b); führt die Subtraktion 0-5 durch als Typ int Aufgrund der Standardaktionen - einfach "kleiner als int"Typen werden immer befördert zu int Bevor sie als Operanden von Arithmetik- und Bitgewise -Operatoren verwendet werden.

Bearbeiten: Eine Sache, die ich vermisst habe: Seitdem c hat Typ unsigned int, Das Ergebnis -5 im zweiten Programm wird auf konvertiert unsigned int Wenn die Zuordnung zu c wird durchgeführt, was dazu führt UINT_MAX-4. Das sehen Sie mit dem %x Spezifizierer zu printf. Beim Drucken c mit %d, Sie erhalten undefiniertes Verhalten, weil %d erwartet a (signiert) int Argument und Sie haben eine bestanden unsigned int Argument mit einem Wert, der in klar darstellbar ist (signiert) int.

Sie verwenden den Formatspezifizierer %d. Das behandelt das Argument als signierte Dezimalzahl (im Grunde genommen int).

Sie erhalten 251 aus dem ersten Programm, weil (unsigned char)-5 ist 251, dann drucken Sie es wie eine signierte Dezimalstellen. Es wird zu 4 Bytes anstelle von 1 befördert, und diese Bits sind 0, also sieht die Nummer so aus 0000...251 (bei dem die 251 ist binär, ich habe es einfach nicht konvertiert).

Sie erhalten -5 aus dem zweiten Programm, weil (unsigned int)-5 ist ein großer Wert, aber an eine gegossen int, es ist -5. Es wird wie ein int behandelt, weil Sie verwendet werden printf.

Verwenden Sie den Formatspezifizierer %ud Drucken Sie vorzeichenlose Dezimalwerte.

Was Sie sehen, ist das Ergebnis von Wie die zugrunde liegende Maschine die Zahlen darstellt Wie der C -Standard für nicht signierte Typumbauten (für die Arithmetik) signiert ist und wie die zugrunde liegende Maschine Zahlen darstellt (für das Ergebnis des undefinierten Verhaltens am Ende).

Als ich ursprünglich meine Antwort schrieb, hatte ich angenommen, dass der C -Standard nicht explizit festgelegt hat, wie signierte Werte in nicht signierte Werte konvertiert werden sollten, da Der Standard definiert nicht, wie signierte Werte dargestellt werden sollten oder wie nicht signierte Werte in signierte Werte konvertiert werden, wenn der Bereich außerhalb der des signierten Typs liegt.

Es stellt sich jedoch heraus, dass der Standard explizit definiert, wenn sie von negativen zu positiven unsignierten Werten konvertiert werden. Bei einer Ganzzahl wird ein negativer signierter Wert X in Uint_MAX+1-X konvertiert, als wäre er als signierter Wert in zweier Komplement gespeichert und dann als vorzeichenloser Wert interpretiert.

Also, wenn Sie sagen:

unsigned char  a;
unsigned char  b;
unsigned int c;

a = 0; 
b = -5;
c = a + b;

Der Wert von B wird 251, da -5 in einen nicht signierten Wert des Wertes uchar_max-5+1 (255-5+1) unter Verwendung des C-Standards umgewandelt wird. Nach dieser Konvertierung findet der Zugabe statt. Das macht a + b so wie 0 + 251, was dann in c gespeichert wird. Wenn Sie jedoch sagen:

unsigned char  a;
unsigned char  b;
unsigned int c;

a = 0;
b = 5;
c = (a-b);

printf("c dec: %d\n", c);

In diesem Fall werden A und B zu unsignierten INTs befördert, um mit C übereinzustimmen, so dass sie 0 und 5 Wert bleiben. 0 - 5 in unsigned Ganzzahlmathematik führt jedoch zu einem Unterlauffehler, der so definiert ist, dass sie zu UINT_MAX+1-5 führen. Wenn dies vor der Promotion geschehen wäre, wäre der Wert uchar_max+1-5 (dh erneut 251).

Der Grund, warum Sie in Ihrer Ausgabe -5 sehen, ist jedoch eine Kombination aus der Tatsache, dass die nicht signierte Ganzzahl uint_max -4 und -5 die gleiche exakte binäre Darstellung aufweist, genau wie -5 und 251, die mit einem Einzelbyte -Datenatyp und mit einem Einzelbyte -Datentyp tätig sind, und Die Tatsache, dass bei der Verwendung von "%d" als Formatierungszeichenfolge Printf den Wert von C als signierte Ganzzahl anstelle einer unsignierten Ganzzahl interpretiert.

Da eine Konvertierung von nicht signierten Werten zu signierten Werten für ungültige Werte nicht definiert ist, wird das Ergebnis implementierungsspezifisch. Da in Ihrem Fall die zugrunde liegende Maschine zwei Komplement für signierte Werte verwendet, wird das Ergebnis, dass der nicht signierte Wert uint_max -4 zum signierten Wert -5 wird.

Der einzige Grund, warum dies im ersten Programm nicht geschieht, da ein nicht signiertes INT und ein signiertes INT beide 251 darstellen können, so dass die Konvertierung zwischen den beiden gut definiert ist und "%d" oder "%u" keine Rolle spielt, spielt keine Rolle. Im zweiten Programm führt dies jedoch zu undefiniertem Verhalten und wird implementierungsspezifisch, da Ihr Wert von UINT_MAX-4 den Bereich eines signierten INT außerhalb des Gebiets verlag hat.

Was passiert unter der Motorhaube?

Es ist immer gut, zu überprüfen, was Ihrer Meinung nach passiert oder was mit dem passieren soll, was tatsächlich passiert. Schauen wir uns also die Ausgabe der Montagesprachen des Compilers jetzt an, um genau zu sehen, was vor sich geht. Hier ist der sinnvolle Teil des ersten Programms:

    mov     BYTE PTR [rbp-1], 0   ; a becomes 0
    mov     BYTE PTR [rbp-2], -5  ; b becomes -5, which as an unsigned char is also 251
    movzx   edx, BYTE PTR [rbp-1] ; promote a by zero-extending to an unsigned int, which is now 0
    movzx   eax, BYTE PTR [rbp-2] ; promote b by zero-extending to an unsigned int which is now 251
    add     eax, edx  ; add a and b, that is, 0 and 251

Beachten Sie, dass wir, obwohl wir einen signierten Wert von -5 in dem Byte B speichern, wenn der Compiler ihn fördert, die Zahl durch Null fördert, was bedeutet, dass er als vorzeichenloser Wert interpretiert wird, den 11111011 anstelle des signierten Werts darstellt. Dann werden die geförderten Werte zusammen addiert, um c zu werden. Dies ist auch der Grund, warum der C -Standard für nicht signierte Konvertierungen so definiert ist, wie er es tut - es ist einfach, die Konvertierungen auf Architekturen zu implementieren, die das Komplement von zwei für signierte Werte verwenden.

Jetzt mit Programm 2:

    mov     BYTE PTR [rbp-1], 0 ; a = 0
    mov     BYTE PTR [rbp-2], 5 ; b = 5
    movzx   edx, BYTE PTR [rbp-1] ; a is promoted to 32-bit integer with value 0
    movzx   eax, BYTE PTR [rbp-2] ; b is promoted to a 32-bit integer with value 5
    mov     ecx, edx 
    sub     ecx, eax ; a - b is now done as 32-bit integers resulting in -5, which is '4294967291' when interpreted as unsigned

Wir sehen, dass A und B erneut vor einer Arithmetik beworben werden. Daher subtrahieren wir zwei unsignierte INTs, was zu einem Uint_MAX -4 aufgrund des Unterlaufs führt, was ebenfalls -5 als signierter Wert ist. Unabhängig davon, ob Sie es als signierte oder nicht signierte Subtraktion interpretieren, stimmt das Ergebnis mit der Maschine mit zwei Komplementformulierungen mit dem C -Standard ohne zusätzliche Konvertierungen überein.

Die Zuweisung einer negativen Zahl einer unsignierten Variablen ist im Grunde die Regeln. Was Sie tun, ist die negative Zahl in eine große positive Zahl. Sie sind technisch nicht einmal garantiert, dass die Konvertierung von einem Prozessor zum anderen gleich ist - auf dem Komplementsystem eines Einsten (falls noch existierte) Sie einen anderen Wert erhalten, z. B.

Also bekommst du, was du bekommst. Sie können nicht erwarten, dass die signierte Algebra noch angewendet wird.

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