Handbuch zur dynamischen Ablaufverfolgung in Solaris

Kapitel 8 Typ- und Konstantendefinitionen

In diesem Kapitel erfahren Sie, wie Typ-Aliasnamen und benannte Konstanten in D deklariert werden. Darüber hinaus wird in diesem Kapitel die Typ- und Namensraumverwaltung in D für Programm- und Betriebssystemtypen sowie -Bezeichner besprochen.

Typedef

Das Schlüsselwort typedef dient zum Deklarieren eines Bezeichners als Aliasname für einen bereits vorhandenen Typ. Wie bei allen D-Typ-Deklarationen wird das Schlüsselwort typedef außerhalb der Prüfpunktklauseln gesetzt. Die Deklarationen haben die folgende Form:

typedef vorhandener_Typ neuer_Typ ;

dabei ist vorhandener_Typ eine beliebige Typ-Deklaration und neuer_Typ ist ein Bezeichner, der als Aliasname für diesen Typ verwendet werden soll. So wird beispielsweise die Deklaration:

typedef unsigned char uint8_t;

intern vom D-Compiler für die Erzeugung des Typ-Aliasnamens uint8_t verwendet. Typ-Aliasnamen können überall dort eingesetzt werden, wo ein normaler Typ, wie zum Beispiel der Typ eines Variablen- oder assoziativen Vektorwerts oder einer Tupel-Komponente, verwendet werden kann. Außerdem lässt sich typedef mit komplexeren Deklarationen wie der Definition einer neuen Struktur (struct) kombinieren:

typedef struct foo {
	int x;
	int y;
} foo_t;

In diesem Beispiel wird struct foo als derselbe Typ seines Aliasnamens, foo_t, definiert. In den C-System-Headern von Solaris wird ein typedef-Aliasname häufig durch den Zusatz _t gekennzeichnet.

Aufzählungen

Indem Sie symbolische Namen für Konstanten definieren, verbessern Sie nicht nur die Lesbarkeit der Programme, sondern erleichtern auch die zukünftige Programmpflege. Ein mögliches Verfahren besteht in der Definition einer Aufzählung, die eine Menge von Ganzzahlen einem Satz Bezeichnern, den so genannten Aufzählungskonstanten, zuordnen. Diese erkennt der Compiler und ersetzt sie durch die entsprechenden ganzzahligen Werte. Aufzählungen werden mit Deklarationen wie der folgenden definiert:

enum colors {
	RED,
	GREEN,
	BLUE
};

Die erste Aufzählungskonstante in der Aufzählung, ROT, erhält den Wert Null, und jeder nachfolgende Bezeichner den nächsthöheren ganzzahligen Wert. Sie können für jede beliebige Aufzählungskonstante auch einen expliziten ganzzahligen Wert angeben, indem Sie ihr ein Gleichheitszeichen und eine ganzzahlige Konstante nachstellen:

enum colors {
	RED = 7,
	GREEN = 9,
	BLUE
};

Der Aufzählungskonstante BLAU wird vom Compiler der Wert 10 zugewiesen, da für sie kein Wert angegeben ist und die vorhergehende Aufzählungskonstante den Wert 9 besitzt. Nachdem eine Aufzählung definiert wurde, können deren Auszählungskonstanten überall dort in einem D-Programm verwendet werden, wo ganzzahlige Konstanten zum Einsatz kommen. Die Aufzählung enum colors ist außerdem als Typ int definiert. Der D-Compiler lässt Variablen des Typs enum überall dort zu, wo ein int verwendet werden kann. Einer Variable des Typs enum darf jeder ganzzahlige Wert zugewiesen werden. Wenn der Typname nicht erforderlich ist, können Sie in der Deklaration auf den Namen enum verzichten.

Aufzählungskonstanten sind in allen nachfolgenden Klauseln und Deklarationen eines Programms sichtbar. Deshalb kann ein Name für eine Aufzählungskonstante nicht in mehreren Aufzählungen definiert werden. Sie können allerdings sowohl in derselben als auch in verschiedenen Aufzählungen mehrere Aufzählungskonstanten mit demselben Wert definieren. Es ist auch möglich, einer Variable des Aufzählungstyps Ganzzahlen ohne entsprechende Aufzählungskonstante zuzuweisen.

Die Syntax für D-Aufzählungen ist identisch mit der entsprechenden Syntax in ANSI-C. D ermöglicht auch den Zugriff auf Aufzählungen, die im Betriebssystemkern und dessen ladbaren Modulen definiert sind. Diese Aufzählungskonstanten sind im D-Programm jedoch nicht global sichtbar. Kernel-Aufzählungskonstanten sind nur dann sichtbar, wenn sie zum Vergleich mit einem Objekt des entsprechenden Aufzählungstyps als Argument für einen binären Vergleichsoperator eingesetzt werden. So besitzt beispielsweise die Funktion uiomove(9F) einen Parameter des Typs enum uio_rw, der wie folgt definiert ist:

enum uio_rw { UIO_READ, UIO_WRITE };

Die Aufzählungskonstanten UIO_READ und UIO_WRITE sind in einem D-Programm normalerweise nicht sichtbar, können aber durch Vergleich mit einem Wert des Typs enum uio_rw auf globale Sichtbarkeit erweitert werden. Dies zeigt die nächste Beispielklausel:

fbt::uiomove:entry
/args[2] == UIO_WRITE/
{
	...
}

In diesem Beispiel werden Aufrufe der Funktion uiomove(9F) für Schreibanforderungen verfolgt. Dabei wird args[2], eine Variable des Typs enum uio_rw, mit der Aufzählungskonstante UIO_WRITE verglichen. Da das Argument auf der linken Seite den Aufzählungstyp besitzt, durchsucht der D-Compiler die Aufzählung in dem Versuch, den Bezeichner auf der rechten Seite aufzulösen. Diese Einrichtung schützt Ihre D-Programme vor Namenskonflikten mit der umfangreichen Sammlung der im Betriebssystemkernel definierten Aufzählungen.

Inline

Benannte Konstanten in D können auch anhand von inline-Direktiven definiert werden. Dabei handelt es sich um ein allgemeineres Hilfsmittel für die Erzeugung von Namen, die bei der Kompilierung durch vordefinierte Werte oder Ausdrücke ersetzt werden. Inline-Direktiven stellen im Vergleich zu der #define-Direktive des C-Preprozessors eine leistungsfähigere lexikalische Ersetzung zur Verfügung, da der Ersetzung ein tatsächlicher Typ zugewiesen ist und sie nicht nur anhand einer Menge lexikalischer Symbole (Token), sondern unter Verwendung des kompilierten Syntaxbaums durchgeführt wird. Inline-Direktiven werden in folgender Form deklariert:

inline Typ Name = Ausdruck ;

wobei Typ eine Typdeklaration eines vorhandenen Typs ist, Name ein beliebiger gültiger D-Bezeichner, der noch nicht als Inline- oder globale Variable definiert wurde, und Ausdruck ein beliebiger gültiger D-Ausdruck. Sobald die Inline-Direktive verarbeitet ist, setzt der D-Compiler die kompilierte Form von Ausdruck für jede nachfolgende Instanz von Name im Programmquellcode ein. Das nächste D-Programm verfolgt beispielsweise die Zeichenkette "hello" und den ganzzahligen Wert 123:

inline string hello = "hello";
inline int number = 100 + 23;

BEGIN
{
	trace(hello);
	trace(number);
}

Ein Inline-Name ist überall dort zulässig, wo eine globale Variable des entsprechenden Typs verwendet werden kann. Wenn die Inline-Direktive zur Kompilierungszeit eine ganzzahlige oder eine Zeichenkettenkonstante ergibt, kann der Inline-Name auch in einem Kontext verwendet werden, in dem konstante Ausdrücke wie zum Beispiel skalare Vektordimensionen erforderlich sind.

Zur Auswertung der Anweisung gehört die Prüfung der Inline-Direktive auf Syntaxfehler. Der Ergebnistyp des Ausdrucks muss mit dem in der Inline-Direktive definierten Typ vereinbar sein. Hierbei gelten dieselben Regeln wie für den D-Zuweisungsoperator (=). Ein Inline-Ausdruck darf nicht auf den Inline-Namen selbst verweisen: Rekursive Definitionen sind nicht erlaubt.

Mit der DTrace-Software werden im Systemverzeichnis /usr/lib/dtrace eine Reihe von D-Quelldateien installiert, die Inline-Direktiven zur Verwendung in Ihren D-Programmen enthalten. So enthält beispielsweise die Bibliothek signal.d Direktiven der Form:

inline int SIGHUP = 1;
inline int SIGINT = 2;
inline int SIGQUIT = 3;
...

Diese Inline-Definitionen ermöglichen den Zugriff auf den in signal(3HEAD) beschriebenen aktuellen Signalnamensatz in Solaris. Analog enthält die Bibliothek errno.d Inline-Direktiven für die errno-Konstanten in C, die in Intro(2) beschrieben sind.

Standardmäßig nimmt der D-Compiler alle bereitgestellten D-Bibliotheksdateien automatisch auf, sodass die Definitionen in jedem D-Programm verwendet werden können.

Namensräume für Typen

Dieser Abschnitt befasst sich mit Namensräumen in D und Problemen in Bezug auf Namensräume für Typen. In herkömmlichen Sprachen wie ANSI-C ergibt sich die Typ-Sichtbarkeit daraus, ob ein Typ in eine Funktion oder eine andere Deklaration eingebettet ist. Im externen Gültigkeitsbereich eines C-Programms deklarierte Typen haben einen einzelnen, globalen Namensraum und sind im gesamten Programm sichtbar. Die in C-Definitionsdateien vereinbarten Typen werden in der Regel in diesen externen Gültigkeitsbereich aufgenommen. Im Gegensatz zu diesen Sprachen ermöglicht D den Zugriff auf Typen von mehreren externen Gültigkeitsbereichen.

