Warum wird ((unsigned char)0x80)

Lesezeit: 3 Minuten

Benutzeravatar von RubenLaguna
RubenLaguna

Das folgende Programm

#include <inttypes.h> /*  printf(" %" PRIu32 "\n"), my_uint32_t) */
#include <stdio.h> /* printf(), perror() */

int main(int argc, char *argv[])
{
  uint64_t u64 = ((unsigned char)0x80) << 24;
  printf("%"  PRIX64 "\n", u64);

  /* uint64_t */ u64 = ((unsigned int)0x80)  << 24;
  printf("%016"  PRIX64 "\n", u64);
}

produziert

FFFFFFFF80000000
0000000080000000

Was ist der Unterschied zwischen ((unsigned char)0x80) und ((unsigned int)0x80) in diesem Zusammenhang?

Ich vermute, dass (unsigned char)0x80 wird befördert (unsigned char)0xFFFFFFFFFFFFFF80 und dann wird etwas verschoben, aber warum denkt diese Konvertierung das? unsigned char ist unterschrieben?

Es ist auch interessant, das zu bemerken 0x80 << 16 bringt das erwartete Ergebnis, 0x0000000000800000.

  • Weil shift Typen zu Integer hochstuft: stackoverflow.com/a/22734721/2709018

    – myaut

    9. April 2015 um 12:51 Uhr

  • Ich bin auf dieses Verhalten gestoßen, wenn ich mit allen möglichen Warnungen und Fehlern kompiliert habe: Die Bitverschiebung erzeugt als Ergebnis vorzeichenbehaftete Werte, und es ist eine Hündin, dann die vorzeichenbehafteten/vorzeichenlosen Vergleiche/Zuweisungen zum Laufen zu bringen.

    – Emvee

    9. April 2015 um 12:57 Uhr

  • Hinweis: Sollte Code auf einem System mit 16-Bit laufen int/unsigned, eine Verschiebung von 24 ist ein undefiniertes Verhalten. (16-Bit in eingebetteten Systemen üblich) Besser zu verwenden u64 = ((uint32_t)0x80) << 24 oder u64 = ((uint64_t)0x80) << 24

    – chux – Wiedereinsetzung von Monica

    9. April 2015 um 14:42 Uhr


C-Compiler führt ganzzahlige Aktionen bevor die Schicht ausgeführt wird.

Regel 6.3.1.1 des Standards sagt:

Wenn ein int alle Werte des ursprünglichen Typs darstellen kann, wird der Wert in ein konvertiert int; andernfalls wird es in ein umgewandelt unsigned int. Diese werden als Integer-Promotions bezeichnet.

Da alle Werte von unsigned char vertreten werden kann durch int, 0x80 wird in ein signiertes umgewandelt int. Dasselbe gilt nicht für unsigned int: Einige seiner Werte können nicht als dargestellt werden intalso bleibt es unsigned int nach Anwendung von Integer-Promotions.

  • @SergeBallesta Aus dem Titel geht hervor, dass OP etwas über die Zeichenerweiterung weiß, also habe ich beschlossen, es ihm nicht noch einmal zu erklären.

    – Sergej Kalinitschenko

    9. April 2015 um 13:03 Uhr

  • @SergeBallesta (unsigned char) 0x80 wird nicht umgewandelt 0xFFFFFF80. es ist ((unsigned char)0x80) << 24 das ergibt ein int mit Wert (int) 0x80000000 und dann bei der Umwandlung in uint64_t die Vorzeichenerweiterung erfolgt.

    – au

    9. April 2015 um 13:12 Uhr

  • @ouah Danke für diese Erklärung, das hat mich auch verwirrt.

    – mshildt

    9. April 2015 um 13:14 Uhr

  • Beachten Sie das (unsigned char)0x80 kann ein bisschen ein Ablenkungsmanöver sein, weil es auch mit passiert ((unsigned char)0x40) << 25.

    – mwfearnley

    9. April 2015 um 13:57 Uhr

Benutzeravatar von ouah
ouah

Der linke Operand der << Der Operator wird einer ganzzahligen Heraufstufung unterzogen.

