13.4. Unionen

Bei einer Union handelt es sich um ein Datenobjekt, dessen Speicherplatz von mehreren Variablen verschiedenen Typs genutzt werden kann. Die Deklaration (mit dem Schlüsselwort union) und der Zugriff auf die Elemente einer Union erfolgt syntaktisch auf die gleiche Weise wie bei Strukturen. Während für Variablen vom Typ der Struktur


struct conv{
	short sNumber;
	char cByte[2];
	};

so viel Speicherplatz angefordert wird, daß die beiden Variablen sNumber und cByte[2] nebeneinander Platz finden, müssen sich bei der Union


union conv{
	short sNumber;
	char cByte[2];
	};

sNumber und cByte[2] den gleichen Speicherplatz teilen. Der Speicherbedarf einer Union richtet sich also nicht nach der Gesamtgröße aller Elemente, sondern nach dem Platzbedarf des größten Elements.

Zur Initialisierung von Unionen darf nur ein Wert verwendet werden, dessen Datentyp mit jenem des ersten Elements übereinstimmt. Werte anderer Datentypen sind unzulässig und führen zu einer Fehlermeldung. Soll die Variable


/* Zusammensetzung der Union conv
 * siehe oben */
union conv scConv

initialisiert werden, dann ist die Anweisung


union conv scConv = {124}

korrekt,


union conv scConv = {'x', 'y'}

hingegen falsch.

Die Eigenschaft einer Union, mehrere Variablen innerhalb des gleichen Speicherbereichs zu versammeln, eröffnet die Möglichkeit, diesen Speicherabschnitt unter "verschiedenen Gesichtspunkten" zu betrachten. Gemeint ist damit, daß vier hintereinanderliegende Byte wahlweise z.B. als float, long oder als Character-Array mit 4 Elementen betrachtet werden können.

Da nicht alle Größen der fundamentalen Datentypen vom ANSI-Komitee festgelegt wurden und die Ausrichtung der Variablen im Speicher systemabhängig ist, liegt es auf der Hand, daß je nach Plattform verschiedene Ergebnisse auftreten können, wenn ein Wert in Form eines Typs gespeichert und in Form eines anderen Typs gelesen wird. Dies zeigt das nächste Beispiel:


/* conv_ibf.c */

#include <stdio.h>

union conv_ibf{
	int iNumber;
	char cByte[2];
	float fFloat;
	};

int main(void){

union conv_ibf conv;

conv.fFloat = 3.14159;

printf("cByte[1]: %c cByte[0]: %c\n"
	"iNumber: %d fFloat: %f\n",
	conv.cByte[0],
	conv.cByte[1],
	conv.iNumber,
	conv.fFloat);

return 0;
}

Unter DOS produziert conv_ibf diese Ausgabe


cByte[0]: ?_	cByte[1]: ¤_
iNumber: 4048 fFloat: 3.141590

hingegen unter OS/2 Warp die folgende:


cByte[0]: _ cByte[1]: _
iNumber: 1078530000 fFloat: 3.141590

Unionen finden Anwendung für Typenkonvertierungen, wobei im Hinblick auf Portierbarkeit die Abhängigkeit vom verwendeten System beachtet werden muß.

Unionen eignen sich darüber hinaus zur Erstellung von Tabellen, bei denen jedes Feld Werte verschiedenen Typs aufnehmen kann (wie es z.B. bei Tabellenkalkulationen der Fall ist). Wichtig dabei ist, daß über den zuletzt verwendeten Datentyp Buch geführt wird, weil jeder Wert so gelesen werden muß, wie er abgespeichert wurde. Da Unionen diese Möglichkeit nicht anbieten, empfiehlt sich die Verwendung einer Struktur, in die eine Union eingebettet wird.

Das nächste Beispiel zeigt einen einfachen Anwendungsfall einer solchen Tabelle. Sie besteht aus fünfzig Feldern, in die der Reihe nach Werte beliebigen Datentyps eingelesen werden können. Zum Schluß werden alle Werte in umgekehrter Reihenfolge ausgegeben, wobei Sie über den vorliegenden Datentyp informiert werden:


/* uniontab.c */

#include <stdio.h>
#include <stdlib.h>

#include <ctype.h>
#include <string.h>

#define MAXSTRING 127
#define NO_CELL 50

/* Funktions-Prototypen */
struct Zelle *GetData(struct Zelle *cell);
void PrintData(struct Zelle *cell);

/* Aufzählungstyp für Variable, die über
 * den gespeicherten Datentyp Buch führt
 */
enum uType{ STRING, LONG, FLOAT};

/* Union für eingelesenen Wert */
union TabVal{
	char szString[MAXSTRING];
	long lLong;
	float fFloat;
	};

/* Struktur, in die TabVal eingebettet ist */
struct Zelle{
	union TabVal Werte;
	 enum uType Typ;
	 };

/************************************ ***********
 *
 *	main()
 *
 ********************************************* **/

