Rundum-Erklärung für vorzeichenbehaftete und vorzeichenlose Variablen in C?

Lesezeit: 8 Minuten

Benutzer-Avatar
orustammanapov

Ich habe ein bisschen in der C-Spezifikation gelesen, dass vorzeichenlose Variablen (insbesondere unsigned kurz int) führen einige sogenannte umwickeln auf Ganzzahlüberlauf, obwohl ich nichts zu vorzeichenbehafteten Variablen finden konnte, außer dass ich mit gegangen bin undefiniertes Verhalten.

Mein Professor hat mir gesagt, dass ihre Werte auch herumgewickelt werden (vielleicht meinte er nur gcc). Ich dachte, die Bits werden einfach abgeschnitten und die Bits, die ich hinterlassen habe, geben mir einen seltsamen Wert!

Was Wrap-Around ist und wie unterscheidet es sich vom einfachen Abschneiden von Bits?

  • Ihr Professor liegt falsch, es sei denn, seine Aussage, dass Werte umlaufen, wurde für bestimmte C-Implementierungen qualifiziert, möglicherweise mit bestimmten Flags in Bezug auf Optimierung oder Semantik.

    – Eric Postpischil

    7. November 2013 um 17:31 Uhr

  • @EricPostpischil was meinst du mit spezifischer C-Implementierung und wie ist sie im Allgemeinen?

    – orustammanapov

    7. November 2013 um 17:47 Uhr


  • Der C-Standard definiert nicht, was passiert, wenn ein ganzzahliger Überlauf auftritt. Eine C-Implementierung kann jedoch definieren, was passiert. C-Implementierungen dürfen über den C-Standard hinausgehen. So ist es beispielsweise möglich, dass GCC eine Anweisung ausgibt, die besagt: „Wenn ein Ganzzahlüberlauf auftritt und die -fSomething switch beim Kompilieren verwendet wird, dann wird das Ergebnis in das Zweierkomplement gepackt.“ Oder, wenn GCC dies nicht angibt, kann Fred Doe die GCC-Quellen auschecken, sie nach Wunsch ändern und eine neue Fred-spezifische Version von GCC herausgeben, die sich auf eine bestimmte Weise verhält.

    – Eric Postpischil

    7. November 2013 um 18:03 Uhr


Benutzer-Avatar
AnT steht zu Russland

Vorzeichenbehaftete Integer-Variablen haben in der Sprache C kein Umlaufverhalten. Vorzeichenbehafteter Ganzzahlüberlauf während arithmetischer Berechnungen erzeugt undefiniertes Verhalten. Beachten Sie übrigens, dass der von Ihnen erwähnte GCC-Compiler für die Implementierung bekannt ist strenge Überlaufsemantik in Optimierungen, was bedeutet, dass es die Freiheit nutzt, die durch solche undefinierten Verhaltenssituationen geboten wird: Der GCC-Compiler geht davon aus, dass vorzeichenbehaftete Ganzzahlwerte niemals umlaufen. Das bedeutet, dass GCC tatsächlich einer der Compiler ist, in denen Sie kann nicht Verlassen Sie sich auf das Umlaufverhalten von vorzeichenbehafteten Integer-Typen.

Beispielsweise kann der GCC-Compiler dies für Variablen annehmen int i folgende Bedingung

if (i > 0 && i + 1 > 0)

ist gleichbedeutend mit einem bloßen

if (i > 0)

Das ist genau das, was strenge Überlaufsemantik meint.

Ganzzahltypen ohne Vorzeichen implementieren Modulo-Arithmetik. Der Modulo ist gleich 2^N wo N ist die Anzahl der Bits in der Wertdarstellung des Typs. Aus diesem Grund scheinen vorzeichenlose Integer-Typen tatsächlich beim Überlauf umgebrochen zu werden.

Die C-Sprache führt jedoch niemals arithmetische Berechnungen in Domänen durch, die kleiner sind als die von int/unsigned int. Typ unsigned short int die Sie in Ihrer Frage erwähnen, wird normalerweise zum Tippen befördert int in Ausdrücken, bevor Berechnungen beginnen (unter der Annahme, dass der Bereich von unsigned short passt ins Sortiment int). Was bedeutet, dass 1) die Berechnungen mit unsigned short int wird im Bereich von vorgeformt intwobei der Überlauf passiert, wenn int Überläufe, 2) Überläufe während solcher Berechnungen führen zu undefiniertem Verhalten, nicht zu Umlaufverhalten.

Dieser Code erzeugt beispielsweise einen Wraparound