(C99, 6.5.7p3) “Die ganzzahligen Beförderungen werden für jeden der Operanden durchgeführt.”

Es bedeutet diesen Ausdruck:

 ((unsigned char)0x80) << 24

ist äquivalent zu:

 ((int) (unsigned char)0x80) << 24

gleichwertig:

  0x80 << 24

die das Vorzeichenbit von an setzen int in 32bit int System. Dann wenn 0x80 << 24 umgewandelt wird uint64_t in dem u64 Deklaration tritt die Vorzeichenerweiterung auf, um den Wert zu liefern 0xFFFFFFFF80000000.

BEARBEITEN:

Beachten Sie, dass Matt McNabb technisch korrekt in den Kommentaren hinzugefügt hat 0x80 << 24 ruft undefiniertes Verhalten in C auf, da das Ergebnis im Typ von nicht darstellbar ist << linker Operand. Wenn Sie verwenden gccdie aktuelle Compiler-Version Garantien dass es diese Operation derzeit nicht undefiniert macht.

  • 0x80 << 24 verursacht undefiniertes Verhalten (obwohl ein häufiges Ergebnis davon ist, zu produzieren INT_MIN)

    – MM

    20. Mai 2015 um 22:34 Uhr


  • @MattMcNabb danke, hat eine Anmerkung zu deinem Kommentar hinzugefügt

    – au

    20. Mai 2015 um 22:47 Uhr

Marians Benutzeravatar
Marian

Der seltsame Teil der Konvertierung passiert beim Konvertieren des Ergebnisses von << von int32 nach uint64. Sie arbeiten auf einem 32-Bit-System, daher beträgt die Größe des Integer-Typs 32 Bit. Der folgende Code:

 u64 = ((int) 0x80) << 24;
 printf("%llx\n", u64);

Drucke:

 FFFFFFFF80000000

weil (0x80 << 24) gibt 0x8000000 Dies ist eine 32-Bit-Darstellung von -2147483648. Diese Zahl wird in 64 Bit umgewandelt, indem das Vorzeichenbit multipliziert wird und es ergibt 0xFFFFFFFF80000000.

Was du bezeugst, ist undefiniertes Verhalten. C99 §6.5.7/4 beschreibt das Verschieben nach links wie folgt:

Das Ergebnis von E1 << E2 ist E1 links verschoben E2 Bitpositionen; frei gewordene Bits werden mit Nullen aufgefüllt. Wenn E1 einen vorzeichenlosen Typ hat, ist der Wert des Ergebnisses E1 × 2E2, reduziert Modulo eins mehr als der im Ergebnistyp darstellbare Maximalwert. Wenn E1 hat einen vorzeichenbehafteten Typ und einen nichtnegativen Wert und E1 × 2E2 im Ergebnistyp darstellbar ist, dann ist das der Ergebniswert; andernfalls ist das Verhalten undefiniert.

In Ihrem Fall, E1 hat den Wert 128, und sein Typ ist intnicht unsigned char. Wie andere Antworten erwähnt haben, erhält der Wert gefördert zu int vor der Auswertung. Die beteiligten Operanden sind vorzeichenbehaftet intund der Wert von 128 um 24 Stellen nach links verschoben ist 2147483648, was eins mehr als der maximal darstellbare Wert ist int auf Ihrem System. Daher ist das Verhalten Ihres Programms undefiniert.

Um dies zu vermeiden, könnten Sie sicherstellen, dass die Art von E1 ist unsigned int durch Typumwandlung auf that statt to unsigned char.

Eine große Schwierigkeit bei der Entwicklung des C-Standards besteht darin, dass es zu der Zeit, als Anstrengungen unternommen wurden, die Sprache zu standardisieren, nicht nur Implementierungen gab, die bestimmte Dinge voneinander unterschieden, sondern dass für diese Implementierungen eine beträchtliche Menge an Code geschrieben wurde die sich auf diese Verhaltensunterschiede stützten. Da die Ersteller des C-Standards vermeiden wollten, Implementierungen zu verbieten, sich auf eine Weise zu verhalten, auf die sich Benutzer dieser Implementierungen verlassen könnten, sind bestimmte Teile des C-Standards ein echtes Durcheinander. Einige der schlimmsten Aspekte betreffen Aspekte der Integer-Promotion, wie die, die Sie beobachtet haben.

