Manuel de suivi dynamique Solaris

Chapitre 8 Définitions des types et des constantes

Ce chapitre décrit la méthode de déclaration des alias de type et des constantes nommées en langage D. Il présente également la gestion de l'espace de noms et des types en langage D pour les identificateurs et les types de système d'exploitation et de programme.

Typedef

Le mot-clé typedef permet de déclarer un identificateur en tant qu'alias d'un type existant. Comme toutes les déclarations de type en langage D, le mot-clé typedef est utilisé en dehors des clauses de sonde dans une déclaration se présentant sous la forme suivante :

typedef existing-type new-type ;

existing-type correspondant à une déclaration de type et new-type à un identificateur à utiliser comme alias de ce type. Par exemple, la déclaration :

typedef unsigned char uint8_t;

est utilisée en interne par le compilateur D pour créer l'alias de type uint8_t. Chaque fois que vous pouvez utiliser un type normal (comme le type d'une variable, d'une valeur de tableau associatif ou d'un membre du tuple), vous pouvez utiliser les alias de type. Vous pouvez également combiner typedef avec des déclarations plus élaborées comme la définition d'une nouvelle struct :

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

Dans cet exemple, struct foo est défini comme le même type que son alias, foo_t. Les en-têtes du système en langage C de Solaris utilisent fréquemment le suffixe _t pour indiquer un alias typedef.

Énumérations

La définition de noms symboliques de constantes dans un programme simplifie le processus de lisibilité et de mise à jour ultérieure du programme. Une méthode consiste à définir une énumération, qui associe un ensemble de nombres entiers à un ensemble d'identificateurs, appelés énumérateurs, que le compilateur reconnaît et remplace par la valeur entière correspondante. Une énumération est définie à l'aide d'une déclaration comme suit :

enum colors {
	RED,
	GREEN,
	BLUE
};

Le premier énumérateur de l'énumération, RED, reçoit la valeur zéro et chaque identificateur ultérieur reçoit la valeur entière suivante. Vous pouvez également attribuer une valeur entière spécifique à un énumérateur en lui donnant un suffixe comprenant un signe égal (=) et une constante entière, comme dans l'exemple suivant :

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

Le compilateur assigne à l'énumérateur BLUE la valeur 10, car aucune n'est spécifiée et que l'énumérateur précédent est défini sur 9. Une fois l'énumération définie, vous pouvez utiliser les énumérateurs dans tout programme en D où une constante entière peut être utilisée. Par ailleurs, l'énumération enum colors est également définie comme un type équivalant à int. Le compilateur D permettra également d'utiliser une variable de type enum chaque fois que vous pouvez utiliser int, ainsi que d'affecter une valeur entière à une variable de type enum. Vous pouvez également omettre le nom enum dans la déclaration si le nom du type n'est pas requis.

Les énumérateurs sont visibles dans toutes les clauses et déclarations ultérieures de votre programme, de sorte que vous pouvez définir le même identificateur d'énumérateur dans plusieurs énumérations. Vous pouvez, cependant, définir plusieurs énumérateurs possédant la même valeur dans la même ou dans différentes énumérations. Vous pouvez également affecter à une variable de type énumération des nombres entiers ne possédant aucun énumérateur correspondant.

La syntaxe d'énumération en D est identique à la syntaxe correspondante en ANSI-C. Le langage D permet également d'accéder aux énumérations définies dans le noyau du système et ses modules chargeables. Ces énumérateurs ne sont toutefois pas visibles globalement dans votre programme en D. Les énumérateurs du noyau ne sont visibles que lorsqu'ils sont utilisés comme argument vers les opérateurs de comparaison binaire dans le cadre d'une comparaison avec un objet du type d'énumération correspondant. Par exemple, la fonction uiomove(9F) possède un paramètre de type enum uio_rw défini comme suit :

enum uio_rw { UIO_READ, UIO_WRITE };