unsigned i = USHRT_MAX;
i *= INT_MAX; /* <- unsigned arithmetic, overflows, wraps around */

während dieser Code

unsigned short i = USHRT_MAX;
i *= INT_MAX; /* <- signed arithmetic, overflows, produces undefined behavior */

führt zu undefiniertem Verhalten.

Wenn nein int es kommt zu einem Überlauf und das Ergebnis wird wieder in ein umgewandelt unsigned short int Typ, es wird wieder modulo reduziert 2^Nwas so aussieht, als ob der Wert umgebrochen wäre.

  • wofür steht “unsigned i”? Du hast es sehr gut erklärt, könntest du es an diesem Beispiel zeigen: short int y = 511, z = 512; y*=z;

    – orustammanapov

    7. November 2013 um 20:57 Uhr


  • @orustammanapov: unsigned i steht für unsigned int iso wie long i steht für long int i. int ist in solchen Zusammenhängen impliziert. Was dein Beispiel angeht, y*=z wird interpretiert als y = (short) ((int) y * (int) z)was darauf hinausläuft y = (short) 261632. Beachten Sie, dass es in diesem Fall bei arithmetischen Auswertungen (die in int vollkommen gut), aber es gibt einen Überlauf während der Konvertierung zurück zu short. Das Verhalten in diesem Fall ist implementierungsabhängig.

    – AnT steht zu Russland

    7. November 2013 um 21:05 Uhr


  • Ich frage mich, wie viel realer Vorteil es ist, einen Integer-Überlauf ohne Einschränkungen für UB zu machen, anstatt zu sagen, dass Compiler Berechnungen mit Integer-Typen zusätzliche Genauigkeit hinzufügen und beliebige oder alle Bits mit übermäßiger Genauigkeit jederzeit neu schreiben können, wenn sie nicht alle damit einverstanden sind das Vorzeichenbit, und können diese zusätzliche Präzision nach Belieben in nichtflüchtigen Integer-Variablen speichern? Solche Regeln würden es einem Compiler ermöglichen, sie zu berücksichtigen i+1 > i als bedingungslos wahr, würde es Compilern jedoch nicht erlauben, die Kausalität zu negieren. Wie viel Vorteil ergibt sich daraus, darüber hinauszugehen?

    – Superkatze

    14. Mai 2015 um 19:45 Uhr


Benutzer-Avatar
Johannes Bode

Stellen Sie sich vor, Sie haben einen Datentyp, der nur 3 Bit breit ist. Auf diese Weise können Sie 8 unterschiedliche Werte von 0 bis 7 darstellen. Wenn Sie 1 bis 7 addieren, werden Sie auf 0 zurückgeführt, da Sie nicht genügend Bits haben, um den Wert 8 (1000) darzustellen.

Dieses Verhalten ist für vorzeichenlose Typen wohldefiniert. es ist nicht gut definiert für vorzeichenbehaftete Typen, da es mehrere Methoden zum Darstellen von vorzeichenbehafteten Werten gibt und das Ergebnis eines Überlaufs basierend auf dieser Methode unterschiedlich interpretiert wird.

Vorzeichengröße: das oberste Bit repräsentiert das Vorzeichen; 0 für positiv, 1 für negativ. Wenn mein Typ wieder drei Bit breit ist, kann ich vorzeichenbehaftete Werte wie folgt darstellen:

000  =  0
001  =  1
010  =  2
011  =  3
100  = -0
101  = -1
110  = -2
111  = -3

Da ein Bit für das Vorzeichen belegt ist, habe ich nur zwei Bits, um einen Wert von 0 bis 3 zu codieren. Wenn ich 1 bis 3 addiere, werde ich mit -0 als Ergebnis überlaufen. Ja, es gibt zwei Darstellungen für 0, eine positive und eine negative. Sie werden nicht allzu oft auf eine Vorzeichen-Größen-Darstellung stoßen.

Einerkomplement: Der negative Wert ist die bitweise Umkehrung des positiven Werts. Verwenden Sie erneut den Drei-Bit-Typ:

000  =  0
001  =  1
010  =  2
011  =  3
100  = -3
101  = -2
110  = -1 
111  = -0

Ich habe drei Bits, um meine Werte zu codieren, aber der Bereich ist [-3, 3]. Wenn ich 1 zu 3 addiere, werde ich mit -3 als Ergebnis überlaufen. Dies unterscheidet sich von dem obigen Vorzeichen-Größen-Ergebnis. Auch hier gibt es zwei Kodierungen für 0, die diese Methode verwenden.