Konzeptionell scheint es sinnvoller zu sein, es zu haben unsigned char fördern soll unsigned int als zu signed intzumindest wenn es als etwas anderes als der rechte Operand von verwendet wird - Operator. Kombinationen anderer Operatoren können große Ergebnisse liefern, aber es gibt keinen anderen Operator als - könnte zu einem negativen Ergebnis führen. Um zu sehen warum signed int ausgewählt wurde, obwohl das Ergebnis nicht negativ sein kann, beachten Sie Folgendes:

int i1; unsigned char b1,b2; unsigned int u1; long l1,l2,l3;

l1 = i1+u1;
l2 = i1+b1;
l3 = i1+(b1+b2);

Es gibt keinen Mechanismus in C, durch den eine Operation zwischen zwei verschiedenen Typen einen Typ ergeben könnte, der nicht zu den Originalen gehört, daher muss die erste Anweisung die Addition mit oder ohne Vorzeichen ausführen; unsigned führt im Allgemeinen zu etwas weniger überraschenden Ergebnissen, insbesondere wenn man bedenkt, dass Integer-Literale standardmäßig signiert sind (es wäre sehr seltsam, wenn man hinzufügen würde 1 statt 1u auf einen vorzeichenlosen Wert könnte es negativ machen). Es wäre jedoch überraschend, wenn die dritte Aussage einen negativen Wert haben könnte i1 in eine große Zahl ohne Vorzeichen. Wenn die erste Anweisung oben ein vorzeichenloses Ergebnis liefert, die dritte Anweisung jedoch ein vorzeichenbehaftetes Ergebnis ergibt, impliziert dies (b1+b2) muss unterschrieben werden.

