| #include <pronix.de> |
|
|
18.10. Aufzählungstyp enum
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
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.
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:
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
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
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
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.
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.
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.