Typumwandlung – unsigned to signed int/char

Lesezeit: 10 Minuten

Benutzeravatar von user2522685
Benutzer2522685

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 Ausgaben?
Sollte die Ausgabe wie folgt sein?

char ist GLEICH!!! int ist GLEICH!!!

EIN Codepad-Link.

  • Die Förderung des impliziten Integer-Typs schlägt erneut zu!

    – Mystisch

    26. Juni 2013 um 5:59 Uhr

  • Ausführliche Erklärung hier.

    – Ludin

    17. Mai 2018 um 11:32 Uhr

Benutzeravatar von Lundin
Lundin

Dies liegt an den verschiedenen impliziten Typkonvertierungsregeln in C. Es gibt zwei davon, die ein C-Programmierer kennen muss: die üblichen arithmetischen Umrechnungen und die ganzzahlige Aktionen (letztere sind Teil der ersteren).

Im Zeichenfall haben Sie die Typen (signed char) == (unsigned char). Das sind beides kleine Integer-Typen. Andere solche kleinen Integer-Typen sind bool und short. Das Integer-Promotion-Regeln Geben Sie an, dass immer dann, wenn ein kleiner Integer-Typ ein Operand einer Operation ist, sein Typ zu befördert wird int, die signiert ist. Dies geschieht unabhängig davon, ob der Typ signiert oder unsigniert war.

Im Falle der signed charwird das Zeichen beibehalten und zu einem heraufgestuft int mit dem Wert -5. Im Falle der unsigned char, es enthält einen Wert, der 251 (0xFB ) ist. Es wird zu einem befördert int denselben Wert enthalten. Sie enden mit

if( (int)-5 == (int)251 )

Im ganzzahligen Fall haben Sie die Typen (signed int) == (unsigned int). Sie sind keine kleinen Integer-Typen, daher gelten die Integer-Promotions nicht. Stattdessen werden sie durch ausgeglichen die üblichen arithmetischen Umrechnungen, die besagen, dass, wenn zwei Operanden denselben “Rang” (Größe), aber unterschiedliche Vorzeichen haben, der vorzeichenbehaftete Operand in denselben Typ wie der vorzeichenlose umgewandelt wird. Sie enden mit

if( (unsigned int)-5 == (unsigned int)-5)

  • leichte Ungenauigkeit: Es ist auch möglich, zu befördern unsigned int wenn int ist nicht groß genug, um alle Werte des Typs mit niedrigerem Conversion-Rang darzustellen; zum Beispiel annehmen int und short sind beide 16-Bit-Typen; dann Umwandlung von unsigned short zu int kann Werte im Allgemeinen nicht bewahren, also gehen wir mit unsigned int stattdessen

    – Christoph

    26. Juni 2013 um 8:17 Uhr

  • Fördert es int wenn ich explizit die Variablen wie caste if((unsigned char) 128 == (signed char) -128)?

    – noufal

    28. Oktober 2013 um 4:09 Uhr

  • @noufal Ja. Es gibt keine Möglichkeit, die Integer-Promotion durch Code zu vermeiden. Das einzige, was Sie tun können, ist, das Ergebnis der Operation in den beabsichtigten Typ umzuwandeln, der zufällig 100 % sicher ist. Der Compiler kann die Heraufstufung jedoch wegoptimieren, solange sie das Ergebnis des Ergebnisses nicht ändert. Was wiederum bedeutet, dass unerwartete Nebeneffekte wie stille Änderungen der Signierung auch im optimierten Code vorhanden sind.

    – Ludin

    28. Oktober 2013 um 11:56 Uhr

  • @Lundin: Gibt es dafür eine Garantie (unsigned short)((unsigned short)65535u * (unsigned short)65535u) würde 1 ergeben, anstatt die Atomsprengköpfe zu starten? Wäre es möglich, die unteren 16 Bits des Produkts zweier 16-Bit-Zahlen zu berechnen, wäre dies auf einem 16-Bit-Computer effizient, aber auf einem 32-Bit-Computer garantiert korrekt?

    – Superkatze

    2. Juni 2014 um 19:23 Uhr

