Warum macht es einen Unterschied, ob Links- und Rechtsverschiebung zusammen in einem Ausdruck verwendet werden oder nicht?

Lesezeit: 7 Minuten

Benutzeravatar von odzhychko
odschitschko

Ich habe folgenden Code:

unsigned char x = 255;
printf("%x\n", x); // ff

unsigned char tmp = x << 7;
unsigned char y = tmp >> 7;
printf("%x\n", y); // 1

unsigned char z = (x << 7) >> 7;
printf("%x\n", z); // ff

hätte ich erwartet y und z gleich sein. Sie unterscheiden sich jedoch je nachdem, ob eine Zwischenvariable verwendet wird. Es wäre interessant zu wissen, warum das so ist.

  • (x<<7)>>7 speichert grundsätzlich auch ein Zwischenergebnis. Aber ich weiß nicht, wo steht, was für eine Art dieses Zwischenergebnisses sein soll.

    – Das Photon

    22. Mai 2020 um 15:52 Uhr

  • @ThePhoton: Im C-Standard steht, dass der Zwischentyp zur Bewertung verwendet wird (x << 7) >> 7 ist int oder unsigned int abhängig von den Größen von unsigned char und int.

    – chqrlie

    22. Mai 2020 um 17:31 Uhr

Benutzeravatar von chqrlie
chqrlie

Dieser kleine Test ist tatsächlich subtiler, als es aussieht, da das Verhalten von der Implementierung definiert wird:

  • unsigned char x = 255; keine Zweideutigkeit hier, x ist ein unsigned char mit Wert 255Typ unsigned char garantiert genügend Reichweite zum Verstauen 255.

  • printf("%x\n", x); Dies produziert ff auf der Standardausgabe wäre es aber sauberer zu schreiben printf("%hhx\n", x); wie printf erwartet ein unsigned int für die Konvertierung %xdie x ist nicht. Vorbeigehen x könnte tatsächlich passieren int oder ein unsigned int Streit.

  • unsigned char tmp = x << 7; Um den Ausdruck auszuwerten x << 7, x ein sein unsigned char erfährt zunächst die ganzzahlige Aktionen in der C-Norm definiert 6.3.3.1: Wenn ein int alle Werte des ursprünglichen Typs darstellen kann (eingeschränkt durch die Breite, für ein Bitfeld), wird der Wert in ein umgewandelt int; andernfalls wird es in ein umgewandelt unsigned int. Diese werden als Integer-Promotions bezeichnet.

    Also, wenn die Anzahl der Wertbits in unsigned char kleiner oder gleich dem von ist int (der häufigste Fall ist derzeit 8 vs. 31), x wird zunächst zu einem befördert int mit dem gleichen Wert, der dann um nach links verschoben wird 7 Positionen. Das Ergebnis, 0x7f80passt garantiert in die int Typ, sodass das Verhalten gut definiert ist und dieser Wert in Typ umgewandelt wird unsigned char wird effektiv die höherwertigen Bits des Werts abschneiden. Wenn Typ unsigned char 8 Bit hat, wird der Wert sein 128 (0x80), aber wenn Typ unsigned char hat mehr Bits, der Wert in tmp kann sein 0x180, 0x380, 0x780, 0xf80, 0x1f80, 0x3f80 oder auch 0x7f80.

    Wenn Typ unsigned char ist größer als intwas auf seltenen Systemen auftreten kann, wo sizeof(int) == 1, x befördert wird unsigned int und die Linksverschiebung wird bei diesem Typ durchgeführt. Der Wert ist 0x7f80Udie garantiert typgerecht ist unsigned int und das zu speichern tmp verliert eigentlich keine Informationen seit Typ unsigned char hat die gleiche Größe wie unsigned int. So tmp hätte den Wert 0x7f80 in diesem Fall.

  • unsigned char y = tmp >> 7; Die Auswertung erfolgt wie oben, tmp befördert wird int oder unsigned int Je nach System, das seinen Wert behält, wird dieser Wert um 7 Positionen nach rechts verschoben, was vollständig definiert ist, da 7 ist kleiner als die Breite des Typs (int oder unsigned int) und der Wert ist positiv. Abhängig von der Anzahl der Bits des Typs unsigned charder Wert, der in gespeichert ist y kann sein 1, 3, 7, 15, 31, 63, 127 oder 255wird die häufigste Architektur haben y == 1.

  • printf("%x\n", y); Auch hier wäre es besser, nicht zu schreiben printf("%hhx\n", y); und die Ausgabe kann sein 1 (häufigster Fall) bzw 3, 7, f, 1f, 3f, 7f oder ff abhängig von der Anzahl der Wertbits im Typ unsigned char.

  • unsigned char z = (x << 7) >> 7; Die Integer-Promotion wird durchgeführt x wie oben beschrieben, der Wert (255) wird dann als an um 7 Bit nach links verschoben int oder ein unsigned intimmer produzieren 0x7f80 und dann um 7 Positionen nach rechts verschoben, mit einem Endwert von 0xff. Dieses Verhalten ist vollständig definiert.

  • printf("%x\n", z); Auch hier sollte der Formatstring lauten printf("%hhx\n", z); und die Ausgabe wäre immer ff.

Systeme, bei denen Bytes mehr als 8 Bit haben, werden heutzutage selten, aber einige eingebettete Prozessoren, wie z. B. spezialisierte DSPs, tun dies immer noch. Es würde ein perverses System brauchen, um zu versagen, wenn es bestanden wird unsigned char Für ein %x Konvertierungsspezifizierer, aber es ist sauberer für beide Verwendungen %hhx oder tragbarer schreiben printf("%x\n", (unsigned)z);

