Warum sind die Ergebnisse der Integer-Promotion unterschiedlich?

Lesezeit: 8 Minuten

Benutzeravatar von Linuxer
Linuxer

Bitte schau dir meinen Testcode an:

#include <stdlib.h>
#include <stdio.h>


#define PRINT_COMPARE_RESULT(a, b) \
    if (a > b) { \
        printf( #a " > " #b "\n"); \
    } \
    else if (a < b) { \
        printf( #a " < " #b "\n"); \
    } \
    else { \
        printf( #a " = " #b "\n" ); \
    }

int main()
{
    signed   int a = -1;
    unsigned int b = 2;
    signed   short c = -1;
    unsigned short d = 2;

    PRINT_COMPARE_RESULT(a,b);
    PRINT_COMPARE_RESULT(c,d);

    return 0;
}

Das Ergebnis ist folgendes:

a > b
c < d

Meine Plattform ist Linux und meine gcc-Version ist 4.4.2. Ich bin überrascht von der zweiten Zeile der Ausgabe. Die erste Ausgabezeile wird durch Integer-Promotion verursacht. Aber warum ist das Ergebnis der zweiten Zeile anders?

Die folgenden Regeln stammen aus dem C99-Standard:

Wenn beide Operanden denselben Typ haben, ist keine weitere Konvertierung erforderlich. Andernfalls, wenn beide Operanden vorzeichenbehaftete Integer-Typen oder beide vorzeichenlose Integer-Typen haben, wird der Operand mit dem Typ des kleineren ganzzahligen Konvertierungsrangs in den Typ des Operanden mit dem höheren Rang konvertiert.

Andernfalls, wenn der Rang des Operanden vom Typ Ganzzahl ohne Vorzeichen größer oder gleich dem Rang des Typs des anderen Operanden ist, wird der Operand vom Typ Ganzzahl mit Vorzeichen in den Typ des Operanden vom Typ Ganzzahl ohne Vorzeichen konvertiert.

Andernfalls, wenn der Typ des Operanden mit vorzeichenbehafteter Ganzzahl alle Werte des Typs des Operanden mit vorzeichenloser Ganzzahl darstellen kann, wird der Operand mit vorzeichenloser Ganzzahl in den Typ des Operanden mit vorzeichenbehafteter Ganzzahl konvertiert.

Andernfalls werden beide Operanden in den vorzeichenlosen Integer-Typ konvertiert, der dem Typ des Operanden mit vorzeichenbehaftetem Integer-Typ entspricht.

Ich denke, beide Vergleiche sollten zum selben Fall gehören, dem zweiten Fall der Integer-Promotion.

  • Ich denke (aber ich bin mir nicht 100% sicher), dass alle ganzzahligen Typen kürzer als int dazu befördert werden int für alle Operationen, also c und d beide erhalten echte Werbeaktionen und behalten ihren Wert. a ist schon ein intalso der Vergleich konvertiert a zu unsigniert.

    – Kerrek SB

    10. Oktober 2011 um 10:04 Uhr

  • Ich habe dieses Ergebnis vorher vermutet. Aber es scheint nach dem C99-Standard nicht richtig zu sein. Ich habe gerade einige Abschnitte aus C99 in meinen Beitrag eingefügt. Daran könnte man sich orientieren.

    – Linuxer

    10. Oktober 2011 um 10:07 Uhr


  • Danke für das Zitat. Vielleicht gibt es irgendwo eine andere Regel, die die anfängliche Beförderung zu erwähnt int?

    – Kerrek SB

    10. Oktober 2011 um 10:11 Uhr

  • Sie aktivieren nicht Compiler-Warnungen wenn Sie kompilieren tun Sie? Sonst würdest du sehen warning: comparison between signed and unsigned integer expressions [-Wsign-compare]

    – David C. Rankin

    10. August 2016 um 0:29 Uhr

Benutzeravatar von Dietrich Epp
Dietrich Ep

Wenn Sie einen arithmetischen Operator verwenden, durchlaufen die Operanden zwei Konvertierungen.

Ganzzahlige Promotionen: Wenn int alle Werte des Typs darstellen kann, wird der Operand zu int hochgestuft. Dies gilt für beide short und unsigned short auf den meisten Plattformen. Die auf dieser Stufe durchgeführte Konvertierung wird für jeden Operanden einzeln durchgeführt, ohne Rücksicht auf den anderen Operanden. (Es gibt noch mehr Regeln, aber diese gilt.)

Übliche arithmetische Umrechnungen: Vergleicht man ein unsigned int gegen a signed intda keiner den gesamten Bereich des anderen umfasst und beide denselben Rang haben, werden beide in umgewandelt unsigned Typ. Diese Konvertierung wird durchgeführt, nachdem der Typ beider Operanden untersucht wurde.

Offensichtlich gelten die “üblichen arithmetischen Umrechnungen” nicht immer, wenn es nicht zwei Operanden gibt. Aus diesem Grund gibt es zwei Regelwerke. Ein Fallstrick ist zum Beispiel, dass Schichtoperatoren << und >> Führen Sie keine üblichen arithmetischen Konvertierungen durch, da der Typ des Ergebnisses nur vom linken Operanden abhängen sollte (wenn Sie also jemanden tippen sehen x << 5Udann ist die U steht für „unnötig“).

Nervenzusammenbruch: Nehmen wir ein typisches System mit 32-Bit-Int und 16-Bit-Short an.

int a = -1;         // "signed" is implied
unsigned b = 2;     // "int" is implied
if (a < b)
    puts("a < b");  // not printed
else
    puts("a >= b"); // printed
  1. Zuerst werden die beiden Operanden hochgestuft. Da sind beide int oder unsigned intes werden keine Beförderungen durchgeführt.
  2. Als nächstes werden die beiden Operanden in den gleichen Typ konvertiert. Seit int kann nicht alle möglichen Werte von darstellen unsignedund unsigned kann nicht alle möglichen Werte von darstellen int, es gibt keine offensichtliche Wahl. In diesem Fall werden beide konvertiert unsigned.
  3. Beim Konvertieren von signiert zu unsigned, 232 wird wiederholt zum vorzeichenbehafteten Wert addiert, bis er im Bereich des vorzeichenlosen Werts liegt. Dies ist eigentlich ein Noop, was den Prozessor betrifft.
  4. So wird der Vergleich if (4294967295u < 2u)was falsch ist.

Jetzt versuchen wir es mit short:

short c = -1;          // "signed" is implied
unsigned short d = 2;
if (c < d)
    puts("c < d");     // printed
else
    puts("c >= d");    // not printed
  1. Zunächst werden die beiden Operanden heraufgestuft. Da lassen sich beide originalgetreu darstellen intbeide werden befördert int.
  2. Als nächstes werden sie in den gleichen Typ konvertiert. Aber sie sind bereits der gleiche Typ, intalso wird nichts gemacht.
  3. So wird der Vergleich if (-1 < 2)was wahr ist.

Guten Code schreiben: Es gibt eine einfache Möglichkeit, diese „Fallstricke“ in Ihrem Code zu erkennen. Kompilieren Sie einfach immer mit aktivierten Warnungen und beheben Sie die Warnungen. Ich neige dazu, Code wie folgt zu schreiben:

int x = ...;
unsigned y = ...;
if (x < 0 || (unsigned) x < y)
    ...;

Sie müssen aufpassen, dass jeder Code, den Sie schreiben, nicht in den anderen signierten vs. unsignierten Gotcha gerät: signierter Überlauf. Zum Beispiel der folgende Code:

int x = ..., y = ...;
if (x + 100 < y + 100)
    ...;
unsigned a = ..., b = ...;
if (a + 100 < b + 100)
    ...;

Einige beliebte Compiler werden optimiert (x + 100 < y + 100) zu (x < y), aber das ist eine Geschichte für einen anderen Tag. Lassen Sie Ihre signierten Nummern einfach nicht überlaufen.

Fußnote: Beachten Sie, dass während signed ist impliziert für int, short, longund long longes ist NICHT impliziert char. Stattdessen hängt es von der Plattform ab.

  • Danke, aber ich weiß immer noch nicht, warum die Ergebnisse unterschiedlich sind? Wenn es zwei Konvertierungen gibt, sollten die Ergebnisse gleich sein. Weil der zweite Vergleich in int konvertiert wird.

    – Linuxer

    10. Oktober 2011 um 14:00 Uhr


  • Vielen Dank für Ihre weiteren Kommentare. Ich habe lange über deine letzte Antwort nachgedacht. Endlich habe ich deine Bedeutung verstanden. Aber trotzdem vielen Dank für Ihre Kommentare zum Schreiben guter Codes.

    – Linuxer

    11. Oktober 2011 um 2:11 Uhr

  • wenn ich sowas habe int x = 1234 und char *y = &x . Binäre Darstellung von 1234 ist 00000000 00000000 00000100 11010010 . Meine Maschine ist Little Endian, also kehrt sie sie um und speichert sie im Speicher 11010010 00000100 00000000 00000000 LSB kommt zuerst. Jetzt Hauptteil. wenn ich benutze printf("%d" , *p). printf wird das erste Byte lesen 11010010nur die Ausgabe ist -46 sondern 11010010 ist 210 also warum druckt es -46 . Ich bin wirklich verwirrt, ich schätze, einige Char-to-Integer-Promotion macht etwas, aber ich weiß es nicht.

    – Suraj Jain

    17. August 2016 um 10:20 Uhr


  • @SurajJain: Ihr Computer ist korrekt: 11010010 ist die binäre Darstellung von (char)-46 auf deinem Computer. Dies liegt daran, dass auf Ihrem Computer char ist ein vorzeichenbehafteter Typ, also ist das höchstwertige Bit das Vorzeichenbit. 11010010 = –128 + 64 + 16 + 2 = –46. Wenn Sie 210 wollen, verwenden Sie unsigned char Anstatt von char.

    – Dietrich Ep

    17. August 2016 um 12:52 Uhr


  • Aber ich habe es mit gedruckt %d sollte es also nicht so geändert werden, wie es wirklich passiert, wenn ich char mit drucke %d ist auch *p behandelt wie char aber ist es nicht Teil von int . Was ich meine ist, wenn ich schreibe printf("%d" , *p) so ist *p behandelt wie signed char . Ich frage dies, weil ich a nicht deklariert habe char Variable habe ich nur verwendet pointer to char . ??

    – Suraj Jain

    17. August 2016 um 13:06 Uhr

Benutzeravatar von hege
Hege

Entnommen aus dem C++-Standard:

4.5 Integrale Werbeaktionen [conv.prom]
1 Ein rvalue vom Typ char, signed char, unsigned char, short int oder unsigned short int kann in einen rvalue vom Typ int konvertiert werden, wenn int alle Werte des Quelltyps darstellen kann; Andernfalls kann der Quell-R-Wert in einen R-Wert vom Typ unsigned int konvertiert werden.

In der Praxis bedeutet dies, dass alle Operationen (auf den Typen in der Liste) tatsächlich auf dem Typ ausgewertet werden int wenn es den gesamten Wertsatz abdecken kann, mit dem Sie es zu tun haben, andernfalls wird es weiter ausgeführt unsigned int. Im ersten Fall werden die Werte als verglichen unsigned int weil einer von ihnen war unsigned int und deshalb ist -1 “größer” als 2. Im zweiten Fall werden die Werte a als ganze Zahlen mit Vorzeichen verglichen, as int deckt den gesamten Bereich von beiden ab short und unsigned short also ist -1 kleiner als 2.

(Hintergrundgeschichte: Tatsächlich führt diese ganze komplexe Definition, alle Fälle auf diese Weise abzudecken, dazu, dass die Compiler den eigentlichen Typ dahinter (!) 🙂 ignorieren und sich nur um die Datengröße kümmern können.)

Der Konvertierungsprozess für C++ wird als beschrieben übliche arithmetische Umrechnungen. Ich denke jedoch, dass die relevanteste Regel im Abschnitt ist, auf den unter Verweis verwiesen wird conv.prom: Integrale Werbeaktionen 4.6.1:

Ein prvalue eines ganzzahligen Typs außer bool, char16_t, char32_t oder wchar_t, dessen ganzzahliger Konvertierungsrang ([conv.rank]) kleiner als der Rang von int ist, kann in einen Prvalue vom Typ int konvertiert werden, wenn int alle Werte des Quelltyps darstellen kann; Andernfalls kann der Quell-prvalue in einen prvalue des Typs unsigned int konvertiert werden.

Das Witzige dabei ist die Verwendung des Wortes “kann”, was meines Erachtens darauf hindeutet, dass diese Beförderung im Ermessen des Compilers erfolgt.

fand ich auch diese C-spec-Snippet, das auf das Weglassen der Werbung hinweist:

11   EXAMPLE 2       In executing the fragment
              char c1, c2;
              /* ... */
              c1 = c1 + c2;
     the ``integer promotions'' require that the abstract machine promote the value of each variable to int size
     and then add the two ints and truncate the sum. Provided the addition of two chars can be done without
     overflow, or with overflow wrapping silently to produce the correct result, the actual execution need only
     produce the same result, possibly omitting the promotions.

Es gibt auch die Definition von “Rang” in Betracht gezogen werden. Die Liste der Regeln ist ziemlich lang, aber in Bezug auf diese Frage ist “Rang” einfach:

Der Rang eines vorzeichenlosen Ganzzahltyps muss gleich dem Rang des entsprechenden vorzeichenbehafteten Ganzzahltyps sein.

1437270cookie-checkWarum sind die Ergebnisse der Integer-Promotion unterschiedlich?

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

Privacy policy