Programmieren
 
 
 
 
 
C-Grundkurs

 
 
 
 
.. Varianten - union
 
 

 
 
 
 


Normalerweise besitzen Variable der Programmiersprache C einen fest zugeordneten Typ. Bei der union (Variante) wird diese Gesetzmässigkeit unterbrochen. Varianten sind eine Art Variable mit Untervariablen, bei der die Untervariablen unterschiedliche Typen besitzen können. Von diesen darf allerdings zu einem bestimmten Zeitpunkt, nur eine einen Wert besitzen. (Letzteres unterscheidet Varianten von den Strukturen, bei denen alle Untervariablen gleichzeitig einen Wert enthalten können).

Der Grund für die Einführung dieses Variablentyps liegt in der Menge des benötigten Speichers. Die Untervariablen teilen sich nämlich alle den gleichen Speicherplatz und so wird nie mehr Speicher belegt, als es für den grössten Variablentyp nötig ist. Im unteren Beispiel wären dies 8 Bytes für die Fliesskommavariable x.

Beispiel:


union zahlen { int i;
               double x;
               char c;
             } z;


Im oberen Beispiel wird eine Variante vom Typ union zahlen erklärt und eine Variable z mit diesem Typ deklariert. Die Variable z besitzt die Untervariablen i, x, und c, deren Variablentypen voneinander abweichen.
Die Variable z wird im Speicher eines Computers 8 Bytes benötigen, denn die grösste Untervariable ist vom Typ double, und der benötigt 8 Bytes Anstelle einer doppeltgenauen Fliesskommazahl bietet die Variable z auch Raum für einen Integer i (4 Bytes) oder einen Character c (1 Byte)






Zuweisung von Werten und Zugriff auf die Variableninhalte
Um die Untervariablen zu erreichen, werden sie durch einen Punkt '.' von der Hauptvariablen getrennt. Die Zeile ..
z.i = 1234
.. füllt die Untervariable i der Hauptvariablen z mit dem Wert 1234. Dementsprechend, wird die Fliesskomma-Untervariable x wie folgt mit einem Wert beschrieben.
z.x = 4321.567

Sollen die Variablen wieder gelesen werden, gilt Gleiches. Eine Variable f vom typ double kann mit dem Inhalt von z.x im Verbund mit einer Berechnung, beispielsweise durch folgende Zeile gefüllt werden.
f = z.x – 26.36

Unsinnig wäre der Versuch, beiden Untervariablen gleichzeitig einen Wert zuzuordnen, denn wie bereits gesagt, es steht bei Varianten nur ein Speicherbereich zur Verfügung. Entweder die eine oder die andere Untervariable kann gefüllt werden.

Das nachfolgende Programm zeigt diese Eigenschaften aus der Warte des Programmierers.
Es deklariert die bisher besprochene union zahlen und leitet aus ihr eine Hauptvariable mit der Bezeichnung z ab. Diese besteht aus den Untervariablen z.i, z.x und z.c. Jede dieser Variablen wird einmal mit einem Wert geladen, der dann ausgedruckt wird. Um zu zeigen, dass es in diesem Fall unsinnig ist die anderen Variablen zu benutzen, werden auch ihre Werte gelesen und ausgegeben.

Das Programm besteht aus zwei Teilen, wobei die Funktion scan_union() grundsätzlich alle 8 Bytes der Variablen z ausliest und hexadezimal darstellt. Je zwei hexadezimale Ziffern stellen ja bekannterweise den Inhalt eines Bytes dar, und so werden von scan_union() 16 Ziffern für die 8 Bytes erzeugt. scan_union() stellt den acht gelesen Bytes die Adresse des ersten Bytes voraus. Mehr braucht man von dieser Funktion nicht zu wissen, aber es soll niemand davon abgehalten werden, über ihre Arbeitsweise weiter nachzudenken.

