Bisher wurden mit Arrays Datenstrukturen des selben Typs verwendet. In den folgenden Kapitel werden jetzt unterschiedliche Datentypen zu einer Struktur zusammengefasst. Damit kann anschließen auf diese Struktur zugegriffen werden, wie auf einfache Variablen. C C++ C/C++ Strukturen Arrays von Strukuren Strukturen in Strukturen Datenstrukturen enum typedef Bitfelder Unions Strukturen - Arrays von Strukuren Strukturen in Strukturen Datenstrukturen enum typedef Bitfelder Unions Kapitel 18: Strukturen

18.10. Aufzählungstyp enum            zurück  Ein Kapitel tiefer  zum Inhaltsverzeichnis

Das Schlüsselwort enum dient der Aufzählung von Konstanten. Mit dieser Struktur lassen sich beim Programmstart eine Reihe von Konstanten festlegen. Ein Beispiel:

#include <stdio.h>

enum zahl {NU_LL,EINS,ZWEI,DREI,VIER};

int main()
{
   enum zahl x;
   x=NU_LL;
   printf("%d\n",x);

   x=EINS;
   printf("%d\n",x);

   x=ZWEI;
   printf("%d\n",x);

   x=DREI;
   printf("%d\n",x);

   x=VIER;
   printf("%d\n",x);
   return 0;
}

Bei Ausführung des Programms werden die Zahlen von null bis vier auf dem Bildschirm ausgegeben. Die Aufzählung lautet hier:

enum zahl {NU_LL,EINS,ZWEI,DREI,VIER}; 

In der Regel beginnt der Aufzählungstyp, sofern nicht anders angegeben, mit 0; also NU_LL=0. Das nächste Feld, wenn nicht anders angegeben, hat den Wert 1. Somit ist EINS auch 1. gleichbedeutend hätte man dies auch so schreiben können:

enum zahl {NU_LL=0,EINS=1,ZWEI=2,DREI=3,VIER=4};  

Wird enum hingegen so benutzt

enum farben {rot, gelb=6, blau, gruen};

würden folgende Konstanten definiert werden:

enum farben {0, 6, 7, 8};

Die Farbe gelb wurde mit dem Wert 6 initialisiert. Die Steigerung des Werts zur nächsten Konstante beträgt bei enum immer plus eins. Somit hat die Konstante blau den Wert 7 und gruen den Wert 8.

Häufig wird enum zur Nachbildung der boolschen Variablen verwendet. Hier das Beispiel dazu:

#include <stdio.h>

enum BOOL {FALSE, TRUE};

int main()
{
   int zahl;
   printf("Gib mir eine Zahl (0-9): ");

   if( (scanf("%d",&zahl)) == FALSE )
      printf("Das war keine Zahl!\n");
   else
      printf("Vielen Dank!\n");

   if( (zahl==7) == TRUE)
      printf("Wow, die 7, meine Lieblingszahl\n");
   return 0;
}

Der Aufzählungstyp enum dient der besseren Lesbarkeit eines Programms. BOOL (in C++ gibt es diesen Datentyp wirklich) könnten Sie aber auch als Makro implementieren:

#define BOOL int
#define FALSE 0
#define TRUE  1

Es gibt also viele Wege, die ans Ziel führen. Die zwölf Monate eines Jahres könnten Sie mit enum beispielsweise so realisieren:

enum Monate {
         JAN=1,FEB,MAR,APR,MAI,JUN,JUL,AUG,SEP,OKT,NOV,DEC
            }

oder mit define so:

#define JAN  1
#define FEB  2
#define MAR 3
#define APR  4
#define MAI  5
...

Bei beiden Beispielen wurden die einzelnen Monate als Konstanten definiert. Wo liegt dann der Unterschied zwischen enum und einer Reihe von Präprozessor-Defines? Ironischerweise besteht kaum ein Unterschied. Geplant war (laut ANSI C-Standard) enum, um ohne Casts verschiedene integrale Typen vermischen zu können, was ja sonst in der Regel einen Compilerfehler zur Folge hat. So hätten eine Menge Programmierfehler aufgefangen werden können.

Aber enum hat auch Vorteile:

18.11. Typendefinition mit typedef            zurück  Ein Kapitel tiefer  Ein Kapitel höher  zum Inhaltsverzeichnis

