Eine Frage, über Vereinigung in C - Geschäft als eine Art und als einen anderen lesen - es ist Implementierung definiert?

StackOverflow https://stackoverflow.com/questions/1812348

Frage

Ich war über Vereinigung in C von K & R Lesen, soweit ich verstanden, kann eine einzelne Variable in der Vereinigung eine der mehreren Arten halten und wenn etwas wie eine Art und extrahiert, wie ein anderer gespeichert wird das Ergebnis rein ist Implementierung definiert .

Jetzt bitte diesen Code-Schnipsel überprüfen:

#include<stdio.h>

int main(void)
{
  union a
  {
     int i;
     char ch[2];
  };

  union a u;
  u.ch[0] = 3;
  u.ch[1] = 2;

  printf("%d %d %d\n", u.ch[0], u.ch[1], u.i);

  return 0;
}

Ausgabe:

3 2 515

Hier ist Wert in den u.ch bin Zuordnung aber sowohl von u.ch und u.i abruft. Ist es Implementierung definiert? Oder mache ich etwas wirklich dumm?

Ich weiß, dass es sehr Anfänger anderer Menschen zu den meisten scheinen mag, aber ich bin nicht in der Lage hinter dem Ausgang, den Grund herauszufinden.

Danke.

War es hilfreich?

Lösung

Das ist nicht definiertes Verhalten. u.i u.ch und sind an der gleichen Speicheradresse befindet. So hängt das Ergebnis des Schreibens in eine und von dem anderen zu lesen auf der Compiler, Plattform, Architektur und manchmal sogar Optimierungsstufe des Compilers. Daher ist der Ausgang für u.i nicht immer 515 werden kann.

Beispiel

Zum Beispiel gcc auf meiner Maschine produziert zwei verschiedene Antworten für -O0 und -O2.

  1. Weil meine Maschine 32-Bit-Little-Endian-Architektur hat, mit -O0 ich mit zwei am wenigsten signifikanten Bytes 2 und 3, zwei höchstwertigen Bytes sind nicht initialisierten initialisiert enden. So Speicher der Union sieht wie folgt aus: {3, 2, garbage, garbage}

    So bekomme ich die Ausgabe ähnlich 3 2 -1216937469.

  2. Mit -O2, erhalte ich die Ausgabe von 3 2 515 wie Sie das tun, was Union Speicher {3, 2, 0, 0} macht. Was passiert, ist, dass gcc das Gespräch mit den tatsächlichen Werten printf optimiert, so dass die Montage Ausgabe sieht aus wie ein Äquivalent:

    #include <stdio.h>
    int main() {
        printf("%d %d %d\n", 3, 2, 515);
        return 0;
    }
    

    Der Wert 515 kann als andere in anderen Antworten auf diese Frage erläutert, erhalten werden. Im Wesentlichen bedeutet dies, dass, wenn gcc optimiert, um den Anruf es Nullen als Zufallswert eines Möchtegern-uninitialisiert Vereinigung gewählt hat.

Schreiben auf ein Gewerkschaftsmitglied und von einem anderen zu lesen in der Regel nicht viel Sinn machen, aber manchmal kann es für Programme nützlich sein, mit strengem Aliasing zusammengestellt.

Andere Tipps

Die Antwort auf diese Frage hängt von dem historischen Kontext, da die Angabe der Sprache, mit der Zeit verändert. Und diese Frage ist nun mal die eine von den Änderungen betroffen sein.

Sie haben gesagt, Sie K & R wurden zu lesen. Die neueste Ausgabe des Buchs (ab sofort), beschreibt die erste standardisierte Version von C-Sprache - C89 / 90. In dieser Version von C-Sprache ein Mitglied der Vereinigung zu schreiben und ein anderes Mitglied zu lesen ist nicht definiertes Verhalten . Nicht Implementierung definiert (das ist eine andere Sache), aber undefined Verhalten. Der relevante Teil des Sprachstandard ist in diesem Fall 6,5 / 7.

Nun, zu einem späteren Zeitpunkt in der Evolution von C (C99-Version von Sprachspezifikation mit Technical Corrigendum 3 angewandt) wurde es plötzlich rechtliche Vereinigung für Typen punning zu verwenden, dh ein Mitglied der Vereinigung zu schreiben und dann noch lesen.

Beachten Sie, dass das zu tun versuchen, noch zu undefiniertem Verhalten führen. Wenn der Wert, den Sie geschieht lesen für die Art Sie es lesen ungültig (so genannte „Trap-Darstellung“) werden durch, dann ist das Verhalten immer noch nicht definiert. Ansonsten liest der Wert Sie ist Implementierung definiert.