Les énumérateurs UIO_READ et UIO_WRITE ne sont logiquement pas visibles dans votre programme en D. Vous pouvez toutefois leur conférer une visibilité globale en en comparant un avec une valeur de type enum uio_rw, comme illustré dans l'exemple de clause suivant :

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

Cet exemple suit les appels à la fonction uiomove(9F) pour les demandes d'écriture en comparant args[2], une variable de type enum uio_rw, à l'énumérateur UIO_WRITE. Comme l'argument de gauche est un type d'énumération, le compilateur D recherche l'énumération lorsqu'il essaye de résoudre l'identificateur de droite. Cette fonction protège vos programmes en D des conflits de noms d'identificateurs avec les nombreuses énumérations définies dans le noyau du système d'exploitation.

Inlines

Vous pouvez également définir les constantes nommées en D à l'aide de directives inline. Ces dernières fournissent une méthode plus générale de création d'identificateurs qui sont remplacés par des valeurs ou des expressions prédéfinies pendant la compilation. Les directives intégrées constituent une forme plus puissante de remplacement vertical que la directive #define fournie par le préprocesseur C, le type de remplacement étant réel et le remplacement étant exécuté au moyen de l'arborescence de syntaxe compilée au lieu de l'être tout simplement au moyen d'un ensemble de jetons lexicaux. Vous spécifiez une directive intégrée à l'aide d'une déclaration comme suit :

inline type name = expression ;

type correspondant à la déclaration de type d'un type existant, name à un identificateur en D valide non précédemment défini en tant que variable intégrée ou globale et expression à une expression en D valide. Une fois la directive intégrée traitée, le compilateur D substitue la forme compilée de expression de chaque instance ultérieure de name dans la source du programme. Par exemple, le programme en D suivant doit suivre la chaîne "hello" et la valeur entière 123 :

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

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

Vous pouvez utiliser un nom intégré chaque fois que vous pouvez utiliser une variable globale du type correspondant. Si vous pouvez évaluer l'expression intégrée en une constante de chaîne ou entière au moment de la compilation, vous pouvez également utiliser le nom en ligne dans des contextes nécessitant des expressions constantes, comme les dimensions de tableau scalaire.

L'expression intégrée est validée pour les erreurs de syntaxe dans le cadre de l'évaluation de la directive. Le type qui résulte de l'expression doit être compatible avec le type défini dans la variable intégrée, conformément aux règles appliquées à l'opérateur d'affectation en D (=). Une expression intégrée peut ne pas référencer l'identificateur intégré lui-même : les définitions récursives ne sont pas autorisées.

Les packages logiciels de DTrace installent plusieurs fichiers sources en D dans le répertoire système /usr/lib/dtrace qui contient les directives intégrées que vous pouvez utiliser dans vos programmes. Par exemple, la bibliothèque signal.d comprend des directives qui se présentent comme suit :

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

Ces définitions intégrées permettent d'accéder à l'ensemble actuel des noms de signaux Solaris décrits dans signal(3HEAD). La bibliothèque errno.d contient, de façon similaire, des directives intégrées pour les constantes errno en C décrites dans Intro(2).

Par défaut, le compilateur D intègre automatiquement tous les fichiers de bibliothèque en D fournis de sorte que vous pouvez utiliser ces définitions dans n'importe quel programme en D.

Espaces de noms de types

Cette section présente les espaces de noms en D et les problèmes d'espace de noms liés aux types. Dans les langages classiques comme l'ANSI-C, la visibilité du type est déterminée par le type d'imbrication (à l'intérieur d'une fonction ou d'une autre déclaration). Les types déclarés dans l'étendue extérieure d'un programme en C sont associés à un espace de noms global unique et sont visibles à travers tout le système. Les types définis dans les fichiers d'en-tête en C figurent généralement dans cette étendue extérieure. Contrairement à ces langages, le langage D permet d'accéder aux types à partir de plusieurs étendues extérieures.