Vorbei schalten 8 Anstatt von 7 in diesem Beispiel wäre noch konstruierter. Es hätte ein undefiniertes Verhalten auf Systemen mit 16-Bit int und 8bit char.

  • Ich bin bereit zu argumentieren, dass ein Fehler beim Übergeben des unsigned char an printf außerhalb der Spezifikation liegt.

    – Josua

    23. Mai 2020 um 3:46 Uhr

  • Du sagst das unsigned char kann sein größer als int auf Systemen mit sizeof(int)==1. Per Definition hätten sie dasselbe sizeof() In diesem Fall ist es möglicherweise irreführend, “größer” zu sagen. Es ist möglich dass unsigned char könnte mehr Wertbits haben als int (int kann Polsterung haben; unsigned char darf nicht). Aber auch ohne all das, das obere Ende des Wertebereichs von unsigned char kann größer sein als für int für die gleiche Anzahl von Wertbits, einfach weil es unsigned ist.

    – Peter Cordes

    23. Mai 2020 um 5:55 Uhr

  • Ich finde es auch seltsam zu sagen, dass sie “gleich” sind, wenn die oberen Grenzen des Wertebereichs übereinstimmen unsigned char und signed int (wodurch unsigned char zu int befördert werden kann). Sie können nicht vom gleichen Typ sein (sie müssen sich im Vorzeichen unterscheiden), und die gleiche Obergrenze des Wertebereichs (positives Ende) würde dies bedeuten int hat 1 Wertbit mehr.

    – Peter Cordes

    23. Mai 2020 um 5:56 Uhr


  • @PeterCordes: Das Vorzeichenbit ist nicht Teil der Wert Bitswie in verwendet C17 6.2.6.2: […] Für vorzeichenbehaftete Integer-Typen müssen die Bits der Objektdarstellung in drei Gruppen unterteilt werden: Wertbits, Füllbits und das Vorzeichenbit.[…]. Also technisch, int und unsigned char kann die gleiche Anzahl von haben Wert Bitsaber dann muss es ein separates Vorzeichenbit haben, und daher mindestens CHAR_BIT-1 Füllbits auf einer so seltsamen Architektur.

    – chqrlie

    23. Mai 2020 um 16:59 Uhr

  • Ah, mein Fehler, danke, dass Sie mich korrigiert haben, wie C den Begriff “Wertbits” verwendet. Das Beispiel von 8 vs. 31 ist sehr hilfreich, um deutlich zu machen, dass das Vorzeichenbit nicht enthalten ist, falls es jemand anderes vergessen hat. Gute Bearbeitung.

    – Peter Cordes

    23. Mai 2020 um 21:36 Uhr

Benutzeravatar von Adrian Mole
Adrian Mol

Die “Zwischenwerte” in Ihrem letzten Fall sind (vollständige) ganze Zahlen, also die Bits, die “außerhalb des Bereichs” des Originals verschoben werden unsigned char Typ bleiben erhalten und werden daher auch dann noch gesetzt, wenn das Ergebnis in ein einzelnes Byte zurückgewandelt wird.

Davon C11-Standardentwurf:

6.5.7 Bitweise Verschiebungsoperatoren

3 Die ganzzahligen Heraufstufungen werden an jedem der Operanden durchgeführt. Der Typ des Ergebnisses ist der des hochgestuften linken Operanden …

Aber in Ihrem ersten Fall unsigned char tmp = x << 7;das tmp verliert die sechs “hohen” Bits, wenn die resultierende “vollständige” Ganzzahl konvertiert wird (dh gekürzt) auf ein einzelnes Byte zurück und ergibt einen Wert von 0x80; wenn diese dann nach rechts verschoben wird unsigned char y = tmp >> 7;das Ergebnis ist (wie erwartet) 0x01.

  • Exzellent! Nun steht die Integer-Promotion an unsigned int da der ursprüngliche Typ ist unsigned char? Andernfalls könnte ich erwarten, eine Zeichenerweiterung in der rechten Schicht zu sehen.

    – Fred Larson

    22. Mai 2020 um 16:10 Uhr

  • @FredLarson Es spielt keine Rolle, ob der beworbene Typ signiert oder nicht signiert ist! Als Wert 255 kann sein richtig vertreten bei beiden tritt keine Vorzeichenerweiterung auf. Das heißt, auch wenn Sie explizit eine wirken unsigned char Wert von 255 zu einem unterzeichnet 32-Bit intsein Wert wird sein 255 (nicht INT_MIN).

    – Adrian Maulwurf

    22. Mai 2020 um 16:13 Uhr


  • @FredLarson Sie würden definitiv keine Zeichenerweiterung mit einem unsignierten Typ sehen. Was es anbetrifft, fördert es an int (vorausgesetzt ein int ist größer als a char auf besagtem System) gemäß C11-Normentwurf Abschnitt 6.3.1.1: “Wenn ein int alle Werte des ursprünglichen Typs darstellen kann (eingeschränkt durch die Breite, für ein Bitfeld), wird der Wert in ein konvertiert int; andernfalls wird es in ein konvertiert unsigned int.

    – Christian Gibbons

    22. Mai 2020 um 16:19 Uhr


Der Schichtoperator ist für die nicht definiert char Typen. Der Wert von jedem char Operand wird umgewandelt in int und das Ergebnis des Ausdrucks wird konvertiert char Typ. Wenn Sie also den linken und den rechten Verschiebungsoperator in denselben Ausdruck einfügen, wird die Berechnung als Typ ausgeführt int (ohne etwas zu verlieren), und das Ergebnis wird umgewandelt in char.

1394900cookie-checkWarum macht es einen Unterschied, ob Links- und Rechtsverschiebung zusammen in einem Ausdruck verwendet werden oder nicht?

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

Privacy policy