int main(void){

/* Array aus 50 Pointern auf Typ Zelle */
struct Zelle *pCell[NO_CELL] = {NULL};
int iCount = 0;

 puts("Bitte beliebige Daten eingeben!");

 /* Der Reihe nach bis zu max. NO_CELL
  * Werte einlesen
  */
 while( iCount < NO_CELL ){
	pCell[iCount] = GetData( pCell[iCount] );
	if( pCell[iCount] == NULL)
		break;
	 iCount++;
	}

 /* Eingelesene Daten inkl. Information
  * über Datentyp ausgeben und Speicher
  * freigeben
  */
 for( iCount--; iCount >= 0; iCount--){
	PrintData(pCell[iCount]);
	free( pCell[iCount]);
	pCell[iCount] = NULL;
	}

 return 0;
 }

/************************************* **********
 *
 * GetData()
 * liest Zeichen einzeln ein und speichert sie
 * unter Berücksichtigung des korrekten Datentyps
 * in einer Variablen der Union ab.
 * Rückgabewert:
 * Pointer auf struct zelle, falls Daten gespeichert
 * wurden,
 * NULL, bei Eingabe einer Leerzeile
 *
 ********************************************* **/

struct Zelle *GetData(struct Zelle *pCell){

/* Eingabepuffer */
char szBuffer[MAXSTRING];
int iCount = 0;

 /* Für jede Zelle Speicher anfordern */
 pCell = (struct Zelle *) malloc(sizeof(struct Zelle));

 if(pCell == NULL){
	 fputs("Kein Speicher mehr frei!",stderr);
	 exit(EXIT_FAILURE);
	}

 /* Als Standard-Datentyp LONG setzen */
 pCell->Typ = LONG;

 /* Bis zu MAXSTRING Zeichen mit getchar() lesen */
 while( iCount < MAXSTRING){

	szBuffer[iCount] = getchar();

	/* Wenn ein Buchstabe eingeben wurde, dann
	 * auf den Datentyp char[] festlegen
	 */
	if( isalpha(szBuffer[iCount]) )
		 pCell->Typ = STRING;

	/* Wenn ein Whitespace vorliegt (ausgenommen
	 * '\n', das jede Eingabe beendet), dann ist
	 * es ein String
	 */
    else if( isspace(szBuffer[iCount]) &&

		 szBuffer[iCount] != '\n')
		
		 pCell->Typ = STRING;

	/* Wenn ein Interpunktions-Zeichen vorliegt,
	 * dann ist es ebenfalls ein String (aus-
	 * genommen bei einem Punkt, der ja als
	 * Dezimalpunkt dient), es sei denn, die Eingabe
	 * ist schon als String identifiziert
	 */
	else if( ispunct(szBuffer[iCount]) &&
		szBuffer[iCount] != '.')

	 	pCell->Typ = STRING;

	/* Wenn die Eingabe einen Punkt enthält, bis
	 * dato aber nur Ziffern eingelesen wurden,
	 * dann liegt der Typ float vor
	 */
	else if(szBuffer[iCount] == '.' &&
		 pCell->Typ != STRING)

		pCell->Typ = FLOAT;

	/* Wenn das erste Zeichen ein '+' oder
	 * ein '-' ist, Datentyp auf LONG setzen
	 */
	if((szBuffer[iCount] == '+' ||
		szBuffer[iCount] == '-') &&

		iCount == 0)

		 pCell->Typ = LONG;

	/* Beim Auftreten von '\n' Eingabe beenden */
	 if(szBuffer[iCount] == '\n'){
		szBuffer[iCount] = '\0';
		break;
	 	}

	iCount++;
	}

 /* Wenn Wert nur aus '\n' besteht (also
  * Leerzeile vorliegt), NULL zurückgeben
  */
 if( iCount == 0 ){
	free(pCell);
	return NULL;
	}

 /* Abhängig vom Datentyp Werte an die
  * Variablen der Union zuweisen
  */
 switch(pCell->Typ){
	case LONG: pCell->Werte.lLong = atol(szBuffer);
	 	break;
	case FLOAT: pCell->Werte.fFloat = atof(szBuffer);
		break;
	case STRING: strcpy(pCell->Werte.szString, szBuffer);
		break;
		}
 return pCell;
 }
/************************************* **********
 *
 * PrintData()
 * gibt eingelese Daten unter Berücksichtigung
 * ihres Datentyps mit printf() aus
 *
 ********************************************* **/

void PrintData(struct Zelle *pCell){

 switch(pCell->Typ){
	case LONG: printf("%6s\t%ld\n",
		"LONG", pCell->Werte.lLong);
		break;
	case FLOAT: printf("%6s\t%f\n",
		"FLOAT", pCell->Werte.fFloat);
		break;
	case STRING: printf("%6s\t%s\n",
		"STRING", pCell->Werte.szString);
		break;
		 }
}