Le langage D facilite l'observation dynamique à travers plusieurs couches d'une pile logicielle, y compris le noyau du système d'exploitation, un ensemble associé de modules de noyau chargeables et les processus utilisateur en cours d'exécution sur le système. Un seul programme en D peut instancier des sondes pour rassembler des données issues de plusieurs modules de noyau ou d'autres entités logicielles qui sont compilées dans des objets binaires indépendants. Par conséquent, plusieurs types de données possédant le même nom mais pas nécessairement la même définition, peuvent figurer parmi les types disponibles pour DTrace et le compilateur D. Pour gérer cette situation, le compilateur D associe chaque type à un espace de noms identifié par l'objet de programme qu'il contient. Les types d'un objet de programme particulier sont accessibles en spécifiant le nom de l'objet et en utilisant l'opérateur d'étendue ` dans n'importe quel nom de type.

Par exemple, si un module de noyau nommé foo contient la déclaration de type en langage C suivante :

typedef struct bar {
	int x;
} bar_t;

les types struct bar et bar_t sont accessibles à partir du langage D à l'aide des noms de type :

struct foo`bar				foo`bar_t

Vous pouvez utiliser l'opérateur ` dans n'importe quel contexte dès lors qu'un nom de type est approprié, y compris lors de la spécification du type pour les déclarations de variable en D ou les expressions de diffusion dans des clauses de sonde en D.

Le compilateur D intègre également deux espaces de noms de types spéciaux qui portent respectivement les noms C et D. L'espace de noms de types C comprend à l'origine les types ANSI-C standard intrinsèques comme int. Par ailleurs, les définitions de type acquises à l'aide du préprocesseur C cpp(1) au moyen de l'option dtrace -C sont traitées par et ajoutées à l'étendue de C. Vous pouvez ainsi inclure des fichiers d'en-tête en C contenant des déclarations de types déjà visibles dans un autre espace de noms de types sans engendrer d'erreur de compilation.

L'espace de noms de types D comprend à l'origine les types D intrinsèques comme int et string, ainsi que des alias de types D intégrés comme uint32_t. Toute nouvelle déclaration de types qui apparaît dans la source du programme en D est automatiquement ajoutée à l'espace de noms de types D. Si vous créez dans votre programme en D un type complexe, comme une struct, constitué de types de membre provenant d'autres espaces de noms, les types de membre sont copiés dans l'espace de noms D par la déclaration.

Lorsque le compilateur D rencontre une déclaration de type ne spécifiant aucun espace de noms de manière explicite à l'aide de l'opérateur `, le compilateur recherche l'ensemble des espaces de noms de types actifs pour trouver une correspondance à l'aide du nom de type spécifié. L'espace de noms C est toujours recherché en premier, suivi de l'espace de noms D. Si le nom du type n'est trouvé ni dans l'espace de noms C ni dans l'espace de noms D, la recherche des espaces de noms de types des modules de noyau actifs est réalisée dans l'ordre ascendant par ID de module de noyau. Cet ordre garantit que la recherche des objets binaires qui constitue le noyau de base est exécutée avant la recherche des modules de noyau chargeables. Par contre, il ne garantit pas l'ordre des propriétés au niveau des modules chargeables. Vous devez utiliser l'opérateur d'étendue lorsque vous accédez aux types définis dans les modules de noyau chargeables pour éviter les conflits de nom de type avec les autres modules de noyau.

Le compilateur D utilise les informations de débogage en ANSI-C compressées fournies avec les modules de noyau de base de Solaris afin d'accéder automatiquement au code source du système d'exploitation sans devoir accéder aux fichiers en C inclus correspondants. Ces informations de débogage symboliques peuvent ne pas être disponibles pour tous les modules de noyau sur votre système. Le compilateur D signale une erreur si vous tentez d'accéder à un type dans un espace de noms d'un module qui empile les informations de débogage en C compressées dont l'utilisation est prévue avec DTrace.