Behält die Umwandlung zwischen vorzeichenbehaftetem und vorzeichenlosem int das genaue Bitmuster der Variablen im Speicher bei?

Lesezeit: 6 Minuten

Benutzer-Avatar
Blinken

Ich möchte eine 32-Bit-Ganzzahl mit Vorzeichen übergeben x durch eine Steckdose. Damit der Empfänger weiß, welche Byte-Reihenfolge ihn erwartet, rufe ich an htonl(x) vor dem Absenden. htonl erwartet ein uint32_t obwohl und ich möchte sicher sein, was passiert, wenn ich meine werfe int32_t zu einem uint32_t.

int32_t x = something;
uint32_t u = (uint32_t) x;

Ist es immer so, dass die Bytes in x und u jedes wird genau das gleiche sein? Was ist mit dem Zurückwerfen:

uint32_t u = something;
int32_t x = (int32_t) u;

Mir ist klar, dass negative Werte in große unsignierte Werte umgewandelt werden, aber das spielt keine Rolle, da ich nur am anderen Ende zurückwerfe. Wenn der Cast jedoch mit den tatsächlichen Bytes durcheinander kommt, kann ich nicht sicher sein, dass das Zurücksenden denselben Wert zurückgibt.

Im Allgemeinen wird das Casting in C in Form von Werten angegeben, nicht von Bitmustern – ersteres wird (wenn möglich) beibehalten, letzteres jedoch nicht unbedingt. Im Falle von Zweierkomplementdarstellungen ohne Padding – was für die Fixed-with-Integer-Typen obligatorisch ist – spielt diese Unterscheidung keine Rolle, und die Umwandlung wird in der Tat ein Noop sein.

Aber selbst wenn die Konvertierung von vorzeichenbehaftet zu vorzeichenlos das Bitmuster geändert hätte, hätte eine erneute Konvertierung den ursprünglichen Wert wiederhergestellt – mit der Einschränkung, dass die Konvertierung von vorzeichenbehaftet zu vorzeichenbehaftet außerhalb des Bereichs implementierungsdefiniert ist und ein Signal auslösen kann auf Überlauf.

Für eine vollständige Portabilität (die wahrscheinlich übertrieben sein wird) müssen Sie Typ-Wortspiel anstelle von Konvertierung verwenden. Dies kann auf zwei Arten erfolgen:

Über Pointer-Casts, dh

uint32_t u = *(uint32_t*)&x;

mit dem Sie vorsichtig sein sollten, da es gegen effektive Typisierungsregeln verstoßen kann (aber für vorzeichenbehaftete/vorzeichenlose Varianten von Integer-Typen in Ordnung ist) oder über Vereinigungen, dh

uint32_t u = ((union { int32_t i; uint32_t u; }){ .i = x }).u;

die auch zum Konvertieren von z. B. verwendet werden können double zu uint64_twas Sie mit Zeigerumwandlungen möglicherweise nicht tun, wenn Sie undefiniertes Verhalten vermeiden möchten.

  • Super, das wollte ich wissen. Es wird also immer funktionieren, vorausgesetzt, dass vorzeichenbehaftete Ints mit dem Zweierkomplement dargestellt werden, und dies ist so ziemlich immer der Fall. Ist das korrekt?

    – Blinken

    21. Oktober 2013 um 9:52 Uhr

  • @Andrew: richtig – und selbst auf Nicht-Zweierkomplement-Hardware müsste der Compiler es entweder für die Integer-Typen mit fester Breite vortäuschen oder sie überhaupt nicht bereitstellen; Auch das Setzen eines Signals bei Überlauf sollte in der Praxis kein Problem darstellen

    – Christoph

    21. Oktober 2013 um 10:02 Uhr

  • Verletzt das Pointer-Casting-Beispiel nicht die strikte Aliasing-Regel? int32_t und uint32_t sind inkompatibel. Sind sie nicht?

    – Zulan

    17. April 2016 um 9:00 Uhr

  • @Zulan: signierte und unsignierte Versionen eines Typs kann Alias ​​sein (vgl. C11 Abschnitt 6.5 §7)

    – Christoph

    17. April 2016 um 18:41 Uhr

  • “womit Sie vorsichtig sein sollten, da es gegen wirksame Tippregeln verstoßen kann” können Sie das näher erläutern?

    – polynomial_donut

    20. März 2018 um 18:37 Uhr


Benutzer-Avatar
Filipe Gonçalves

Casts werden in C verwendet, um sowohl “Typkonvertierung” als auch “Typdisambiguierung” zu bedeuten. Wenn Sie so etwas haben

(float) 3

Dann ist es eine Typkonvertierung, und die eigentlichen Bits ändern sich. Wenn du sagst

(float) 3.0

es ist eine Typendisambiguierung.