Mit dem Schlüsselwort typedef kann ein neuer Bezeichner für einen einfachen Datentyp verwendet werden. Die Syntax einer einfachen Typendefinition sieht so aus:

typedef Typendefinition Bezeichner; 

Damit lässt sich die Lesbarkeit eines Programms erheblich verbessern. Diese Typendefinition mit typedef soll anhand des Adressprogramms demonstriert werden. Hier das Listung dazu:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MAX 30

static int x;

struct adres {
               char vname[MAX];
               char nname[MAX];
               long PLZ;
               char ort[MAX];
               int geburtsjahr;
             }adressen[100];

typedef struct adres ADRESSE;

void Eingabe(int nr, ADRESSE *neu)
{
   printf("Vorname : ");
   fgets(neu[nr].vname, MAX, stdin);
   printf("Nachname : ");
   fgets(neu[nr].nname, MAX, stdin);
   printf("Postleitzahl: ");
   do {
       scanf("%5ld",&neu[nr].PLZ);
      } while(getchar()!= '\n');
   printf("Wohnort : ");
   fgets(neu[nr].ort, MAX, stdin);
   printf("Geburtsjahr : ");
   do {
        scanf("%4d",&neu[nr].geburtsjahr);
      } while(getchar()!= '\n');
}

void Suche(ADRESSE *search,char buchstabe,int nr)
{
   int i;
   for(i=0; i<=nr; i++)
      {
         if(search[i].nname[0] == buchstabe)
           {
             printf("\n\nGefunden unter Buchstabe :\"%c\"\n\n",
                                                      buchstabe);
             printf("Vorname.......:%s",search[i].vname);
             printf("Nachname......:%s",search[i].nname);
             printf("Postleitzahl..:%ld\n",search[i].PLZ);
             printf("Ort...........:%s",search[i].ort);
             printf("Geburtsjahr...:%d\n",search[i].geburtsjahr);
             printf("\n\tWeiter mit <ENTER>\n");
             getchar();
           }
      }
}

void Ausgabe(ADRESSE *all,int nr)
{
   int i;

   for(i=0; i<nr; i++)
      {
         printf("Vorname.........:%s",all[i].vname);
         printf("Nachname........:%s",all[i].nname);
         printf("Postleitzahl....:%ld\n",all[i].PLZ);
         printf("Ort.............:%s",all[i].ort);
         printf("Geburtsjahr.....:%d\n\n",all[i].geburtsjahr);

         if((!(i%2))&& i!=0)
            {
               //fflush(stdin);
               printf("\n\tWeiter mit <Enter>\n\n");
               getchar();
            }
      }
}

void Sort(ADRESSE *sort,int nr)
{
   ADRESSE *temp;
   int i,j;
   temp = (ADRESSE *)malloc(sizeof(ADRESSE *));
   if(NULL == temp)
      {
         printf("Konnte keinen Speicher reservieren...\n");
         return;
      }

   for(i=0; i<nr; i++)
      {
         for(j=i+1;j<nr;j++)
            {
               if(strcmp(sort[i].nname, sort[j].nname)>0)
                  {
                     *temp=sort[j];
                      sort[j]=sort[i];
                      sort[i]=*temp;
                  }
            }
      }
   printf(".....Sortiert!!\n");
}

int main()
{
   int auswahl;
   char c;

   do {
         printf("-1- Neue Adresse eingeben\n");
         printf("-2- Bestimmte Adresse ausgeben\n");
         printf("-3- Alle Adressen ausgeben\n");
         printf("-4- Adressen sortieren\n");
         printf("-5- Programm beenden\n");
         printf("\nIhre Auswahl : ");
         scanf("%d",&auswahl);
         /* fflush(stdin); */
         getchar();
         switch(auswahl)
            {
               case 1 : Eingabe(x++,adressen);
                        break;
               case 2 : printf("Anfangsbuchstabe Nachnamen :");
                        do {
                             scanf("%c",&c);
                           } while(getchar()!= '\n');
                        Suche(adressen,c,x);
                        break;
               case 3 : Ausgabe(adressen,x);
                        break;
               case 4 : Sort(adressen,x);
                        break;
               default: break;
            }
      }while(auswahl <5);
   return 0;
}

Dank der neuen Typendefinition

typedef struct adres ADRESSE;

kann auf die Struktur jetzt mit

ADRESSE neueadressen[100];