Die Sprache D begünstigt die dynamische Beobachtungsfähigkeit über mehrere Ebenen eines Software-Stacks hinweg, einschließlich des Betriebssystemkernels, einer Gruppe ladbarer Kernelmodule und der auf dem System laufenden Benutzerprozesse. Ein einzelnes D-Programm kann Prüfpunkte zum Abrufen von Daten aus mehreren Kernelmodulen oder anderen Softwareeinheiten instanziieren, die in unabhängige binäre Objekte kompiliert werden. Aus diesem Grund ist es denkbar, dass in dem Universum der DTrace und dem D-Compiler zur Verfügung stehenden Typen mehrere Datentypen mit demselben Namen, möglicherweise mit unterschiedlichen Definitionen, vorliegen. Um dieser Situation Herr zu werden, verbindet der D-Compiler mit jedem Typ einen Namensraum, der in dem ihn enthaltenden Programmobjekt vereinbart ist. Typen aus einem bestimmten Programmobjekt können durch Angabe des Objektnamens und des Backquote-Operators (`) in einem beliebigen Typnamen angesprochen werden.

Wenn beispielsweise ein Kernelmodul namens foo die folgende C-Typdeklaration enthält:

typedef struct bar {
	int x;
} bar_t;

dann kann auf die Typen struct bar und bar_t aus D anhand der folgenden Typnamen zugegriffen werden:

struct foo`bar				foo`bar_t

Der Backquote-Operator kann in jedem Kontext verwendet werden, in dem ein Typname geeignet ist, einschließlich bei der Angabe des Typs für D-Variablendeklarationen oder ausdrückliche Typumwandlungen in D-Prüfpunktklauseln.

Der D-Compiler stellt auch zwei spezielle Namensräume für integrierte Typen, C und D, zur Verfügung. Der Namensraum für C-Typen wird anfänglich mit den ANSI-C-Standardtypen wie zum Beispiel int gefüllt. Darüber hinaus werden mit dem C-Preprozessor cpp(1) und der dtrace-Option -C erfasste Typdefinitionen von dem C-Gültigkeitsbereich verarbeitet und in ihn aufgenommen. Folglich können Sie C-Definitionsdateien aufnehmen, die bereits in einem anderen Typ-Namensraum sichtbare Typdeklarationen enthalten, ohne dadurch Kompilierungsfehler zu verursachen.

Der Namensraum für D-Typen wird anfänglich mit den D-internen Typen wie int und string und Typ-Aliasnamen wie uint32_t gefüllt. Neue Typdeklarationen, die im D-Programmcode vorkommen, werden automatisch dem Namensraum für D-Typen hinzugefügt. Wenn Sie in einem D-Programm einen komplexen Typ wie struct erzeugen, dessen Komponententypen aus anderen Namensräumen stammen, werden die Komponententypen durch die Deklaration in den D-Namensraum kopiert.

Sollte der D-Compiler auf eine Typdeklaration treffen, die keine explizite Namensraumangabe mit dem Backquote-Operator enthält, durchsucht der Compiler die Menge der aktiven Typ-Namensräume nach einer Entsprechung für den angegebenen Typnamen. Der C-Namensraum wird stets zuerst durchsucht. Danach erfolgt die Suche im D-Namensraum. Kann der Typname weder im C- noch im D-Namensraum gefunden werden, werden die Typ-Namensräume der aktiven Kernelmodule in aufsteigender Reihenfolge nach Kernelmodul-ID durchsucht. Diese Reihenfolge gewährleistet, dass die den inneren Kern bildenden binären Objekte vor etwaigen ladbaren Kernelmodulen durchsucht werden, garantiert aber keinerlei Reihenfolgeneigenschaften über die ladbaren Module hinaus. Benutzen Sie den Backquote-Operator für den Zugriff auf Typen, die in ladbaren Kernelmodulen definiert sind, um Typnamenskonflikte mit anderen Kernelmodulen zu vermeiden.

Für den automatischen Zugriff auf die Typen des Betriebssystemquellcodes stützt sich der D-Compiler auf komprimierte ANSI-C-Debugging-Informationen aus den zentralen Solaris-Kernelmodulen, sodass nicht auf die entsprechenden C-Include-Dateien zugegriffen werden muss. Diese symbolischen Debugging-Informationen stehen möglicherweise nicht für alle Kernelmodule auf dem System zur Verfügung. Bei dem Versuch, auf einen Typ im Namensraum eines Moduls ohne die für DTrace vorgesehenen komprimierten C-Debugging-Informationen zuzugreifen, gibt der D-Compiler einen Fehler aus.