Unter der Annahme einer 2er-Komplement-Darstellung (siehe Kommentare unten), wenn du an wirfst int zu unsigned int, das Bitmuster wird nicht verändert, nur seine semantische Bedeutung; Wenn Sie es zurückwerfen, wird das Ergebnis immer korrekt sein. Es fällt in den Fall der Typdisambiguierung, da keine Bits geändert werden, sondern nur die Art und Weise, wie der Computer sie interpretiert.

Beachten Sie, dass das 2er-Komplement theoretisch nicht verwendet werden darf, und unsigned und signed können sehr unterschiedliche Darstellungen haben, und das tatsächliche Bitmuster kann sich dabei ändern.

Ab C11 (dem aktuellen C-Standard) ist Ihnen dies jedoch tatsächlich garantiert sizeof(int) == sizeof(unsigned int):

(§6.2.5/6) Für jeden der vorzeichenbehafteten Integer-Typen gibt es einen entsprechenden (aber unterschiedlichen) vorzeichenlosen Integer-Typ (mit dem Schlüsselwort unsigned bezeichnet), der die gleiche Menge an Speicherplatz (einschließlich Vorzeicheninformationen) verwendet und die gleiche hat Ausrichtungsanforderungen […]

Ich würde sagen, dass Sie in der Praxis davon ausgehen können, dass es sicher ist.

  • dies ist falsch – die Konvertierung von vorzeichenbehaftet zu vorzeichenlos kann Bitmuster ändern – es passiert nur so, dass dies bei einer Zweierkomplementdarstellung mit identischer Auffüllung nicht der Fall ist

    – Christoph

    21. Oktober 2013 um 9:29 Uhr

  • Das Ausgangsbeispiel ist falsch, da 3.0 ist ein doubleder Ausdruck (float) 3.0 ist sicherlich auch eine Typkonvertierung.

    – abschalten

    21. Oktober 2013 um 10:00 Uhr

  • @unwind Laut Expert C Programming – Deep C Secrets (Seite 223) handelt es sich um eine Typendisambiguierung, da der Compiler die richtigen Bits überhaupt erst einfügen kann. Es ist wie 3.0f

    – Filipe Gonçalves

    21. Oktober 2013 um 10:06 Uhr

  • Hatte ich benutzt double x = 3.0; float y; y = (float) x;dann wäre es sicherlich eine Typumwandlung

    – Filipe Gonçalves

    21. Oktober 2013 um 10:13 Uhr

  • Dieses Buch scheint mir nicht richtig, oder zumindest seine Unterscheidung nicht sinnvoll. Der Compiler kann die richtigen Bits für pflanzen (float) 3 auch, da es eine konstante Faltung durchführt.

    – Erich

    12. Oktober 2017 um 2:05 Uhr


Dies sollte immer sicher sein, denn die intXX_t Typen stehen garantiert im Zweierkomplement wenn Sie existieren:

7.20.1.1 Ganzzahltypen mit exakter Breite Der Typedef-Name intN_t bezeichnet einen vorzeichenbehafteten Ganzzahltyp mit der Breite N , ohne Füllbits und als Zweierkomplementdarstellung. Int8_t bezeichnet also einen solchen vorzeichenbehafteten Integer-Typ mit einer Breite von genau 8 Bit.

Theoretisch sieht die Rückwandlung aus uint32_t zu int32_t ist die Implementierung definiert, wie für alle unsigned zu signed Konvertierungen. Aber ich kann mir kaum vorstellen, dass eine Plattform anders handeln würde, als Sie erwarten.

Wenn Sie sich dessen wirklich sicher sein wollen, können Sie diese Konvertierung immer noch manuell durchführen. Sie müssten nur einen Wert für testen > INT32_MAX und dann ein bisschen rechnen. Selbst wenn Sie dies systematisch tun, sollte ein anständiger Compiler in der Lage sein, dies zu erkennen und zu optimieren.

  • also ich kann vorstellen dass eine Plattform, die einen Integer-Überlauf abfängt, davon überzeugt werden könnte, dies bei der Typkonvertierung zu tun (z. B. könnte der Compiler eine nutzlose add 0 Anweisung zum Auslösen); Ich wäre sehr überrascht, wenn es einen Compiler gibt, der dies standardmäßig (oder überhaupt) tut. Außerdem würde ich lieber auf Wortspiele setzen als auf Überprüfungen INT32_MAX – Die Fixed-with-Integer enthalten keine Trap-Darstellungen, was den C-Standard betrifft, ist es so sicher wie es nur geht und erfasst tatsächlich die Absicht des Programmierers

    – Christoph

    21. Oktober 2013 um 10:10 Uhr


1373500cookie-checkBehält die Umwandlung zwischen vorzeichenbehaftetem und vorzeichenlosem int das genaue Bitmuster der Variablen im Speicher bei?

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

Privacy policy