zugegriffen werden. Dies lässt sich bei längeren Programmen wesentlich einfacher lesen. Vor allem ist dies sinnvoll, wenn mehrere Strukturen vorhanden sind, die einander vom Aufbau sehr ähnlich sind. Die Typendefinition im Programm ließe sich auch noch anders definieren:

typedef struct adres {
                       char vname[20];
                       char nname[20];
                       long PLZ;
                       char ort[20];
                       int geburtsjahr;
                     }ADRESSE;

ADRESSE adressen[100];

Die Typendefinitionen kann ebenso auf andere Variablen angewendet werden. Recht häufig sind folgende Definitionen zu sehen:

typedef unsigned char BYTE;    /*1 Byte = 8 BIT*/
typedef unsigned int WORD;     /*1 WORD = 16 BIT*/
typedef unsigned long DWORD;   /*1 DOUBLE WORD = 32 BIT*/
typedef unsigned double QWORD; /*1 QUAD WORD = 64 BIT */
typedef unsigned int uint;
typedef unsigned char uchar;

Zum Beispiel ist die folgende Schreibweise

uint wert1,wert2; 

äquivalent zu

unsigned int wert1,wert2;

Das Schlüsselwort typedef wird ebenfalls dazu benutzt, so genannte primitive Datentypen zu erzeugen. Wozu soll das gut sein? Nehmen wir als Beispiel den primitiven Datentyp uclock_t (primitive Datentypen enden normalerweise immer mit _t). Dieser ist in der Headerdatei <time.h> definiert mit:

typedef long uclock_t; 

Auf einem anderen System sieht diese Definition vielleicht so aus:

typedef unsigned int uclock_t;

Die primitiven Datentypen machen ein Programm portabler. Dadurch müssen Sie sich nicht mit den Datentypen bei Portierung auf anderen Systemen auseinander setzen. Wenn Sie ein Programm beispielsweise auf einem 32-Bit-System programmiert haben und dies anschließend auf einem 16-Bit-System getestet wird, kann die Suche nach dem Fehler einer falschen Werteausgabe frustrierend sein.

Wie schon mit enum oder define wird mit typedef das Programm nicht etwa besser oder schneller, sondern es dient auch hier lediglich dem besseren Lesen, Schreiben und Portieren des Programms auf andere Systeme.

18.12. Attribute von Strukturen verändern (nicht ANSI-C)            zurück  Ein Kapitel tiefer  Ein Kapitel höher  zum Inhaltsverzeichnis

Der Speicherplatz für eine Struktur wird - wie schon bei den Arrays - lückenlos im Hauptspeicher abgelegt. Damit das System schneller auf diese Daten im Hauptspeicher zurückgreifen kann, werden diese in zwei oder in durch vier teilbare Adressen angeordnet. Dies ist abhängig vom Betriebssystem. Dabei wird einiges an Speicherplatz verschwendet. Zur Demonstration dient das folgende Programm:

#include <stdio.h>

struct speicher
    {
      char x;
      int z;
    };

int main()
{
   struct speicher test;
   printf("%ld Bytes\n",sizeof(test));
   return 0;
}

Auf 32-Bit-Systemen dürfte diese Struktur acht Byte benötigen. Und dies, obwohl es eigentlich fünf Byte sein sollten (char + int = 5 Byte).

Abbildung 18.10: Speicherabbild mit einer Struktur mit unbenannten Lücken
Abbildung 18.10: Speicherabbild mit einer Struktur mit unbenannten Lücken

Diese Abbildung stellt ein Vier-Byte-Alignment dar, wie es bei den meisten Systemen der Fall sein wird. Dabei entsteht eine Lücke von drei Byte (grau gefärbt), welche ungenutzt bleibt. Es wird hier auch vom Padding (Auffüllen, Polsterung) des Speichers gesprochen.

Hinweis
 

Dies ist übrigens auch der Grund, warum Sie Strukturen nicht direkt miteinander vergleichen können. Auch ein Low-Level-Vergleich, Byte für Byte, kann da nicht viel helfen, da dieser durch zufällig gesetzte Bits in den Löchern verfälscht sein könnte. In solch einem Fall müssen Sie sich mit einer eigenen Funktion, welche die einzelnen Strukturelemente miteinander vergleicht, selbst behelfen.