Ihre spezifische Beispiel ist relativ sicher für Typ punning von int Array char[2]. Es ist in der Sprache C immer legal den Inhalt eines Objekts als char Array (wiederum 6,5 / 7) neu zu interpretieren.

Allerdings ist das Gegenteil nicht wahr. Das Schreiben von Daten in das char[2] Array Mitglied Ihrer Vereinigung und dann als int lesen kann möglicherweise eine Falle Darstellung erstellen und führen zu nicht definiertes Verhalten . Die potentielle Gefahr besteht, auch wenn Ihr char-Array ausreichende Länge hat den gesamten int abzudecken.

Aber in Ihrem speziellen Fall, wenn int als char[2] größer sein geschieht, die int Sie sich über das Ende des Feldes nicht initialisierten Bereich gelesen wird decken, was wiederum zu undefiniertem Verhalten führt.

Der Grund für die Ausgabe ist, dass auf Ihrem Computer gespeichert sind, ganze Zahlen in Little-Endian Format: das am wenigsten signifikante Bytes zuerst gespeichert werden. Daraus ergibt sich die Bytefolge [3,2,0,0] die ganze Zahl 3 + 2 * 256 = 515.

Dieses Ergebnis hängt von der spezifischen Implementierung und der Plattform.

Die Ausgabe von solchen Code auf Ihrer Plattform und C-Compiler Implementierung abhängig sein. Die Ausgabe lässt mich denken, du läufst diesen Code auf einem litte-Endian-System (wahrscheinlich x86). Wenn Sie sind 515 in i setzen und es in einem Debugger aussehen, würden Sie sehen, dass das niedrigstwertige Byte wäre ein 3 und das nächste Byte im Speicher sein ein 2 wäre, die genau abbildet, was Sie in Kap setzen.

Wenn Sie das getan auf einem Big-Endian-System, würden Sie (wahrscheinlich) haben bekommen 770 (vorausgesetzt, 16-Bit-Ints) oder 50.462.720 (unter der Annahme, 32-Bit-Ints).

Es ist abhängig von der Implementierung und die Ergebnisse auf einer anderen Plattform / Compiler variieren könnte, aber es scheint, das ist, was passiert:

515 binär ist

1000000011

Padding Nullen, um es zwei Bytes (unter der Annahme, 16-Bit-int):

0000001000000011

Die beiden Bytes sind:

00000010 and 00000011

Welche ist 2 und 3

Hope jemand erklärt, warum sie umgekehrt werden -. Meine Vermutung ist, dass Zeichen nicht rückgängig gemacht werden, aber die int ist Little-Endian

Anzahl der zu einer Union zugewiesenen Speicher ist mit dem Speicher gleich erforderlich, um das größte Mitglied zu speichern. In diesem Fall haben Sie einen int und char-Array der Länge 2 Unter der Annahme, int ist 16 Bit und 8-Bit-Zeichen sind, benötigen beide denselben Raum und damit die Union zugeteilten zwei Bytes.

Wenn Sie drei (00000011) und zwei (00000010) mit dem char-Array zuweisen, der Zustand der Vereinigung ist 0000001100000010. Wenn Sie die int aus dieser Verbindung gelesen, wandelt sie das Ganze in und integer. Unter der Annahme, little-endian Darstellung in dem LSB bei niedrigsten Adresse gespeichert ist, die Lese int von der Vereinigung 0000001000000011 würde, welche die binäre für 515 ist.

Hinweis: Dies gilt auch, wenn der 32-Bit-int war - Überprüfen Sie Amnon Antwort

Wenn Sie auf einem 32-Bit-System sind, dann ist ein int 4 Byte, aber sie initialisieren nur nur 2 Bytes. uninitialised Zugriff auf die Daten ist nicht definiertes Verhalten.

Angenommen, Sie auf einem System sind mit 16-Bit ints, dann, was Sie tun noch Implementierung definiert ist. Wenn Ihr System Little-Endian, dann u.ch [0] mit dem niederwertigen Byte von ui entsprechen wird und u.ch 1 wird das höchstwertige Byte sein. Auf einem Big-Endian-System, ist es umgekehrt. Auch dann, wenn der C-Standard nicht die Umsetzung erzwingen Zweier-Komplement Signed Integer darstellen Werte, obwohl Zweierkomplement die häufigste ist. Offensichtlich ist die Größe einer ganzen Zahl ist auch die Umsetzung definiert.

Hinweis: es ist einfacher zu sehen, was passiert, wenn Sie Hexadezimal-Werte verwenden. Auf einem kleinen Endian-System, wäre das Ergebnis in Hex 0x0203 sein.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top