scan_union() gibt also nach jeden Aufruf den Inhalt aller 8 Bytes der Variablen z wieder und nennt auch die Ablageadresse des ersten Bytes im Speicher. Dabei ist es interessant zu sehen, dass tatsächlich alle drei Untervariablen auf der gleichen Speicheradresse beginnen.

main() gibt die Adressen zur Kontrolle auch noch einmal aus, bestimmt aber darüberhinaus für jede Variable die Anzahl der belegten Bytes. Hier wird deutlich, dass die Hauptvariable z tatsächlich genausoviel Speicherbytes in Anspruch nimmt, wie ihre grösste Untervariable, und das ist mit 8Bytes die Variable z.x.
In der Folge wird jeder Untervariablen ein Wert zugewiesen, und mit scan_union() die daraus resultierende Speicherbelegung ermittelt. Dass das Integer-Zahlenformat nur sehr wenig mit dem Fliesskomma-Zahlenformat zu tun hat wird an den jeweils ausgegebenen Zahlen deutlich. Aber auch bei int und char wird deutlichen, das Fehler resultieren würden, wenn man eine als char abgelegte Untervariable als Integer aus der union herauslesen würde. Nach mehrfachem Gebrauch der union, stehen nämlich in den Speicherzellen die nicht zum Character gehören, die Ziffern der vorhergehenden Benutzung.






Unions oder Varianten sind eine Art Variable mit einer Untervariablen, bei der die Untervariable allerdings unterschiedliche Typen besitzen kann. Anders als bei der Struktur (struct) teilt sich die eine Untervariable den verfügbaren Speicherraum der union mit den anderen möglichen Variablen der alternativen Variablentypen.


Dementsprechend bestimmte der Variablentyp mit dem grössten Speicherbedarf den von der union belegten Speicherplatz. Bei der unten angegebenen Union ist das die Fliesskommavariable x mit 8Byte. denn für den Integer i werden 2 Bytes, für die Fliesskommazahl x, 8 Bytes und für den Character c, 1 Byte benötigt.


Beispiel 1:


union {
         int i;
         double x;
         char c;
       } y, z;



Im oberen Beispiel werden zwei Variablen y und z einer Union deklariert. Jede dieser zwei Variablen y und z kann entweder einen Integer (int) mit der Bezeichnung i, oder eine Fliesskommazahl vom Typ double, Bezeichnung x, oder einen Character (char) mit der Bezeichnung c aufnehmen. Es können nie alle drei Variablen gleichzeitig in der Union vorhanden sein.
Die Variablen y und z werden im Speicher eines Computers jeweils mindestens 8 Bytes benötigen, denn den grössten Umfang an Speicher benötigt die Fliessommazahl mit eben 8 Bytes. Die genaue Anzahl, die von der Ausrichtung im Speicher abhängt kann mit dem sizeof()-Operator ermittelt werden.


 

 



Beispiel 2:


union zahl {
              int i;
              double x;
              char c;
            };



Im oberen Beispiel wird eine Union mit dem Namen Zahl deklariert. In der Folge kann der Name dieser Union wie ein Typbezeichner verwendet werden, so dass man zu beliebiger Zeit, Variable von der Art dieser Union erzeugen kann. Beispielsweise ..


union zahl y;

                        oder an andere Stelle ..

union zahl z;


Wie bereits angemerkt kann jede dieser zwei Variablen y und z, zu einer Zeit, eine der Untervariablen i, x, und c, deren Variablentypen voneinander abweichen, aufnehmen.


 

 



Beispiel 3:


typedef struct {
                 int i;
                 double x;
                 char c;
               } zahl;



Wen es stört, dass man vor den Namen des Union-Variablentyps immer union schreiben muss, der kann mit der Präprozessordirektive typedef arbeiten. Die funktioniert ein Stück weit wie #define. Es wird also das gesamte Literal union { ...} hinter dem Ausdruck zahl verborgen. Nun kann man so tun, als sei der Unionname zahl ein eigenständiger Variablentyp, denn man darf schreiben ..