Viele Compiler besitzen daher einen speziellen Schalter, mit dem diese Lücke entfernt werden kann. Wobei ich gleich anmerken muss, dass dies nicht ANSI C-konform und Compiler-abhängig ist. Mit dem Schalter

__attribut__ 

können dem Compiler mehrere Informationen zu einer Funktion, zu Variablen oder Datentypen übergeben werden. Um damit eine lückenlose Speicherbelegung zu erreichen, könnten Sie das Attribut packed verwenden. Sollte dieser Schalter bei Ihrem Compiler nicht funktionieren, können Sie auch das Pragma pack verwenden:

#pragma pack(n)

Für n kann hier der Wert 1, 2, 4, 8 oder 16 angegeben werden. Je nachdem, welche Angabe Sie dabei machen, wird jedes Strukturelement nach dem ersten kleineren Elementtyp oder auf n Byte abgespeichert.

Beim Testen auf den verschiedenen Systemen und den unterschiedlichen Compilern gab es keine Probleme mit dem #pragma pack. Die Option __attribut__ hingegen wurde nicht von jedem Compiler erkannt. Wie Sie dabei vorgehen, müssen Sie letztendlich selbst herausfinden.

Hier das Beispiel dazu:
#include <stdio.h>

/* Lässt sich dieses Listing nicht übersetzen,
 * entfernen Sie __attribute__((packed)) und
 * verwenden stattdessen #pragma pack(1)
 */

/* #pragma pack(1) */
struct speicher{
                 char x;
                 int z;
               } __attribute__ ((packed));

int main()
{
   struct speicher test;
   printf("%ld Bytes\n",sizeof(test));
   return 0;
}

Damit benötigt die Struktur tatsächlich 5 Byte. Dies funktioniert bei manchen Compilern auch bei den enum-Aufzählungen:

#include <stdio.h>

/* #pragma pack(1); */
enum{TRUE,FALSE}__attribute__ ((packed));

int main()
{
   printf("%ld Bytes\n",sizeof(TRUE));
   return 0;
}

Das Beispiel funktionierte beim Testen allerdings nur mit dem gcc-Compiler. In diesem Fall wird durch packed ein 1-Byte-Alignment angelegt. Das ist gegenüber den vier Bytes ohne packed beachtlich. Diese Werte können natürlich von System zu System unterschiedlich sein.

Hinweis
 

Natürlich müssen Sie sich auch im Klaren sein, dass hier zwischen Performance und Speicherplatz entschieden wird. Schließlich greifen Sie ja in die geschwindigkeitsoptimierte Speicherung des Betriebssystems ein.

Bei den Vorteilen, die mit packed oder dem Pragma pack erzielt werden, sollten die Nachteile beachtet werden. Wenn diese Daten (struct speicher) auf einem System mit fünf Bytes pro Struktur in einer Datei gespeichert werden, kann es passieren, dass diese Daten auf einem anderen System falsch angezeigt werden, weil das System vielleicht dort die Option packed nicht kennt und einfach ignoriert. Außerdem könnten Low-Level-Funktionen fehlschlagen, da sich die Daten wegen des Alignments nicht dort befinden, wo die Funktionen diese vermutet.

18.13. Bitfelder            zurück  Ein Kapitel tiefer  Ein Kapitel höher  zum Inhaltsverzeichnis

Bitfelder sind Strukturelemente, die mit weniger als 1 Byte in eine Struktur gepackt werden können. Laut ANSI C müssen die einzelnen Elemente von Bitfeldern vom Datentyp int oder unsigned int sein.

Als Beispiel soll hier ein Roboter für eine Fließbandproduktion programmiert werden. Der Roboter muss ein bestimmtes Produkt von Position A nach Position B transportieren. Hier die einzelnen Arbeitsabläufe, die dazu erforderlich sind:

  1. Produkt zum Befördern vorhanden (Sensor 1 = aktiv). Wenn ja …
  2. Produkt erfassen und hochheben und auf das Fließband legen. Wenn Produkt auf Fließband liegt (Sensor 2 = aktiv), dann ...
  3. Fließband bewegen (Schalter 1) und warten, bis Produkt beim Sensor 3 ankommt. Dann wieder prüfen, ob ein Produkt auf Sensor 1 vorhanden ist. Falls an Sensor 1 ein Produkt vorhanden ist, alles wieder von vorn.