Benutzeravatar von zmbq
zmbq

Coole Frage!

Das int Der Vergleich funktioniert, weil beide Ints genau die gleichen Bits enthalten, also im Wesentlichen gleich sind. Aber was ist mit der chars?

Ah, C fördert implizit chars zu ints bei verschiedenen Gelegenheiten. Dies ist einer von ihnen. Dein Code sagt if(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.

BEARBEITEN: Wie @Carbonic-Acid betonte, (int)b ist 251 nur, wenn a char ist 8 Bit lang. Wenn int ist 32 Bit lang, (int)b ist -32764.

REDIT: Es gibt eine ganze Reihe von Kommentaren, in denen die Art der Antwort diskutiert wird, wenn ein Byte nicht 8 Bit lang ist. Der einzige Unterschied in diesem Fall ist der (int)b ist nicht 251, sondern eine andere positiv Zahl, die nicht -5 ist. Dies ist nicht wirklich relevant für die Frage, die immer noch sehr cool ist.

  • Stimmt, aber ich möchte OP lieber nicht verwirren. Eine kleine Bemerkung füge ich hinzu.

    – zmbq

    26. Juni 2013 um 6:04 Uhr

  • Wo haben Sie in den letzten 40 Jahren ein Byte mit mehr als 8 Bit gesehen?

    – zmbq

    26. Juni 2013 um 6:10 Uhr

  • @ user2522685 Weil die C-Sprache es verlangt und die C-Sprache weder rational, konsistent noch logisch ist.

    – Ludin

    26. Juni 2013 um 6:36 Uhr

  • @zmbq: DSPs müssen keine 8 Bit pro Byte haben, Unisys ist immer noch im Mainframe-Geschäft, es gibt einige seltsame Forth-Prozessoren (die jedoch keinen C-Compiler haben müssen) – wenn Sie genau genug suchen, können Sie immer noch solche Systeme finden heute produziert

    – Christoph

    26. Juni 2013 um 7:44 Uhr

  • Beachten Sie auch, dass die Antwort irreführend ist – die int Vergleich funktioniert nicht, weil die Variablen die gleichen Bits enthalten, sondern weil ihre Werte nach der Konvertierung gleich sind; die C-Sprache kümmert sich meistens nicht um Repräsentation – (unsigned)-1 == UINT_MAX gilt auch dann, wenn eine Vorzeichen-Größen-Darstellung verwendet wird, wo im Gegensatz zum Zweierkomplement die Umwandlung kein Noop ist

    – Christoph

    26. Juni 2013 um 8:08 Uhr


Benutzeravatar von Nobilis
Nobilis

Willkommen zu ganzzahlige Förderung. Wenn ich von der Website zitieren darf:

Wenn ein int alle Werte des ursprünglichen Typs darstellen kann, wird der Wert in einen int konvertiert; andernfalls wird es in ein unsigned int umgewandelt. Diese werden als Integer-Promotions bezeichnet. Alle anderen Typen bleiben von den Integer-Promotions unverändert.

C kann wirklich verwirrend sein, wenn Sie Vergleiche wie diese anstellen. Ich habe kürzlich einige meiner Nicht-C-Programmierfreunde mit dem folgenden 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 demonstriert scheinbar, 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 explizit die size_t Typ zurückgegeben von strlen als vorzeichenbehaftete Ganzzahl:

if ((int)(strlen(string)) < -1)

Dann wird 25 gegen -1 verglichen und alles wird gut mit der Welt.

Ein guter Compiler sollte Sie vor dem Vergleich zwischen einer vorzeichenlosen und einer vorzeichenbehafteten Ganzzahl warnen, und doch ist er immer noch so leicht zu übersehen (insbesondere wenn Sie keine Warnungen aktivieren).

Dies ist besonders verwirrend für Java-Programmierer, da dort alle primitiven Typen signiert sind. Hier ist, was James Gosling (einer der Schöpfer von Java) musste zu dem Thema sagen:

Gosling: Für mich als Sprachdesigner, zu dem ich mich heutzutage nicht wirklich zähle, bedeutete „einfach“ am Ende wirklich, ob ich erwarten könnte, dass J. Random Developer die Spezifikation in seinem Kopf behält. Diese Definition sagt zum Beispiel, dass Java das nicht ist – und tatsächlich enden viele dieser Sprachen mit vielen Sonderfällen, Dingen, die niemand wirklich versteht. Befragen Sie einen C-Entwickler über unsigned, und ziemlich bald werden Sie feststellen, dass fast kein C-Entwickler tatsächlich versteht, was mit unsigned passiert, was vorzeichenlose Arithmetik ist. Solche Dinge machten C komplex. Der Sprachteil von Java ist meiner Meinung nach ziemlich einfach. Die Bibliotheken müssen Sie nachschlagen.

  • Goslings Begründung versäumt es, das unsigniert zu bemerken Byte Typen in Pascal bereiteten keinerlei Schwierigkeiten. Die einzigen vorzeichenlosen Typen, die überhaupt problematisch sind, sind diejenigen, die größer als die standardmäßige Ganzzahlgröße sind.

    – Superkatze

    2. Juni 2014 um 19:24 Uhr

  • Ich verstehe nicht warum (int)(strlen(string)) < -1 funktioniert. Sie haben nur die linke Seite des Vergleichs geändert, die rechte Seite ist immer noch 4294967295 gemäß der vorherigen Erklärung. (cc @Xolve)

    – Benutzer13107

    10. August 2017 um 9:09 Uhr


  • @ user13107 strlen(string) gibt a zurück size_t Ergebnis, das nicht als dargestellt werden kann int. Somit werden alle Operanden des Vergleichs hochgestuft unsigned int.

    – Fabio Pozzi

    24. September 2017 um 9:37 Uhr

Die Hex-Darstellung von -5 ist:

  • 8-Bit, Zweierkomplement signed char: 0xfb
  • 32-Bit, Zweierkomplement signed int: 0xfffffffb

Wenn Sie eine Zahl mit Vorzeichen in eine Zahl ohne Vorzeichen umwandeln oder umgekehrt, macht der Compiler … genau gar nichts. Was gibt es zu tun? Die Zahl ist entweder konvertierbar oder nicht, in diesem Fall folgt undefiniertes oder implementierungsdefiniertes Verhalten (ich habe nicht wirklich überprüft, welches) und das effizienteste implementierungsdefinierte Verhalten ist, nichts zu tun.

Also die Hex-Darstellung von (unsigned <type>)-5 ist:

  • 8 Bit, unsigned char: 0xfb
  • 32-Bit, unsigned int: 0xfffffffb

Ähnlich aussehend? Sie sind Bit für Bit identisch mit den signierten Versionen.

Wenn du schreibst if (a == b)wo a und b sind vom Typ charwas der Compiler tatsächlich lesen muss, ist if ((int)a == (int)b). (Das ist diese „Ganzzahl-Promotion“, über die alle anderen reden.)

Was passiert also, wenn wir umwandeln? char zu int?

  • 8 Bit signed char auf 32-Bit signed int: 0xfb -> 0xfffffffb
    • Nun, das macht Sinn, weil es zu den Darstellungen von passt -5 Oben!
    • Es wird “Sign-Extend” genannt, weil es das oberste Bit des Bytes, das “Sign-Bit”, nach links in den neuen, breiteren Wert kopiert.
  • 8 Bit unsigned char auf 32-Bit signed int: 0xfb -> 0x000000fb
    • Diesmal macht es eine “Null-Erweiterung”, weil der Quelltyp ist ohne Vorzeichensodass kein Vorzeichenbit kopiert werden muss.

So, a == b wirklich tut 0xfffffffb == 0x000000fb => keine Übereinstimmung!

Und, c == d wirklich tut 0xfffffffb == 0xfffffffb => passen!

Benutzeravatar von Antonio
Antonio

Mein Punkt ist: Haben Sie zur Kompilierzeit keine Warnung “Vergleichen von signierten und unsignierten Ausdrücken” erhalten?

Der Compiler versucht Ihnen mitzuteilen, dass er berechtigt ist, verrückte Sachen zu machen! 🙂 Ich würde hinzufügen, dass verrückte Dinge passieren werden, wenn große Werte verwendet werden, die nahe an der Kapazität des primitiven Typs liegen. Und

 unsigned int d = -5;

d definitiv einen großen Wert zuweist, ist es äquivalent (auch wenn es wahrscheinlich nicht garantiert ist, dass es äquivalent ist):

 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). Das bedeutet also, dass der Compiler, der die Konvertierungsregeln anwendet, sicher ist, dass beim Vergleich keine Fehler auftreten unsigned char und char (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 teilt es Ihnen mit, dass dies nicht der Fall sein wird unsigned int und int: Während des Vergleichs wird einer der 2 in einen Typ konvertiert, der ihn nicht vollständig darstellen kann.

Zur Vollständigkeit, Ich habe es auch kurz gecheckt: Der Compiler verhält sich genauso wie bei chars, und wie erwartet gibt es zur Laufzeit keine Fehler.

.

Im Zusammenhang mit diesem Thema habe ich kürzlich diese Frage gestellt (noch C++ orientiert).

  • Abstimmen, weil…? Es wäre konstruktiv, den Grund zu kennen.

    – Antonio

    26. Juni 2013 um 6:47 Uhr

  • Weil Sie die Frage nicht beantwortet haben? Warum funktioniert es nicht mit char, sondern mit int?

    – Ludin

    26. Juni 2013 um 12:25 Uhr

  • @Lundin Meine Motivation ist, dass dieser Code einen Fall auslöst, in dem das Verhalten nicht definiert ist. Unterschiedliche Compiler werden unterschiedliche Ergebnisse liefern (sind dazu berechtigt). Sie können versuchen zu erraten, warum dieser spezielle Compiler dieses Ergebnis geliefert hat, aber ich glaube nicht, dass dies einen Sinn hat: Undefiniertes Verhalten sollte vermieden werden, und das war’s.

    – Antonio

    26. Juni 2013 um 12:41 Uhr


  • @Lundin Und übrigens funktioniert es (es hat das richtige Verhalten) mit chars und nicht mit int 🙂

    – Antonio

    26. Juni 2013 um 12:50 Uhr

  • Abstimmen, weil…? Es wäre konstruktiv, den Grund zu kennen.

    – Antonio

    26. Juni 2013 um 6:47 Uhr

  • Weil Sie die Frage nicht beantwortet haben? Warum funktioniert es nicht mit char, sondern mit int?

    – Ludin

    26. Juni 2013 um 12:25 Uhr

  • @Lundin Meine Motivation ist, dass dieser Code einen Fall auslöst, in dem das Verhalten nicht definiert ist. Unterschiedliche Compiler werden unterschiedliche Ergebnisse liefern (sind dazu berechtigt). Sie können versuchen zu erraten, warum dieser spezielle Compiler dieses Ergebnis geliefert hat, aber ich glaube nicht, dass dies einen Sinn hat: Undefiniertes Verhalten sollte vermieden werden, und das war’s.

    – Antonio

    26. Juni 2013 um 12:41 Uhr


  • @Lundin Und übrigens funktioniert es (es hat das richtige Verhalten) mit chars und nicht mit int 🙂

    – Antonio

    26. Juni 2013 um 12:50 Uhr

1417210cookie-checkTypumwandlung – unsigned to signed int/char

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

Privacy policy