zahl y;

                        oder an andere Stelle ..

zahl z;


.. was indirekt dem Beispiel 1 ähnlich ist.

Wie bereits angemerkt kann jede dieser zwei Variablen y und z, zu einer Zeit, eine der Untervariablen i, x, und c, deren Variablentypen voneinander abweichen, aufnehmen.






Deklaration und Initialisation

Der nachfolgende Ausdruck wird zwar vom Compiler nicht mit einer Fehlermeldung begleitet und würde auch für die Integervariable int i, sowie die Charactervariable char c zur Übernahme eines richtigen Wertes führen, bei dem Versuch den Speicher der Union mit einer Fliesskommazahl double x zu füllen schlägt er aber fehl. Kurz und gut, die sichere Initialisation der Union auf diesem Weg ist nicht möglich.


UnBeispiel:

zahl z = {1234}; // funktioniert zwar

zahl z = {1234.57}; // funktioniert nicht


 

 



Zuweisung von Werten und Zugriff auf die Variableninhalte zur Laufzeit
Um die Untervariablen zu erreichen, werden sie durch einen Punkt '.' von der Hauptvariablen getrennt. Die Zeile ..
z.i = 0x1234;
.. füllt die Untervariable i der Hauptvariablen z mit dem Wert 1234h. Dementsprechend wird die Fliesskomma-Untervariable x, wie folgt mit einem Wert beschrieben.
z.x = 1234.57;

Sollen die Variablen wieder gelesen werden gilt gleiches. Eine Variable r vom typ double kann mit dem Inhalt von z.x im Verbund mit einer Berechnung, beispielsweise durch folgende Zeile gefüllt werden.
r = z.x – 26.36;


Bei Unions ist es (anders als bei Strukturen ) nicht möglich, jeder Untervariablen gleichzeitig einen eigenen Wert einzuschreiben. Das nachfolgende Programm zeigt diese Eigenschaften aus der Warte des Programmierers.


Ein Beispielprogramm

Es deklariert die bisher besprochene union zahl und leitet in main() aus dieser eine Variable mit der Bezeichnung z ab. Diese kann aus einer der Untervariablen z.i, z.x oder z.c bestehen. Jede dieser Variablen wird folgend und nacheinander mit einem neuen Wert beschrieben, der dann in Form eines Speicherbyte- Listings ausgegeben wird. Um den Einfüllablauf der Werte in den Speicherraum der Struktur im Detail zu zeigen, wurden nach jeder Setzung eines Wertes, alle Speicherzellen des Speicherraums ausgegeben.

Das Programm besteht aus zwei Teilen, wobei die Funktion scan_union() grundsätzlich alle 8 Bytes der Variablen z ausliest und hexadezimal darstellt. Je zwei hexadezimale Ziffern stellen ja bekannterweise den Inhalt eines Bytes dar, und so werden von scan_union() 16 Ziffern für die 8 Bytes erzeugt. scan_union stellt den acht gelesen Bytes die Adresse der ersten Bytes voraus. Mehr braucht man von dieser Funktion nicht zu wissen, aber es soll niemand davon abgehalten werden, über ihre Arbeitsweise weiter nachzudenken.

scan_union() gibt also nach jedem Aufruf den Inhalt aller 8 Bytes der Variablen z wieder und nennt auch die Ablageadresse des ersten Bytes im Speicher. Dabei ist es interessant zu sehen, dass tatsächlich für jede neue Variable der gleiche Speicherraum benutzt wird, und diese die Ziffern der vorhergehenden Variable zu Teilen überschreibt.

main() gibt die Adresse von z auch noch einmal aus, darüber hinaus aber auch die der Untervariablen. Interessant ist es, dass alle Adressen gleich sind, obwohl bei dem char-Wert nur die hinteren zwei Bytes benutzt werden. Das der sizeof()-Operator für z.c den Wert 1 (Byte) ermittelt, kann nur angenommen werden, dass der Speicherraum der union, intern von rechts nach links ausgewertet wird.