Dieser Vorgang soll auch überprüft werden, und bei einem Fehler wird ein Fehlercode ausgegeben, für den es eine entsprechende Stringtabelle gibt. Außerdem wird dazu eine Anzeige benötigt. Es wird davon ausgegangen, dass der Roboter bereits mit dem PC verbunden ist. Hier die Struktur des Roboters:

struct robo {
              unsigned char sensor1;
              unsigned char sensor2;
              unsigned char sensor3;
              unsigned char schalter;
              int Ausgabe;
            }Roboter1;

Abgesehen davon, dass dies in ANSI C nicht erlaubt ist, benötigt der Roboter mit dieser Struktur 48 Bits (6 Bytes). Wenn jetzt noch mehrere Roboter hinzukommen, ist das eine Speicherplatzverschwendung. Häufig ist bei solchen Automatisierungs-Robotern nicht unbegrenzt Speicherplatz vorhanden. Bei den Sensoren und Schaltern benötigen Sie in der Regel nur zwei Schaltstellungen: 1 für betätigt und 0 für unbetätigt. In C ist es auch möglich, einzelne Bits einer Struktur anzusprechen. Hier die entsprechende Struktur:

struct robo {
              unsigned sensor1:1;
              unsigned sensor2:1;
              unsigned sensor3:1;
              unsigned schalter:1;
              unsigned Ausgabe:4;
            }Roboter1;

Jetzt benötigt die Struktur nur noch acht Bits (ein Byte). Der Trick ist eigentlich ganz einfach: Es wurde hier das Schlüsselwort unsigned benutzt, das intern im Prozessor auf 0 gesetzt ist. Das Schlüsselwort unsigned benötigt ein Bit, und mit einem Bit kann man den Wert 1 oder 0 darstellen. Die Variablen-Ausgabe enthält vier Bits, womit eine Zahl bis 16 dargestellt werden kann (2*2*2*2).

Hier das Beispiel dazu:

#include <stdio.h>

enum{OFF, ON};

struct robo {
              unsigned Sensor1:1;
              unsigned Sensor2:1;
              unsigned Sensor3:1;
              unsigned Schalter:1;
              unsigned Ausgabe:4;
             }Roboter1;

char *msg[7] = {
                "Kein Signal an Sensor 1!\n",
                "Kein Signal an Sensor 2!\n",
                "Kein Signal an Sensor 3!\n",
                "Schalter 1 nicht betätigt!\n",
                "Notaus betätigt!\n",
                "Kein Strom!\n"
               };

int main()
{
   int anzahl;

   do{
       printf("Wie viele Produkte von Pos.A nach Pos.B : ");
       do{ scanf("%d",&anzahl); }while(getchar() != '\n');

       while((anzahl>0) && (anzahl--))
          {
      /* Sollte durch eine echte Schnittstelle ersetzt werden */
             Roboter1.Sensor1=ON;
             printf("Sensor 1 ist aktiv\n");
             if(Roboter1.Sensor1 == ON)
                printf("Produkt wird aufgenommen und "
                       "zum Fließband transportiert\n");
             else
                {
                 /*Fehler Sensor 1 nicht aktiv
                   Fehlermeldung ausgeben */
                   Roboter1.Ausgabe=0;
                   printf("%s\n", msg[Roboter1.Ausgabe]);
                }
      /* Sollte durch eine echte Schnittstelle ersetzt werden */
             Roboter1.Sensor2=ON;
             printf("Sensor 2 ist aktiv\n");
             if(Roboter1.Sensor2 == ON)
                {
                   printf("Produkt ist auf dem Fließband\n");
                   printf("Bitte >>ENTER<< drücken"
                          " für den Schalter\n");
                   getchar();
                   printf("Schalter ist eingeschaltet!\n");
      /* Sollte durch eine echte Schnittstelle ersetzt werden */
                   Roboter1.Schalter=ON;
                }
             else
                {
                   Roboter1.Ausgabe=1;
                   printf("%s\n",msg[Roboter1.Ausgabe]);
                   Roboter1.Ausgabe=3;
                   printf("%s\n", msg[Roboter1.Ausgabe]);
                }
      /* Sollte durch eine echte Schnittstelle ersetzt werden */
             Roboter1.Sensor3=ON;
             printf("Sensor 3 aktiv\n");
             if(Roboter1.Sensor3 == ON)
                {
                    printf("Produkt am Ziel angekommen!\n");
                    printf("Schalter für Fließband auf OFF\n");
                    printf("Roboter wieder betriebsbereit\n");
                    printf("Weiter mit >>ENTER<<\n");
                    getchar();
                    Roboter1.Schalter=OFF;
                }
             else
                {
                   Roboter1.Ausgabe=2;
                   printf("%s\n", msg[Roboter1.Ausgabe]);
                }
          }
     }while(anzahl > 0);
   Roboter1.Sensor1=OFF;
   Roboter1.Sensor2=OFF;
   Roboter1.Sensor3=OFF;
   Roboter1.Ausgabe=0;
   printf("%s\n",msg[Roboter1.Ausgabe]);
   return 0;
}