Zweierkomplement: Der negative Wert ist die bitweise Umkehrung des positiven Werts plus 1. Im Drei-Bit-System:

000  =  0
001  =  1
010  =  2
011  =  3
100  = -4
101  = -3
110  = -2
111  = -1

Wenn ich 1 zu 3 addiere, werde ich als Ergebnis mit -4 überlaufen, was sich von den beiden vorherigen Methoden unterscheidet. Beachten Sie, dass wir einen etwas größeren Wertebereich haben [-4, 3] und nur eine Darstellung für 0.

Das Zweierkomplement ist wahrscheinlich die gebräuchlichste Methode zur Darstellung vorzeichenbehafteter Werte, aber es ist nicht die einzige, daher kann der C-Standard keine Garantie dafür geben, was passiert, wenn Sie einen vorzeichenbehafteten Integer-Typ überlaufen lassen. Also verlässt es das Verhalten nicht definiert Der Compiler muss sich also nicht mit der Interpretation mehrerer Darstellungen befassen.

  • Die unterschiedlichen vorzeichenbehafteten Darstellungen waren der ursprüngliche Grund dafür, den vorzeichenbehafteten arithmetischen Überlauf undefiniert zu lassen. Jetzt haben Compiler damit begonnen, undefiniertes Verhalten zur Optimierung zu nutzen, sodass es nicht sicher ist, sich auf einen vorzeichenbehafteten Überlauf zu verlassen, der Zweierkomplement-Ergebnisse erzeugt, selbst wenn Sie wissen, dass Sie auf eine Zweierkomplement-Architektur abzielen. Siehe zB airs.com/blog/archives/120

    – Pascal Cuoq

    7. November 2013 um 17:58 Uhr


Benutzer-Avatar
diapir

Das undefiniertes Verhalten stammt aus frühen Portabilitätsproblemen, als vorzeichenbehaftete Integer-Typen entweder als Vorzeichen und Betrag, Einerkomplement oder Zweierkomplement dargestellt werden konnten.

Heutzutage stellen alle Architekturen ganze Zahlen als Zweierkomplement dar, die umlaufen. Aber seien Sie vorsichtig: Da Ihr Compiler zu Recht davon ausgeht, dass Sie kein undefiniertes Verhalten ausführen werden, könnten Sie auf seltsame Fehler stoßen, wenn die Optimierung aktiviert ist.

  • Bedeutet das, dass ich auch mit vorzeichenbehafteten Ganzzahlen einen Wrap-around bekomme?

    – orustammanapov

    7. November 2013 um 17:46 Uhr

  • @orustammanapov: Nein. Selbst wenn die zugrunde liegende Hardware ein intrinsisches Verhalten dafür aufweist, wie Überläufe behandelt werden oder welche Art von Wrapping auftritt, muss die C-Implementierung sie nicht verwenden. Der C-Standard besagt, dass das Verhalten beim Überlauf von vorzeichenbehafteten Ganzzahlen undefiniert ist. Dies bedeutet, dass die C-Implementierung den Code optimieren kann, ohne Rücksicht darauf, was im Falle eines Überlaufs passiert. Dies kann dazu führen, dass Verhaltensweisen auftreten, als ob Werte umbrochen würden, oder dass Verhaltensweisen auftreten, als ob ein Trap aufgetreten wäre, oder es kann andere Werte erzeugen, als das Umwickeln erzeugen könnte.

    – Eric Postpischil

    7. November 2013 um 17:54 Uhr


In einer vorzeichenbehafteten 8-Bit-Ganzzahl könnte die intuitive Definition von Wraparound so aussehen, als ob sie von +127 nach -128 geht – im Zweierkomplement binär: 0111111 (127) und 1000000 (-128). Wie Sie sehen können, ist dies der natürliche Fortschritt bei der Inkrementierung der Binärdaten – ohne zu berücksichtigen, dass sie eine ganze Zahl mit oder ohne Vorzeichen darstellen. Entgegen der Intuition findet der eigentliche Überlauf statt, wenn von -1 (11111111) auf 0 (00000000) im Sinn des Umlaufs der vorzeichenlosen Ganzzahl gewechselt wird.

Dies beantwortet nicht die tiefere Frage, was das richtige Verhalten ist, wenn eine vorzeichenbehaftete Ganzzahl überläuft, da es laut Standard kein “richtiges” Verhalten gibt.

1137430cookie-checkRundum-Erklärung für vorzeichenbehaftete und vorzeichenlose Variablen in C?

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

Privacy policy