Ich habe versucht, das folgende Programm auszuführen:
#include <stdio.h>
int main() {
signed char a = -5;
unsigned char b = -5;
int c = -5;
unsigned int d = -5;
if (a == b)
printf("\r\n char is SAME!!!");
else
printf("\r\n char is DIFF!!!");
if (c == d)
printf("\r\n int is SAME!!!");
else
printf("\r\n int is DIFF!!!");
return 0;
}
Für dieses Programm erhalte ich die Ausgabe:
char ist DIFF !!! int ist gleich !!!
Warum erhalten wir für beide unterschiedliche Ausgänge?
Sollte die Ausgabe wie folgt sein?
char ist gleich !!! int ist gleich !!!
Ein Codepad-Link .
c
types
type-conversion
integer-promotion
signedness
user2522685
quelle
quelle
Antworten:
Dies liegt an den verschiedenen impliziten Typkonvertierungsregeln in C. Es gibt zwei davon, die ein C-Programmierer kennen muss: die üblichen arithmetischen Konvertierungen und die ganzzahligen Heraufstufungen (letztere sind Teil der ersteren).
Im char-Fall haben Sie die Typen
(signed char) == (unsigned char)
. Dies sind beide kleine Ganzzahltypen . Andere solche kleinen ganzzahligen Typen sindbool
undshort
. Die Regeln für die Ganzzahl-Heraufstufung besagen, dass immer dann, wenn ein kleiner Ganzzahl-Typ ein Operand einer Operation ist, sein Typ heraufgestuft wirdint
, was signiert ist. Dies geschieht unabhängig davon, ob der Typ signiert oder nicht signiert war.Im Fall von
signed char
wird das Zeichen beibehalten und auf einenint
Wert mit dem Wert -5 hochgestuft. Im Fall vonunsigned char
enthält es einen Wert, der 251 (0xFB) ist. Es wird auf einenint
Wert mit demselben Wert hochgestuft. Sie enden mitif( (int)-5 == (int)251 )
Im ganzzahligen Fall haben Sie die Typen
(signed int) == (unsigned int)
. Da es sich nicht um kleine Ganzzahltypen handelt, gelten die Ganzzahlaktionen nicht. Stattdessen werden sie durch die üblichen arithmetischen Konvertierungen ausgeglichen , die besagen, dass wenn zwei Operanden den gleichen "Rang" (Größe), aber unterschiedliche Vorzeichen haben, der vorzeichenbehaftete Operand in den gleichen Typ wie der vorzeichenlose konvertiert wird. Sie enden mitif( (unsigned int)-5 == (unsigned int)-5)
quelle
unsigned int
wennint
nicht groß genug, um alle Werte des Typs mit geringerem Conversion-Rang darzustellen; beispielsweise übernehmenint
undshort
sind beide 16-Bit - Typen; dann die Umwandlung vonunsigned short
zuint
können Werte im allgemeinen erhalten, so dass wir mit gehenunsigned int
stattint
wenn ich die Variablen explizit wieif((unsigned char) 128 == (signed char) -128)
?(unsigned short)((unsigned short)65535u * (unsigned short)65535u)
die 1 ergibt, anstatt die Atomsprengköpfe zu starten? Gibt es eine Möglichkeit, die unteren 16 Bit des Produkts aus zwei 16-Bit-Zahlen zu berechnen, die auf einem 16-Bit-Computer effizient, auf einem 32-Bit-Computer jedoch garantiert korrekt sind?Coole Frage!
Der
int
Vergleich funktioniert, da beide Ints genau die gleichen Bits enthalten und daher im Wesentlichen gleich sind. Aber was ist mit demchar
s?Ah, C fördert implizit
char
s zuint
s bei verschiedenen Gelegenheiten. Dies ist einer von ihnen. Ihr Code sagtif(a==b)
, aber was der Compiler tatsächlich daraus macht, ist:if((int)a==(int)b)
(int)a
ist -5, aber(int)b
ist 251. Das sind definitiv nicht die gleichen.EDIT: Wie @ Carbonic-Acid hervorhob,
(int)b
ist 251 nur, wenn achar
8 Bit lang ist. Wennint
es 32 Bit lang ist,(int)b
ist -32764.REDIT: Es gibt eine ganze Reihe von Kommentaren, die die Art der Antwort diskutieren, wenn ein Byte nicht 8 Bit lang ist. Der einzige Unterschied in diesem Fall ist, dass
(int)b
nicht 251, sondern eine andere positive Zahl ist, die nicht -5 ist. Dies ist nicht wirklich relevant für die Frage, die immer noch sehr cool ist.quelle
int
Vergleich funktioniert nicht, weil die Variablen dieselben Bits enthalten, sondern weil ihre Werte nach der Konvertierung gleich sind. Die C-Sprache kümmert sich meistens nicht um die Repräsentation -(unsigned)-1 == UINT_MAX
gilt auch dann, wenn eine Repräsentation in Vorzeichengröße verwendet wird, bei der die Konvertierung im Gegensatz zum Zweierkomplement kein Noop istWillkommen bei der Ganzzahl-Promotion . Wenn ich von der Website zitieren darf:
C kann sehr verwirrend sein, wenn Sie Vergleiche wie diese anstellen. Ich habe kürzlich einige meiner Nicht-C-Programmierfreunde mit folgendem Scherz verwirrt:
#include <stdio.h> #include <string.h> int main() { char* string = "One looooooooooong string"; printf("%d\n", strlen(string)); if (strlen(string) < -1) printf("This cannot be happening :("); return 0; }
Was tatsächlich druckt
This cannot be happening :(
und anscheinend zeigt, dass 25 kleiner als -1 ist!Was jedoch darunter passiert, ist, dass -1 als vorzeichenlose Ganzzahl dargestellt wird, die aufgrund der zugrunde liegenden Bitdarstellung auf einem 32-Bit-System gleich 4294967295 ist. Und natürlich ist 25 kleiner als 4294967295.
Wenn wir jedoch den von zurückgegebenen
size_t
Typ explizitstrlen
als vorzeichenbehaftete Ganzzahl umwandeln:if ((int)(strlen(string)) < -1)
Dann wird es 25 gegen -1 vergleichen und alles wird gut mit der Welt.
Ein guter Compiler sollte Sie vor dem Vergleich zwischen einer vorzeichenlosen und einer vorzeichenbehafteten Ganzzahl warnen, und dennoch ist es so leicht zu übersehen (insbesondere, wenn Sie keine Warnungen aktivieren).
Dies ist besonders verwirrend für Java-Programmierer, da alle primitiven Typen dort signiert sind. Hier ist, was James Gosling (einer der Schöpfer von Java) zu diesem Thema zu sagen hatte :
quelle
Byte
Typen in Pascal keinerlei Schwierigkeiten bereiteten. Die einzigen vorzeichenlosen Typen, die überhaupt problematisch sind, sind solche, die größer als die Standardgröße für Ganzzahlen sind.(int)(strlen(string)) < -1
funktioniert. Sie haben nur die linke Seite des Vergleichs geändert, die rechte Seite ist nach vorheriger Erklärung immer noch 4294967295. (cc @Xolve)strlen(string)
gibt einsize_t
Ergebnis zurück, das nicht als dargestellt werden kannint
. Somit werden alle Operanden des Vergleichs zu befördertunsigned int
.Die hexadezimale Darstellung von
-5
ist:signed char
:0xfb
signed int
:0xfffffffb
Wenn Sie eine vorzeichenbehaftete Nummer in eine vorzeichenlose Nummer konvertieren oder umgekehrt, tut der Compiler ... genau nichts. Was gibt es zu tun? Die Zahl ist entweder konvertierbar oder nicht. In diesem Fall folgt ein undefiniertes oder implementierungsdefiniertes Verhalten (ich habe nicht überprüft, welches), und das effizienteste implementierungsdefinierte Verhalten besteht darin, nichts zu tun.
Die hexadezimale Darstellung von
(unsigned <type>)-5
lautet also:unsigned char
:0xfb
unsigned int
:0xfffffffb
Ähnlich aussehend? Sie sind Stück für Stück die gleichen wie die signierten Versionen.
Wenn Sie schreiben
if (a == b)
, woa
undb
vom Typchar
, muss der Compiler tatsächlich lesenif ((int)a == (int)b)
. (Dies ist die "Integer-Promotion", auf die sich alle anderen einlassen.)Also, was passiert , wenn wir konvertieren
char
zuint
?signed char
bis 32-Bitsigned int
:0xfb
->0xfffffffb
-5
oben entspricht!unsigned char
bis 32-Bitsigned int
:0xfb
->0x000000fb
Also
a == b
wirklich0xfffffffb == 0x000000fb
= = keine Übereinstimmung!Und
c == d
stimmt wirklich0xfffffffb == 0xfffffffb
überein =>!quelle
"Otherwise, if the new type is unsigned, the value is converted by repeatedly adding or subtracting one more than the maximum value that can be represented in the new type until the value is in the range of the new type."
When a value with integer type is converted to another integer type other than _Bool, if the value can be represented by the new type, it is unchanged.
/ - /"Otherwise, the new type is signed and the value cannot be represented in it; either the result is implementation-defined or an implementation-defined signal is raised."
Mein Punkt ist: Haben Sie beim Kompilieren keine Warnung erhalten, "signierten und nicht signierten Ausdruck zu vergleichen"?
Der Compiler versucht Ihnen mitzuteilen, dass er berechtigt ist, verrückte Sachen zu machen! :) Ich würde hinzufügen, verrücktes Zeug wird mit großen Werten passieren, nahe an der Kapazität des primitiven Typs. Und
unsigned int d = -5;
weist d definitiv einen großen Wert zu, es ist äquivalent (auch wenn es wahrscheinlich nicht garantiert äquivalent ist) zu sein:
unsigned int d = UINT_MAX -4; ///Since -1 is UINT_MAX
Bearbeiten:
Es ist jedoch interessant festzustellen, dass nur der zweite Vergleich eine Warnung ausgibt (überprüfen Sie den Code) . Dies bedeutet, dass der Compiler, der die Konvertierungsregeln anwendet, sicher ist, dass beim Vergleich zwischen
unsigned char
und keine Fehler auftretenchar
(während des Vergleichs werden sie in einen Typ konvertiert, der alle möglichen Werte sicher darstellen kann). Und in diesem Punkt hat er Recht. Dann werden Sie darüber informiert, dass dies nicht der Fall istunsigned int
undint
: Während des Vergleichs wird eine der beiden in einen Typ konvertiert, der sie nicht vollständig darstellen kann.Der Vollständigkeit halber habe ich es auch kurz überprüft : Der Compiler verhält sich genauso wie bei Zeichen, und wie erwartet treten zur Laufzeit keine Fehler auf.
.
Im Zusammenhang mit diesem Thema habe ich kürzlich diese Frage gestellt (noch C ++ -orientiert).
quelle