Abbildung 18.11: Der Roboter im Einsatz
Abbildung 18.11: Der Roboter im Einsatz

Dass die Struktur robo hier vier Bytes anstatt einem Byte groß ist, liegt am Alignment des Betriebssystems. Hier kann der Speicherplatz mit dem Keyword attribute oder dem Pragma pack auf ein Byte zusammenpackt werden. Vorausgesetzt, der Compiler unterstützt dies:

//#pragma pack(1)
struct robo {
                  unsigned Sensor1:1;
                  unsigned Sensor2:1;
                  unsigned Sensor3:1;
                  unsigned Schalter:1;
                  unsigned Ausgabe:4;
                 } __attribute__ ((packed)) Roboter1;

Diese Struktur nochmals bildlich:

Abbildung 18.12: Bitbelegung der einzelnen Bitfelder der Struktur robo
Abbildung 18.12: Bitbelegung der einzelnen Bitfelder der Struktur robo

Das Dumme an diesem Beispiel ist, dass es zu gar nichts taugt, da keine Verbindung mit einem Roboter besteht. Zeilen wie

Roboter1.Sensor1=ON;

Roboter1.Sensor2=ON;

müssen selbst eingegeben werden.

Daher folgt ein ausführbares Beispiel, welches zeigt, was eine Schnittstelle bzw. eine Adresse zum PC genau ist. Es wird der Druckerstatus am Port LPT1 überprüft. Das Listing ist nur unter MS-DOS ausführbar und nicht ANSI C-konform:

#include <stdio.h>
#include <stdlib.h>
#include <dos.h>
/*0x378 ist die Adresse der Schnittstelle von LPT1*/
#define LPT1_PORT 0x378

struct status {
           unsigned        :3;       /*Bit 0-2 nicht verwendet*/
           unsigned fehler :1;       /*0=Druckerfehler*/
           unsigned online :1;       /*1=Drucker online*/
           unsigned papier :1;        /*1=kein Papier*/
           unsigned empfang:1;        /*Emfangsbestätigung*/
           unsigned busy   :1;        /*Drucker bereit*/
         }LPT1_status;

/*Status am LPT1-Port auslesen*/
void druckerstatus(char *statuszeiger)
{
   *statuszeiger = inp( LPT1_PORT+1 ) & 0xF8;
}

int main()
{
   druckerstatus( (char *) &LPT1_status);

   if(LPT1_status.busy && LPT1_status.online)
      {
         printf("Drucker ist bereit!\n");
         exit (0);
      }
   else if(!LPT1_status.online)
      printf("Drucker nicht online!\n");
   else if(LPT1_status.papier)
      printf("Kein Papier vorhanden!\n");
   else
      printf("Drucker ist nicht bereit!\n");
   return 0;
}

Die Adresse 0x378 stellt die Adresse des Ports LPT1 dar. Der Statusregister, der hier überprüft wird, sieht intern folgendermaßen aus:

Abbildung 18.13: Bitbelegung eines Druckers am LPT1-Port unter MS-DOS
Abbildung 18.13: Bitbelegung eines Druckers am LPT1-Port unter MS-DOS

Diese Struktur ähnelt der Struktur, die oben bei den Robotern verwendet wurde. Die Bits 0-2 werden nicht verwendet. Die Bits 3-7 geben anschließend den Status des Druckers zurück. Je nachdem, welche Bits gesetzt sind und welche nicht. Die Funktion druckerstatus() liefert den Status zurück. Neu ist bei diesem Programm:

unsigned:3; 

