Handbuch zur dynamischen Ablaufverfolgung in Solaris

Kapitel 5 Zeiger und Vektoren

Zeiger sind Speicheradressen von Datenobjekten im Betriebssystemkernel oder im Adressbereich eines Benutzerprozesses. D bietet die Fähigkeit, Zeiger zu erstellen und zu manipulieren und sie in Variablen und assoziativen Vektoren zu speichern. Dieses Kapitel befasst sich mit der D-Syntax für Zeiger, Operatoren, die beim Erstellen von Zeigern oder beim Zugriff auf sie angewendet werden können, sowie mit der Beziehung zwischen Zeigern und skalaren Vektoren fester Größe. Darüber hinaus werden Probleme im Zusammenhang mit dem Einsatz von Zeigern in unterschiedlichen Adressbereichen besprochen.


Hinweis –

Wenn Sie viel Erfahrung mit dem Programmieren in C oder C++ besitzen, können Sie einen Großteil dieses Kapitels durchaus überfliegen, da die D-Zeigersyntax mit der entsprechenden ANSI-C-Syntax übereinstimmt. Lesen Sie die Abschnitte Zeiger auf DTrace-Objekte und Zeiger und Adressräume, die DTrace-spezifische Merkmale und Aspekte beschreiben.


Zeiger und Adressen

Im Betriebssystem Solaris wird jedem Benutzerprozess mithilfe von virtuellem Speicher eine eigene virtuelle Sicht der Speicherressourcen auf dem System bereitgestellt. Eine virtuelle Sicht der Speicherressourcen wird als Adressraum bezeichnet. Durch ihn wird ein Bereich von Adresswerten (entweder [0 ... 0xffffffff] bei 32-Bit-Adressräumen oder [0 ... 0xffffffffffffffff] bei 64-Bit-Adressräumen) bestimmten Übersetzungen zugewiesen, auf deren Grundlage das Betriebssystem und die Hardware die einzelnen virtuellen Adressen in einen entsprechenden Speicherbereich im physischen Speicher konvertieren. In D stellen Zeiger Datenobjekte dar, die einen Ganzzahlwert einer virtuellen Adresse speichern und ihm einen D-Typ zuordnen, der das Format der an der entsprechenden Speicherposition abgelegten Daten beschreibt.

Um eine D-Variable als Zeigertyp zu deklarieren, geben Sie zunächst den Typ der referenzierten Daten an und fügen dann ein Sternchen ( *) an den Typnamen an, womit Sie zu erkennen geben, dass ein Zeigertyp deklariert werden soll. So wird beispielsweise die Deklaration:

int *p;

eine globale D-Variable namens p deklariert werden, die einen Zeiger auf eine Ganzzahl darstellt. Diese Deklaration bedeutet, dass p selbst eine 32- oder 64-Bit-Ganzzahl ist, deren Wert die Adresse einer anderen Ganzzahl irgendwo im Speicher darstellt. Da die kompilierte Form des D-Codes zum Zeitpunkt der Prüfpunktauslösung innerhalb des Betriebssystemkernels selbst ausgeführt wird, beziehen sich Zeiger in D typischerweise auf den Kernel-Adressraum. Mit dem Befehl isainfo(1) -b lässt sich ermitteln, wie viele Bits der aktive Betriebssystemkernel auf Zeiger verwendet.

Wenn Sie einen Zeiger auf ein Datenobjekt im Kernel erstellen möchten, können Sie dessen Adresse mit dem Operator & berechnen. Nehmen wir beispielsweise das im Betriebssystemkern-Quellcode deklarierte Tunable int kmem_flags. Sie könnten die Adresse dieses int-Tunables erfassen, indem Sie das Ergebnis der Anwendung des Operators & auf den Namen dieses Objekts in D verfolgen:

trace(&`kmem_flags);

Mit dem Operator * können Sie das durch den Zeiger angesprochene Objekt referenzieren. Der Operator hat die umgekehrte Wirkung des Operators &. So haben beispielsweise die beiden folgenden D-Codefragmente dieselbe Bedeutung:

p = &`kmem_flags;				trace(`kmem_flags);
trace(*p);

Das linke Fragment erstellt eine globale D-Variable in Form des Zeigers p. Da das Objekt kmem_flags den Typ int aufweist, nimmt das Ergebnis von &`kmem_flags den Typ int * an (d. h. ein Zeiger auf int). Das linke Fragment erfasst den Wert von *p, der den Zeiger bis zum Datenobjekt kmem_flags zurückverfolgt. Dieses Fragment ist deshalb gleichbedeutend mit dem rechten, das den Wert des Datenobjekts einfach über dessen Namen verfolgt.

Zeigersicherheit

Als C- oder C++-Programmierer hat Sie der vorangehende Abschnitt möglicherweise ein wenig erschreckt, da Sie wissen, dass die falsche Verwendung von Zeigern in einem Programm zum Programmabsturz führen kann. DTrace ist eine robuste, sichere Umgebung für die Ausführung von D-Programmen, in der diese Fehler keine Programmabstürze verursachen können. Es ist zwar möglich, dass Sie ein fehlerhaftes D-Programm schreiben, doch ungültige D-Zeigerzugriffe bewirken weder, dass DTrace noch der Betriebssystemkernel fehlschlagen oder abstürzen. Stattdessen werden ungültige Zeigerzugriffe von der DTrace-Software erkannt, die Instrumentation deaktiviert und das Problem gemeldet, sodass Sie es beheben können.

Wenn Sie schon einmal ein Java-Programm geschrieben haben, wissen Sie wahrscheinlich, dass Java aus genau diesem Sicherheitsgrund keine Zeiger unterstützt. In D sind Zeiger jedoch erforderlich, da sie einen wesentlichen Bestandteil der Betriebssystemimplementierung in C darstellen. Deshalb bietet DTrace die gleiche Art von Sicherheitsmechanismen, die in der Programmiersprache Java die Beschädigung fehlerhafter Programme durch sich selbst oder andere Programme verhindern. Der Fehlermeldemechanismus in DTrace funktioniert ähnlich wie die Laufzeitumgebung in Java, die Programmierfehler erkennt und eine Ausnahme meldet.

Lassen Sie uns zur Betrachtung der Fehlerbehandlung und -meldung in DTrace absichtlich ein fehlerhaftes D-Programm mit Zeigern schreiben. Geben Sie das folgende D-Programm in einen Texteditor ein und speichern Sie es unter dem Namen badptr.d:


Beispiel 5–1 badptr.d: Veranschaulichung der Fehlerbehandlung in DTrace

BEGIN
{
	x = (int *)NULL;
	y = *x;
	trace(y);
}

Das Programm badptr.d erzeugt einen D-Zeiger namens x, der auf int zeigt. Das Programm weist dem Zeiger den speziellen, für ungültige Zeiger stehenden Wert NULL zu, der ein integrierter Aliasname für die Adresse 0 ist. Die Adresse 0 ist stets als ungültig vereinbart, sodass NULL in C- und D-Programmen dafür als Repräsentationswert eingesetzt werden kann. Mit einem Cast-Ausdruck wird NULL explizit in einen Zeiger auf eine Ganzzahl umgewandelt. Anschließend dereferenziert das Programm den Zeiger über den Ausdruck *x und weist das Ergebnis einer weiteren Variable y zu, bevor es schließlich die Ablaufverfolgung von y versucht. Wenn das D-Programm ausgeführt wird, erkennt DTrace bei der Ausführung der Anweisung y = *x einen ungültigen Zeigerzugriff und meldet den Fehler:


# dtrace -s badptr.d
dtrace: script '/dev/stdin' matched 1 probe
CPU     ID                    FUNCTION:NAME
dtrace: error on enabled probe ID 1 (ID 1: dtrace:::BEGIN): invalid address
(0x0) in action #2 at DIF offset 4
dtrace: 1 error on CPU 0
^C
#

Ein weiteres Problem, das durch Programme mit ungültigen Zeigern verursacht werden kann, ist ein Ausrichtungsfehler. Gemäß der Architekturkonvention werden essenzielle Datenobjekte wie Ganzzahlen im Speicher auf Grundlage ihrer Größe angeordnet. So werden beispielsweise 2-Byte-Ganzzahlen an Adressen ausgerichtet, die Vielfache von 2 sind, 4-Byte-Ganzzahlen an Vielfachen von 4 und so weiter. Wenn Sie einen Zeiger auf eine 4-Byte-Ganzzahl dereferenzieren und die Zeigeradresse ein ungültiger Wert und kein Vielfaches von 4 ist, schlägt der Zugriff mit einem Ausrichtungsfehler fehl. Ausrichtungsfehler in D weisen nahezu immer darauf hin, dass ein Zeiger aufgrund eines Fehlers im D-Programm einen ungültigen oder fehlerhaften Wert besitzt. Ändern Sie im Quellcode von badptr.d die Adresse NULL in (int *)2 ab, um einen Ausrichtungsfehler zur Veranschaulichung zu erzeugen. Da int eine 4-Byte-Ganzzahl und 2 kein Vielfaches von 4 ist, verursacht der Ausdruck *x einen DTrace-Ausrichtungsfehler.

Näheres zum DTrace-Fehlermechanismus finden Sie unter Der Prüfpunkt ERROR.

Vektordeklarationen und Speicherung

D bietet neben den in Kapitel 3 beschriebenen dynamischen assoziativen Vektoren Unterstützung für Skalare Vektoren. Unter skalaren Vektoren versteht man eine Gruppe festgelegter Länge, die aufeinander folgender Speicheradressen enthält. In jeder dieser Adressen wird ein Wert vom gleichen Datentyp gespeichert. Auf skalare Vektoren wird durch Referenzierung auf die einzelnen Positionen mit einer Ganzzahl, ausgehend von Null, zugegriffen. Skalare Vektoren stimmen konzeptuell und syntaktisch direkt mit Vektoren in C und C++ überein. In D werden sie nicht so häufig eingesetzt wie assoziative Vektoren und ihre erweiterten Gegenstücke, die Aggregate, sind aber manchmal für den Zugriff auf vorhandene, in C deklarierte Betriebssystem-Vektordatenstrukturen nötig. Aggregate werden in Kapitel 9Aggregate beschrieben.

Ein skalarer Vektor von 5 Ganzzahlen ließe sich in D wie folgt mit dem Typ int deklarieren, wobei der Deklaration die Anzahl der Elemente in eckigen Klammern voranzustellen ist:

int a[5];

Das folgende Diagramm veranschaulicht die Vektorspeicherung:

Abbildung 5–1 Darstellung skalarer Vektoren

Schaubild eines Vektors mit fünf Objekten.

Der D-Ausdruck a[0] dient zur Referenzierung des ersten Vektorelements, a[1] referenziert das zweite und so weiter. Aus syntaktischer Sicht sind sich skalare und assoziative Vektoren sehr ähnlich. Sie können einen assoziativen Vektor von fünf Ganzzahlen, der mit einem Ganzzahlschlüssel referenziert wird, wie folgt deklarieren:

int a[int];

und diesen Vektor auch über den Ausdruck a[0] referenzieren. Aus Sicht der Speicherung und Implementation hingegen unterscheiden sich die beiden Vektoren sehr stark voneinander. Der statische Vektor a besteht aus fünf aufeinander folgenden, ab Null durchnummerierten Speicherpositionen und der Index verweist auf einen Versatz (Offset) in dem für den Vektor reservierten Speicherbereich. Dagegen haben assoziative Vektoren keine vordefinierte Größe und speichern Elemente nicht an aufeinander folgenden Speicherpositionen. Außerdem haben die Schlüssel assoziativer Vektoren keine Beziehung zur Speicherposition des entsprechenden Werts. Sie können auf die assoziativen Vektorelemente a[0] und a[-5] zugreifen, und DTrace reserviert nur zwei Speicherwörter, die aufeinander folgend sein können oder nicht. Bei den Schlüsseln assoziativer Vektoren handelt es sich um abstrakte Namen für die entsprechenden Werte, die keine Beziehung zu den Speicherpositionen der Werte haben.

Wenn Sie einen Vektor mit anfänglicher Zuweisung erstellen und einen einzelnen Ganzzahlausdruck als Vektorindex einsetzen (zum Beispiel a[0] = 2), erzeugt der D-Compiler stets einen neuen assoziativen Vektor, selbst wenn a in diesem Ausdruck auch als Zuweisung zu einem skalaren Vektor interpretiert werden könnte. In einer solchen Situation müssen skalare Vektoren vordeklariert werden, damit der D-Compiler die Definition der Vektorgröße erkennen und schließen kann, dass es sich um einen skalaren Vektor handelt.

Beziehung zwischen Zeigern und Vektoren

Ebenso wie in ANSI-C besteht auch in D eine besondere Beziehung zwischen Zeigern und Vektoren. Ein Vektor wird durch eine Variable dargestellt, der die Adresse ihrer ersten Speicherposition zugewiesen ist. Auch ein Zeiger ist die Adresse einer Speicherposition mit einem definierten Typ. In D ist also die Verwendung der Vektorindex-Notation [ ] sowohl für Zeiger- als auch Vektorvariablen zulässig. So haben beispielsweise die beiden folgenden D-Codefragmente dieselbe Bedeutung:

p = &a[0];				trace(a[2]);
trace(p[2]);

Im Fragment auf der linken Seite wird durch Anwendung des Operators & auf den Ausdruck a[0] der Zeiger p die Adresse des ersten Vektorelements in a zugewiesen. Der Ausdruck p[2] verfolgt den Wert des dritten Vektorelements (Index 2). Da p jetzt die auch a zugewiesene Adresse enthält, ergibt dieser Ausdruck denselben Wert wie a[2] im rechten Codefragment. Eine Konsequenz dieser Gleichbedeutung ist, dass C und D den Zugriff auf jeden Index eines beliebigen Zeigers oder Vektors zulassen. Weder der Compiler noch die DTrace-Laufzeitumgebung führen eine Überprüfung der Vektorgrenzen durch. Bei einem Zugriff auf eine Speicherposition außerhalb des vordefinierten Werts eines Vektors erhalten Sie entweder ein unerwartetes Ergebnis oder DTrace meldet wie im vorigen Beispiel einen Fehler aufgrund einer ungültigen Adresse. Wie immer, können Sie auch hierdurch weder DTrace noch das Betriebssystem beeinträchtigen, müssen aber den Fehler im D-Programm beseitigen.

Der Unterschied zwischen Zeigern und Vektoren besteht darin, dass eine Zeigervariable auf separaten Speicher verweist, der die ganzzahlige Adresse eines anderen Speichers enthält. Eine Vektorvariable benennt den Speicherbereich des Vektors selbst und nicht etwa die Position einer Ganzzahl, die ihrerseits die Position des Vektors enthält. Dieser Unterschied geht aus dem folgenden Schaubild hervor:

Abbildung 5–2 Speicherung von Zeigern und Vektoren

Schaubild eines Zeigers auf einen Vektor mit fünf Objekten.

Dieser Unterschied kommt in der D-Syntax zur Geltung, wenn Sie versuchen, Zeiger und skalare Vektoren zuzuweisen. Wenn x und y Zeigervariablen sind, ist der Ausdruck x = y zulässig. Er kopiert einfach die Zeigeradresse aus y an die mit x angegebene Speicherposition. Wenn x und y skalare Vektorvariablen sind, ist der Ausdruck x = y nicht zulässig. Eine Zuweisung von Vektoren als Ganze ist in D nicht möglich. Es können aber in jedem Kontext, in dem Zeiger zulässig sind, Vektorvariablen oder Symbolnamen verwendet werden. Handelt es sich bei p um einen Zeiger und bei a um einen Vektor, dann ist die Anweisung p = a erlaubt. Sie ist gleichbedeutend mit der Anweisung p = &a[0].

Zeigerarithmetik

Da es sich bei Zeigern nur um Ganzzahlen handelt, die als Adressen für andere Objekte im Speicher verwendet werden, bietet D verschiedene Leistungsmerkmale für die Zeigerarithmetik. Diese unterscheidet sich jedoch von der Ganzzahlarithmetik. Bei der Zeigerarithmetik wird die betreffende Adresse implizit durch Multiplikation oder Division der Operanden mit der bzw. durch die Größe des Typs angepasst, auf den der Zeiger verweist. Das folgende D-Fragment demonstriert diese Eigenschaft:

int *x;

BEGIN
{
	trace(x);
	trace(x + 1);
	trace(x + 2);
}

Dieses Fragment erzeugt einen ganzzahligen Zeiger x und verfolgt dann seinen Wert, seinen um 1 erhöhten und seinen um 2 erhöhten Wert. Wenn Sie dieses Programm erstellen und ausführen, meldet DTrace die ganzzahligen Werte 0, 4 und 8.

Da x ein Zeiger auf ein Objekt des Typs int ist (Größe 4 Byte), wird der zugrunde liegende Zeigerwert durch Inkrementierung von x um 4 erhöht. Diese Eigenschaft erweist sich als nützlich, wenn Sie Zeiger zum Verweis auf aufeinander folgende Speicherpositionen wie zum Beispiel Vektoren einsetzen möchten. Wenn beispielsweise x der Adresse eines Vektors a, wie dem in Abbildung 5–2 dargestellten, zugewiesen würde, so wäre der Ausdruck x + 1 gleichbedeutend mit dem Ausdruck &a[1]. Ebenso würde der Ausdruck *(x + 1) den Wert a[1] bezeichnen. Der D-Compiler implementiert Zeigerarithmetik immer dann, wenn ein Zeigerwert mit einem der Operatoren +=, + oder ++ inkrementiert wird.

Sie wird auch angewendet, wenn eine Ganzzahl von einem Zeiger auf der linken Seite subtrahiert, ein Zeiger von einem anderen Zeiger subtrahiert oder der Operator -- auf einen Zeiger angewendet wird. So würde beispielsweise das folgende D-Programm das Ergebnis 2 verfolgen:

int *x, *y;
int a[5];

BEGIN
{
	x = &a[0];
	y = &a[2];
	trace(y - x);
}

Unspezifische Zeiger

Unter Umständen ist es praktisch, in einem D-Programm eine unspezifische (generische) Zeigeradresse darzustellen oder zu manipulieren, ohne den Datentyp anzugeben, auf den der Zeiger verweist. Unspezifische Zeiger können mit dem Typ void * angegeben werden, wobei das Schlüsselwort void für das Fehlen einer spezifischen Typangabe steht. Zu diesem Zweck kann ebenfalls der integrierte Typ-Aliasname uintptr_t verwendet werden, der einen vorzeichenlosen Integer-Typ einer für einen Zeiger im aktuellen Datenmodell geeigneten Größe darstellt. Auf Objekte des Typs void * darf Zeigerarithmetik nicht angewendet werden und diese Zeiger können nicht dereferenziert werden, ohne sie zuerst explizit in einen anderen Typ umzuwandeln. Wenn Sie Ganzzahlenarithmetik an einem Zeigerwert durchführen müssen, können Sie den Zeiger explizit in den Typ uintptr_t umwandeln.

Zeiger auf void können in jedem Kontext verwendet werden, in dem ein Zeiger auf einen anderen Datentyp erforderlich ist, wie beispielsweise bei einem Tupel-Ausdruck für einen assoziativen Vektor oder der rechten Seite einer Zuweisungsanweisung. Analog kann in einem Kontext, in dem ein Zeiger auf void benötigt wird, ein Zeiger eines beliebigen Datentyps verwendet werden. Zum Einsetzen eines Zeigers auf einen nicht-void-Typ anstelle eines anderen nicht-void-Zeigertyps wird eine explizite Typumwandlung benötigt. Um Zeiger in Integer-Typen wie uintptr_t umzuwandeln oder diese Ganzzahlen in den entsprechenden Zeigertyp zurückzukonvertieren, müssen Sie stets eine explizite Typumwandlung vornehmen.

Mehrdimensionale Vektoren

Mehrdimensionale skalare Vektoren kommen in D nur selten zum Einsatz, stehen aber zum Zweck der Kompatibilität mit ANSI-C und zum Beobachten sowie Ansprechen von Betriebssystem-Datenstrukturen zur Verfügung, die anhand dieser Fähigkeit in C erstellt wurden. Ein mehrdimensionaler Vektor wird als eine zusammenhängende Folge skalarer Vektorgrößen in eckigen Klammern [ ] im Anschluss an den Basistyp deklariert. Um beispielsweise einen zweidimensionalen Vektor festgelegter Größe mit Ganzzahlen zu 12 Zeilen auf 34 Spalten zu deklarieren, würden Sie Folgendes schreiben:

int a[12][34];

Die gleiche Schreibweise gilt für den Zugriff auf mehrdimensionale skalare Vektoren. Um beispielsweise auf den in Zeile 0, Spalte 1 gespeicherten Wert zuzugreifen, würden Sie folgenden D-Ausdruck schreiben:

a[0][1]

Speicherpositionen für die Werte mehrdimensionaler skalarer Vektoren werden per Multiplikation der Zeilennummer durch die Gesamtzahl der deklarierten Spalten und anschließende Addition der Spaltennummer berechnet.

Achten Sie darauf, die Syntax für mehrdimensionale Vektoren nicht mit der D-Syntax für Zugriffe auf assoziative Vektoren zu verwechseln (d. h., a[0][1] ist nicht identisch mit a[0, 1]). Wenn Sie ein unvereinbares Tupel für einen assoziativen Vektor verwenden oder einen assoziativen Vektorzugriff auf einen skalaren Vektor versuchen, gibt der D-Compiler eine entsprechende Fehlermeldung aus und kompiliert das Programm nicht.

Zeiger auf DTrace-Objekte

Der D-Compiler verbietet Ihnen den Einsatz des Operators & zum Erhalten von Zeigern auf DTrace-Objekte wie assoziative Vektoren, integrierte Funktionen und Variablen. Sie werden daran gehindert, die Adressen dieser Variablen abzurufen, damit die DTrace-Laufzeitumgebung sie zwischen den verschiedenen Prüfpunktauslösungen nach Bedarf verschieben und so den für Ihre Programme benötigten Speicher effizienter verwalten kann. Wenn Sie zusammengesetzte Strukturen erstellen, besteht die Möglichkeit, Ausdrücke zu formulieren, die die Kerneladresse des DTrace-Objektspeichers abrufen. Verzichten Sie in Ihren D-Programmen auf solche Ausdrücke. Sollten Sie dennoch einen solchen Ausdruck benötigen, achten Sie darauf, die Adresse nicht über mehrere Prüfpunktauslösungen hinweg zwischenzuspeichern.

In ANSI-C können Zeiger auch für indirekte Funktionsaufrufe oder Zuweisungen wie das Absetzen eines Ausdrucks mit dem unären *-Dereferenzierungsoperator auf der linken Seite eines Zuweisungsoperators verwendet werden. In D sind diese Arten der Ausdrücke mit Zeigern nicht erlaubt. Sie dürfen D-Variablen Werte nur direkt zuweisen, indem Sie entweder ihre Namen verwenden oder den Vektorindex-Operator [] auf einen skalaren oder assoziativen D-Vektor anwenden. In der DTrace-Umgebung definierte Funktionen dürfen, wie in Kapitel 10Aktionen und Subroutinen erläutert, nur über ihren Namen aufgerufen werden. Indirekte Funktionsaufrufe mithilfe von Zeigern sind in D nicht zulässig.

Zeiger und Adressräume

Ein Zeiger ist eine Adresse, die innerhalb eines virtuellen Adressraums eine Übersetzung für einen physischen Speicherbereich liefert. DTrace führt D-Programme innerhalb des Adressraums für den Betriebssystemkernel selbst aus. Das Solaris-System verwaltet zahlreiche Adressräume: davon einen für den Betriebssystemkernel und einen für Benutzerprozesse. Da jeder Adressraum scheinbar auf den gesamten Speicher des Systems zugreifen kann, lässt sich derselbe virtuelle Adresszeigerwert in den verschiedenen Adressräumen wieder verwenden und in unterschiedliche physische Speicherbereiche übersetzen. Deshalb müssen Sie beim Schreiben von D-Programmen die Adressräume der Zeiger beachten, die Sie verwenden möchten.

Wenn Sie beispielsweise mit dem Provider syscall den Eintritt in einen Systemaufruf instrumentieren, der einen Zeiger auf eine Ganzzahl oder einen Vektor von Ganzzahlen als Argument annimmt (z. B. pipe(2)), ist eine Dereferenzierung des Zeigers oder Vektors mit einem der Operatoren * oder [] nicht zulässig, da sich die betreffende Adresse in dem Adressraum des Benutzerprozesses befindet, der den Systemaufruf durchgeführt hat. In D würde die Anwendung des Operators * oder [] auf diese Adresse einen Zugriff auf den Kernel-Adressraum bedeuten, der einen Fehler aufgrund ungültiger Adresse oder die Rückgabe unerwarteter Daten an das D-Programm zur Folge hätte. Dies hängt davon ab, ob die Adresse zufällig mit einer gültigen Kernel-Adresse übereinstimmt oder nicht.

Um über einen DTrace-Prüfpunkt auf den Benutzerprozess-Speicher zuzugreifen, müssen Sie eine der in Kapitel 10Aktionen und Subroutinen beschriebenen Funktionen copyin(), copyinstr() oder copyinto() auf den Benutzer-Adressraumzeiger anwenden. Achten Sie beim Schreiben von D-Programmen darauf, Variablen für Benutzeradressen angemessen zu benennen und zu kommentieren. Sie können dadurch einiges an Durcheinander verhindern. Alternativ können Sie Benutzeradressen als uintptr_t speichern und somit der Gefahr aus dem Weg gehen, dass Sie D-Code schreiben, der diese dereferenziert. Verfahren zum Verwenden von DTrace für Benutzerprozesse sind in Kapitel 33Ablaufverfolgung von Benutzerprozessen beschrieben.