// union.cpp
// ------------  Version 17.06.2007
//               Borland IDE C++ 3.1 oder 5.02
//               Model:  SMALL
//               Char:   unsigned
//               bearbeitet: www.GoBlack.de, D.Schwarzer

// Hardware:     jeder DOS-PC

#include <stdio>    // für printf()
#include <conio>    // für getch()

// typedef union {int i; double x; char c;} zahl; // Alternative
union zahl {int i; double x; char c;};


// Dieser Programmteil liest die 11 Bytes der übergebenen Variablen
// z vom Typ (union) zahl und gibt sie samt Anfangsadresse
// aufeinanderfolgend zum Bildschirm aus.

void scan_union(zahl z)
{
  int n=0;
  char* m;

  m = (char*)&z;
  printf ("\n Adr von z: %X - Inhalt: ", &z);
  for (n=sizeof(z)-1; n>=0; n--){printf("%02X", *(m+n));}
}

// das Beispielprogramm ermittelt für die Variable z und
// jede ihrer Untervariablen z.i, z.x, z.c die Anzahl der
// belegten Bytes, sowie deren Anfangsadressen im Speicher
// dann wird jeder der drei Untervariablen ein legaler
// Wert zugewiesen und beobachtet zu welchem Resultat das
// bei der Füllung der Struktur und ihrer Untervariablen
// führt.

void main (void)
{
  // Deklaration und Definition einer Variablen z vom neuen Typ
  // (union)zahl. Sie kann nur einen Wert aufnehmen
  union zahl z={0};

  // Anzahl der belegten Bytes im Speicher und ..
  // Anfangsadresse der Variablen
  printf ("\nbelegte Bytes im Speicher / AnfangsAdresse");
  printf ("\nz   = %2d   Adr: %04Xh", sizeof(z),   &z);
  printf ("\nz.i = %2d   Adr: %04Xh", sizeof(z.i), &z.i);
  printf ("\nz.x = %2d   Adr: %04Xh", sizeof(z.x), &z.x);
  printf ("\nz.c = %2d   Adr: %04Xh", sizeof(z.c), &z.c);
  printf ("\n-----");

  // Wert für z.i => noch keine Werte für z.x und z.c
  z.i = 0x1234;
  scan_union(z);
  printf ("\n Wert fuer IntegerVariable z.i");
  printf ("\nInhalt hex   z.i = %04Xh",  z.i);
  printf ("\nInhalt float z.x = %g",     z.x);
  printf ("\nInhalt hex   z.c = %02Xh",  z.c);
  printf ("\n-----");

  // Wert für z.x => noch kein Wert für z.c
  z.x = 1234.57;
  scan_union(z);
  printf ("\n Wert fuer FliesskommaVariable z.x");
  printf ("\nInhalt hex   z.i = %04Xh",  z.i);
  printf ("\nInhalt float z.x = %g",     z.x);
  printf ("\nInhalt hex   z.c = %02Xh",  z.c);
  printf ("\n-----");

  // Wert für z.c => Struktur ist gefüllt
  z.c = 0xC3;
  scan_union(z);
  printf ("\n Wert fuer CharacterVariable z.c");
  printf ("\nInhalt hex   z.i = %04Xh",  z.i);
  printf ("\nInhalt float z.x = %g",     z.x);
  printf ("\nInhalt hex   z.c = %02Xh",  z.c);

  getch();
}

 

 

 




Der Ausgabebildschirm des obigen Programms. Zu beachten ist, dass die Zuweisung von Adressen der Variablen zur Laufzeit erfolgt, .. sie können also bei jedem Lauf des Programms andere Werte annehmen.


 

 

 


Hinweis:
Eine Zeile, die nur der Hauptvariablen z einen Wert zuweist, (z.B. z = 1234) führt zu einer Fehlermeldung des Compilers.


 

 
www..de