Hiermit werden drei Bits ohne Namen definiert. Im Beispiel sind es die ersten drei Bits, die ungenutzt bleiben. Sie können im Programm nicht verwendet werden und werden als anonyme Bitfelder bezeichnet.

18.14. Differenzen zwischen C und C++ (Strukturen)            zurück  Ein Kapitel tiefer  Ein Kapitel höher  zum Inhaltsverzeichnis

Dies ist zwar ein C-Buch, aber viele werden danach auf C++ umsteigen wollen (oder kommen von C++ zu C). Daher kurz einige Differenzen zwischen diesen beiden Sprachen und den Strukturen: Folgende Differenzen gibt es bei Verwendung von struct und union

struct twin{
   int x;
   int y;
   };

struct twin moretwin;  /*Ok in C und C++*/
twin newtwin;          /*falsch in C, Ok in C++*/

In C muss das Schlüsselwort struct vorangestellt werden. Vermeiden können Sie dies durch eine Typendefinition mit typedef.

Zwei zufällig gleiche Namen einer Struktur und einer Variablen machen in C keine Probleme. Wohl aber in C++ :

int twin=99;

int main()
{
 struct twin{
              int x;
              int y;
             };
   struct twin value={ 200, 100};
   double twin=99.99;
   printf("%f\n",twin); /*Ok in C, falsch in C++*/

Um in C++ Folgendes zu realisieren:

struct nested {
               struct struktur1{int a, int b}name;
               struct struktur1 name2;
              };

struct nested value1;     /*Ok in C und C++*/
struct struktur1 value2;  /*Ok in C, falsch in C++*/

So macht man das zweite Beispiel in C++ mithilfe des Konstruktors:

nested::struktur1 value; /*In C++ ok, in C sowieso falsch*/ 

Folgende Unterschiede sind bei enum zwischen C und C++ zu beachten:

enum farben{rot,gruen,blau};
enum farben select;

farben select; /*Ok in C++, Fehler in C*/
select=rot;    /*Ok in C und C++*/
select=10;     /*Warnung in C, Fehler in C++*/
select++;      /*Ok in C, Fehler in C++*/

In C++ können Sie den enum-Variablen keine Werte übergeben, ohne diese zu casten. Folglich würde funktionieren:

select=(enum farben)=10;  /*casting, daher Ok in C++*/  

Sie können aber in C++ keine enum-Variable inkrementieren.

In C muss ein einzelnes Bitfeld immer vom Datentyp int oder unsigned int sein. In C++ dürfen Sie als Datentypen auch char, short, long und Aufzählungstypen verwenden.

18.15. Das offsetof - Makro            zurück  Ein Kapitel höher  zum Inhaltsverzeichnis

Um den Abstand der einzelnen Strukturelemente in Bytes zu ermitteln, kann das Makro offsetof(), welches in der Headerdatei <stddef.h> deklariert ist, verwendet werden. Hier die Syntax dazu:

#include <stddef.h>

size_t offsetof(struktur, name_strukturelement);

Das Makro liefert, wie schon erwähnt, den Abstand vom Anfang der Struktur bis zu dem als zweites Argument angegebenen Strukturelement.

Ist das Makro auf Ihrem System nicht vorhanden, so lässt es sich ohne großen Aufwand selbst implementieren:

#define offsetof(struct_type, member) \
          (size_t) &(((struct_type *)0)->member)

Ein einfaches Listing dazu:

#include <stdio.h>
#include <stddef.h>
#define MAX 15

struct data{
              char datei[MAX];
              unsigned int flag;
              char eigentuemer[MAX];
              char passwort[MAX];
           };

int main()
{
   struct data newfile = { "testfile", 0, "its_me", "believe" };
   struct data *str_ptr = &newfile;
   int offset;

   printf("Vom Anfang zur Strukturvariable flag    : %d Bytes\n",
                                    offsetof(struct data, flag));
   printf("Vom Anfang  zur Strukturvariable owner  : %d Bytes\n",
                             offsetof(struct data, eigentuemer));
   printf("Vom Anfang zur Strukturvariable passwort: %d Bytes\n",
                                offsetof(struct data, passwort));
 return 0;
}

Sie erhalten hier jeweils den Abstand in Byte vom Anfang der Struktur bis zum jeweiligen Strukturelement.

Weiter mit Kapitel 19: Datei Ein- Ausgabe            zum Inhaltsverzeichnis