IMHO wäre der “richtige” Weg, Probleme im Zusammenhang mit Vorzeichen zu lösen, separate numerische Typen zu definieren, die ein dokumentiertes “Wrapping” -Verhalten aufweisen (wie dies bei aktuellen unsignierten Typen der Fall ist), und im Gegensatz zu denen, die sich als ganze Zahlen verhalten sollten und die beiden Arten haben von Typen weisen unterschiedliche Beförderungsregeln auf. Implementierungen müssten das vorhandene Verhalten für Code mit vorhandenen Typen weiterhin unterstützen, aber neue Typen könnten Regeln implementieren, die darauf ausgelegt sind, die Benutzerfreundlichkeit gegenüber der Kompatibilität zu bevorzugen.

  • Ältere Versionen der C-Sprache benötigten eigentlich keine Binärmaschinen. Es gab einige seltsame Worte in den Bereichen über Überlauf, die nur Sinn machten, wenn Sie sich daran erinnerten, dass es in den frühen Tagen Dezimalmaschinen gab. Mit C99 ist dieses Geschwätz weg und unsignierte Wraps.

    – Josua

    9. April 2015 um 19:50 Uhr

  • @Joshua: In den frühen Tagen von C wurde das Verhalten von vorzeichenlosen Werten weitgehend davon bestimmt, “den Compiler tun zu lassen, was am billigsten ist”; In den frühen Tagen war dieses Verhalten praktischerweise nützlich. Heutzutage ist das Wickelverhalten oft nicht das billigste. Auf vielen ARM-Plattformen beispielsweise gegeben uint16_t x;die Aussage x++; kann drei Anweisungen erfordern x wird in einem Register gespeichert, aber wenn kein Wrapping-Verhalten erforderlich wäre, würde es nur eines benötigen.

    – Superkatze

    9. April 2015 um 20:15 Uhr

  • @Joshua: Übrigens, ich weiß, dass es Basis-10-Maschinen gab, aber wann wurde C jemals auf ihnen ausgeführt? Was haben bitweise Operatoren gemacht? Ich kann mir nicht vorstellen, wie sie sich in einem Dezimalsystem verhalten könnten, das die erwartete Beziehung zwischen ihnen aufrechterhält &, |und ^ und garantieren dies auch für alle darstellbaren Werte x und y, x ^ y wäre auch vertretbar.

    – Superkatze

    9. April 2015 um 20:18 Uhr

  • Ich weiß nicht, ob C tatsächlich auf ihnen lief. Wenn dies der Fall wäre, wären bitweise Operationen teuer.

    – Josua

    9. April 2015 um 20:21 Uhr

  • @Joshua: Die einzige Möglichkeit, wie ich eine standardkonforme C-Implementierung sehen kann, die auf einer Dezimalmaschine läuft, wäre, wenn Speicherorte in Gruppen zusammengefasst würden, die darauf beschränkt wären, Zweierpotenzen zu halten (z. B. könnte man jede Gruppe von zehn Dezimalziffern als eine definieren int die Werte von 0 bis 8589934591 enthalten könnte). Leider würden die Beziehungen zwischen Zweier- und Zehnerpotenzen etwas umständlich funktionieren; man könnte sagen, dass jede solche Gruppe als drei interpretiert werden könnte char Werte 0 bis 2047, aber man konnte die Ziffern nicht in 5er Gruppen interpretieren…

    – Superkatze

    9. April 2015 um 21:07 Uhr

  • Ältere Versionen der C-Sprache benötigten eigentlich keine Binärmaschinen. Es gab einige seltsame Worte in den Bereichen über Überlauf, die nur Sinn machten, wenn Sie sich daran erinnerten, dass es in den frühen Tagen Dezimalmaschinen gab. Mit C99 ist dieses Geschwätz weg und unsignierte Wraps.

    – Josua

    9. April 2015 um 19:50 Uhr

  • @Joshua: In den frühen Tagen von C wurde das Verhalten von vorzeichenlosen Werten weitgehend davon bestimmt, “den Compiler tun zu lassen, was am billigsten ist”; In den frühen Tagen war dieses Verhalten praktischerweise nützlich. Heutzutage ist das Wickelverhalten oft nicht das billigste. Auf vielen ARM-Plattformen beispielsweise gegeben uint16_t x;die Aussage x++; kann drei Anweisungen erfordern x wird in einem Register gespeichert, aber wenn kein Wrapping-Verhalten erforderlich wäre, würde es nur eines benötigen.

    – Superkatze

    9. April 2015 um 20:15 Uhr

  • @Joshua: Übrigens, ich weiß, dass es Basis-10-Maschinen gab, aber wann wurde C jemals auf ihnen ausgeführt? Was haben bitweise Operatoren gemacht? Ich kann mir nicht vorstellen, wie sie sich in einem Dezimalsystem verhalten könnten, das die erwartete Beziehung zwischen ihnen aufrechterhält &, |und ^ und garantieren dies auch für alle darstellbaren Werte x und y, x ^ y wäre auch vertretbar.

    – Superkatze

    9. April 2015 um 20:18 Uhr

  • Ich weiß nicht, ob C tatsächlich auf ihnen lief. Wenn dies der Fall wäre, wären bitweise Operationen teuer.

    – Josua

    9. April 2015 um 20:21 Uhr

  • @Joshua: Die einzige Möglichkeit, wie ich eine standardkonforme C-Implementierung sehen kann, die auf einer Dezimalmaschine läuft, wäre, wenn Speicherorte in Gruppen zusammengefasst würden, die darauf beschränkt wären, Zweierpotenzen zu halten (z. B. könnte man jede Gruppe von zehn Dezimalziffern als eine definieren int die Werte von 0 bis 8589934591 enthalten könnte). Leider würden die Beziehungen zwischen Zweier- und Zehnerpotenzen etwas umständlich funktionieren; man könnte sagen, dass jede solche Gruppe als drei interpretiert werden könnte char Werte 0 bis 2047, aber man konnte die Ziffern nicht in 5er Gruppen interpretieren…

    – Superkatze

    9. April 2015 um 21:07 Uhr

1387600cookie-checkWarum wird ((unsigned char)0x80)

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

Privacy policy