Manuel de suivi dynamique Solaris

Chapitre 7 Structs et Unions

Vous pouvez regrouper des ensembles de variables liées au sein d'objets de données composites appelés structs et unions. Vous pouvez définir ces objets en langage D en créant pour eux de nouvelles définitions de type, puis ajouter les nouveaux types dédiés aux variables en langage D, y compris les valeurs de tableau associatif. Ce chapitre étudie la syntaxe et la sémantique de création et de manipulation de ces types composites, ainsi que les opérateurs en langage D qui interagissent avec eux. La syntaxe des structs et unions est illustrée au moyen de plusieurs exemples de programmes démontrant l'utilisation des fournisseurs fbt et pid de DTrace.

Structs

Le mot-clé struct en langage D (raccourci de structure) est utilisé pour introduire un nouveau type composé d'un groupe d'autres types. Vous pouvez utiliser le nouveau type de struct comme type de tableaux et de variables en langage D dans le but de définir des groupes de variables connexes sous un seul nom. Les structs en langage D sont identiques aux constructions correspondantes en C et C++. En cas de programmation en langage java, imaginez une struct en langage D comme une classe qui ne contient que des membres de données et aucune méthode.

Imaginez que vous souhaitez créer, en langage D, un programme plus sophistiqué de suivi des appels système qui enregistre plusieurs données concernant chaque appel système read(2) et write(2) exécuté par votre shell (temps écoulé, nombre d'appels et plus grand comptage d'octets sous forme d'argument). Vous pouvez écrire une clause en langage D pour enregistrer ces propriétés dans trois tableaux associatifs distincts, comme illustré dans l'exemple suivant :

syscall::read:entry, syscall::write:entry
/pid == 12345/
{
	ts[probefunc] = timestamp;
	calls[probefunc]++;
	maxbytes[probefunc] = arg2 > maxbytes[probefunc] ?
	    arg2 : maxbytes[probefunc];
}

Cette clause est toutefois inefficace car DTrace doit créer trois tableaux associatifs distincts et enregistrer les copies distinctes des valeurs de tuple identiques correspondant à probefunc pour chacune d'entre elles. Une struct vous permet d'économiser de l'espace et de simplifier la lecture et la préservation de votre programme. Pour ce faire, commencez par déclarer un nouveau type de struct au sommet du fichier source du programme :

struct callinfo {
	uint64_t ts;      /* timestamp of last syscall entry */
	uint64_t elapsed; /* total elapsed time in nanoseconds */
	uint64_t calls;   /* number of calls made */
	size_t maxbytes;  /* maximum byte count argument */
};

Le mot clé struct est suivi d'un identifiant en option qui sert de renvoi à notre nouveau type, appelé struct callinfo. Les membres de la struct sont ensuite placés entre crochets { }, puis la déclaration toute entière se termine par un point-virgule (; ). Chaque membre de la struct est défini à l'aide de la même syntaxe que celle d'une déclaration de variable en langage D, le type du membre étant indiqué en premier, puis suivi d'un identifiant permettant de nommer le membre et d'un autre point-virgule (;).

La déclaration de la struct en elle-même se contente de définir le nouveau type. Elle ne crée aucune variable ni n'alloue aucun stockage dans DTrace. La déclaration terminée, vous pouvez utiliser le type struct callinfo tout au long du reste de votre programme en D et chaque variable de type struct callinfo enregistre une copie des quatre variables décrites par notre modèle de structure. Les membres sont classés en mémoire conformément à l'ordre de la liste des membres, un espace de remplissage étant introduit entre chaque membre afin d'aligner les objets de données.

Vous pouvez utiliser les noms d'identifiant des membres pour accéder à la valeur de chaque membre à l'aide de l'opérateur “.” en écrivant une expression se présentant comme suit :

variable-name.member-name

L'exemple suivant représente un programme amélioré qui utilise le nouveau type de structure. Ouvrez votre éditeur et entrez le programme en langage D suivant, puis enregistrez-le dans le fichier rwinfo.d:


Exemple 7–1 rwinfo.d : collecte des statistiques read(2) et write(2)

struct callinfo {
	uint64_t ts;      /* timestamp of last syscall entry */
	uint64_t elapsed; /* total elapsed time in nanoseconds */
	uint64_t calls;   /* number of calls made */
	size_t maxbytes;  /* maximum byte count argument */
};

struct callinfo i[string];	/* declare i as an associative array */

syscall::read:entry, syscall::write:entry
/pid == $1/
{
	i[probefunc].ts = timestamp;
	i[probefunc].calls++;
	i[probefunc].maxbytes = arg2 > i[probefunc].maxbytes ?
		arg2 : i[probefunc].maxbytes;
}

syscall::read:return, syscall::write:return
/i[probefunc].ts != 0 && pid == $1/
{
	i[probefunc].elapsed += timestamp - i[probefunc].ts;
}

END
{
	printf("        calls  max bytes  elapsed nsecs\n");
	printf("------  -----  ---------  -------------\n");
	printf("  read  %5d  %9d  %d\n",
	    i["read"].calls, i["read"].maxbytes, i["read"].elapsed);
	printf(" write  %5d  %9d  %d\n",
	    i["write"].calls, i["write"].maxbytes, i["write"].elapsed);
}

Une fois le programme saisi, exécutez la commande dtrace -q -s rwinfo.d en spécifiant l'un de vos processus de shell. Entrez ensuite quelques commandes dans votre shell et, ceci terminé, tapez Control-C dans le terminal dtrace pour déclencher la sonde END et imprimer les résultats :


# dtrace -q -s rwinfo.d `pgrep -n ksh`
^C
        calls  max bytes  elapsed nsecs
------  -----  ---------  -------------
  read     36       1024  3588283144
 write     35         59  14945541
#

Pointeurs vers structs

Faire référence à des structs à l'aide de pointeurs est courant en C et en D. Vous pouvez utiliser l'opérateur -> pour accéder aux membres d'une struct par l'intermédiaire d'un pointeur. Si une struct s intègre le membre m et qu'un pointeur sur cette struct s'appelle sp (sp étant une variable du type struct s *), vous pouvez utiliser l'opérateur * pour déréférencer le pointeur sp, afin d'accéder au membre :

struct s *sp;

(*sp).m

Vous pouvez également utiliser l'opérateur -> comme raccourci pour cette notation. Les deux exemples en D suivants ont la même signification si sp est utilisé comme pointeur vers une struct :

(*sp).m				sp->m

DTrace intègre plusieurs variables de type pointeurs vers structs, y compris curpsinfo et curlwpsinfo. Ces pointeurs renvoient respectivement aux structs psinfo et lwpsinfo et leur contenu offre un aperçu des informations relatives à l'état du processus courant et du processus léger (LWP) associés au thread ayant déclenché la sonde actuelle. Un processus Solaris léger est la représentation du noyau d'un thread utilisateur sur lequel les interfaces de thread Solaris et POSIX sont créées. Pour plus de facilité, DTrace exporte ces informations sous la même forme que les fichiers du système de fichiers /proc /proc/pid/psinfo et /proc/pid/lwps/lwpid/lwpsinfo. Les structures /proc sont utilisées par les outils d'observation et de débogage comme ps(1), pgrep(1) et truss(1). Elles sont définies dans le fichier d'en-tête du système <sys/procfs.h>, ainsi que sur la page de manuel proc(4). Voici quelques exemples d'expressions utilisant curpsinfo, leurs types et leurs significations :

curpsinfo->pr_pid

pid_t

ID de processus courant 

curpsinfo->pr_fname

char []

nom de fichier exécutable 

curpsinfo->pr_psargs

char []

arguments de ligne de commande initiaux 

Vous devez revoir la définition complète de la structure ultérieurement en examinant le fichier d'en-tête <sys/procfs.h> et les descriptions correspondantes dans proc(4). L'exemple suivant utilise le membre pr_psargs pour identifier le processus qui nous intéresse en adaptant les arguments de la ligne de commande.

Les structs sont fréquemment utilisées pour créer des structures de données complexes dans les programmes en langage C. Par conséquent, l'aptitude à décrire et référencer les structs à partir du langage D offre une puissante capacité d'observation du fonctionnement interne du noyau du système d'exploitation Solaris et de ses interfaces système. En plus de l'utilisation de la struct curpsinfo mentionnée ci-dessus, l'exemple suivant examine certaines structs du noyau en observant la relation entre le pilote ksyms(7D) et les demandes read(2). Le pilote utilise les deux structs usuelles uio(9S) et iovec(9S) pour répondre aux demandes de lecture à partir du fichier de périphérique caractère /dev/ksyms.

La struct uio, accessible via le nom struct uio ou l'alias de type uio_t, est décrite sur la page de manuel uio(9S) et permet de décrire une demande d'E/S impliquant la copie de données entre le noyau et un processus utilisateur. La struct uio contient en retour un tableau d'une ou plusieurs structures iovec(9S) qui décrivent chacune un élément de l'E/S demandée dans le cas où plusieurs éléments sont demandés à l'aide des appels système readv(2) ou writev(2). L'une des routines DDI (interface de pilote de périphérique) du noyau qui fonctionne sur struct uio est représentée par la fonction uiomove(9F), cette dernière constituant l'un des ensembles de fonctions que les pilotes du noyau utilisent pour répondre aux demandes read(2) du processus utilisateur et recopier les données sur les processus utilisateur.

Le pilote ksyms gère le fichier de périphérique caractère /dev/ksyms, ce dernier semblant être un fichier ELF contenant des informations sur la table de symboles du noyau. En fait, il s'agit d'une illusion créée par le pilote à l'aide de l'ensemble de modules actuellement chargés dans le noyau. Le pilote utilise la routine uiomove(9F) pour répondre aux demandes read(2). L'exemple suivant montre que les arguments et les appels de read(2) à partir de /dev/ksyms correspondent aux appels du pilote vers uiomove(9F) pour recopier les résultats dans l'espace d'adressage utilisateur à l'emplacement spécifié pour read(2).

Il est possible d'utiliser l'utilitaire strings(1) combiné à l'option -a pour forcer un ensemble de lecture à partir de /dev/ksyms. Essayez d'exécuter strings -a /dev/ksyms dans votre shell et voyez la sortie obtenue. Dans un éditeur, tapez la première clause de l'exemple de script et enregistrez-la dans le fichier ksyms.d:

syscall::read:entry
/curpsinfo->pr_psargs == "strings -a /dev/ksyms"/
{
	printf("read %u bytes to user address %x\n", arg2, arg1);
}

Cette première clause utilise l'expression curpsinfo->pr_psargs pour accéder et s'adapter aux arguments de la ligne de commande de notre commande strings(1) de sorte que le script sélectionne les demandes read(2) appropriées avant de suivre les arguments. Notez qu'en utilisant l'opérateur == avec un argument gauche correspondant à un tableau de char et un argument droit correspondant à une chaîne, le compilateur D déduit que l'argument gauche doit être promu en chaîne et qu'une comparaison de chaîne doit être exécutée. Tapez et exécutez la commande dtrace -q -s ksyms.d dans un shell, puis tapez la commande strings -a /dev/ksyms dans un autre shell. Tandis que strings(1) s'exécute, vous obtenez une sortie à partir de DTrace similaire à l'exemple suivant :


# dtrace -q -s ksyms.d
read 8192 bytes to user address 80639fc
read 8192 bytes to user address 80639fc
read 8192 bytes to user address 80639fc
read 8192 bytes to user address 80639fc
...
^C
#

Il est possible d'étendre cet exemple à l'aide d'une technique de programmation en D classique pour suivre un thread plus profondément dans le noyau à partir de cette demande read(2) initiale. Une fois entré dans le noyau avec syscall::read:entry, le script suivant définit une variable d'indication de thread local signalant le thread qui nous intéresse et efface cet indicateur sur syscall::read:return. Une fois l'indicateur défini, vous pouvez l'utiliser comme prédicat sur d'autres sondes pour instrumenter les fonctions du noyau comme uiomove(9F). Le fournisseur de suivi des limites des fonctions de DTrace ( fbt) édite les sondes d'entrée et revient aux fonctions définies dans le noyau, y compris celles de la DDI. Tapez le code source suivant. Ce dernier utilise le fournisseur fbt pour instrumenter uiomove(9F). Enregistrez-le ensuite dans le fichier ksyms.d.


Exemple 7–2 ksyms.d : suivi de la relation deread(2) et uiomove(9F )

/*
 * When our strings(1) invocation starts a read(2), set a watched flag on
 * the current thread.  When the read(2) finishes, clear the watched flag.
 */
syscall::read:entry
/curpsinfo->pr_psargs == "strings -a /dev/ksyms"/
{
	printf("read %u bytes to user address %x\n", arg2, arg1);
	self->watched = 1;
}

syscall::read:return
/self->watched/
{
	self->watched = 0;
}

/*
 * Instrument uiomove(9F).  The prototype for this function is as follows:
 * int uiomove(caddr_t addr, size_t nbytes, enum uio_rw rwflag, uio_t *uio);
 */
fbt::uiomove:entry
/self->watched/
{
	this->iov = args[3]->uio_iov;

	printf("uiomove %u bytes to %p in pid %d\n",
	    this->iov->iov_len, this->iov->iov_base, pid);
}

La clause finale de l'exemple utilise la variable de thread local self->watched pour identifier à quel moment le thread de noyau qui nous intéresse lance la routine uiomove(9F) de la DDI. À ce stade, le script utilise le tableau args intégré pour permettre au quatrième argument (args[3]) d'accéder à uiomove(), cette fonction consistant en un pointeur vers la struct uio représentant la demande. Le compilateur D associe automatiquement chaque membre du tableau args au type qui correspond au prototype de fonction en langage C de la routine de noyau instrumentée. Le membre uio_iov contient un pointeur vers la struct iovec pour la demande. Une copie de ce pointeur est enregistrée pour être utilisée dans notre clause dans la variable de clause locale this->iov. Dans l'instruction finale, le script déréférence this->iov pour accéder aux membres de iovec iov_len et iov_base qui représentent respectivement la longueur en octets et l'adresse de base de la destination de uiomove(9F). Ces valeurs doivent correspondre aux paramètres d'entrée de l'appel système read(2) émis sur le pilote. Accédez à votre shell et exécutez dtrace -q -s ksyms.d. Entrez ensuite la commande strings -a /dev/ksyms dans un autre shell. Vous devez obtenir une sortie similaire à l'exemple suivant :


# dtrace -q -s ksyms.d
read 8192 bytes at user address 80639fc
uiomove 8192 bytes to 80639fc in pid 101038
read 8192 bytes at user address 80639fc
uiomove 8192 bytes to 80639fc in pid 101038
read 8192 bytes at user address 80639fc
uiomove 8192 bytes to 80639fc in pid 101038
read 8192 bytes at user address 80639fc
uiomove 8192 bytes to 80639fc in pid 101038
...
^C
#

Les adresses et les ID de processus sont différents dans votre sortie. Vous devez toutefois observer que les arguments en entrée vers read(2) correspondent aux paramètres transmis à uiomove(9F) par le pilote ksyms.

Unions

Les unions représentent une autre catégorie de type de composite que les langages ANSI-C et D prennent en charge. Elles sont également étroitement liées aux structs. Une union est un type de composite dans lequel un ensemble de membres de différents types sont définis et dans lequel les objets membres occupent tous la même zone de stockage. Une union est, par conséquent, un objet de type variante dans lequel seul un membre est valide à un moment donné en fonction de la manière dont l'union a été affectée. En règle générale, certaines autres variables ou éléments d'état servent à indiquer le membre de l'union actuellement valide. La taille d'une union correspond à la taille du membre le plus grand et l'alignement de la mémoire utilisé pour l'union correspond à l'alignement maximum requis par les membres de l'union.

La structure kstat de Solaris définit une struct contenant une union utilisée dans l'exemple suivant pour illustrer et observer les unions en C et D. La structure kstat sert à exporter un ensemble de compteurs nommés représentant les statistiques du noyau comme l'utilisation de la mémoire et le débit d'E/S. La structure est utilisée pour implémenter des utilitaires comme mpstat(1M) et iostat(1M). Cette structure utilise struct kstat_named pour représenter un compteur nommé et sa valeur. Elle est définie comme suit :

struct kstat_named {
	char name[KSTAT_STRLEN]; /* name of counter */
	uchar_t data_type;	/* data type */
	union {
		char c[16];
		int32_t i32;
		uint32_t ui32;
		long l;
		ulong_t ul;
		...
	} value;	/* value of counter */
};	

La déclaration examinée est réduite à titre illustratif. La définition complète de la structure figure dans le fichier d'en-tête <sys/kstat.h> et est décrite dans kstat_named(9S). La déclaration ci-dessus est valide en langage ANSI-C et D et définit une struct dont l'un des membres est une valeur d'union dont les membres appartiennent à différents types en fonction du type de compteur. Étant donné que l'union elle-même est déclarée à l'intérieur d'un autre type, struct kstat_named, notez que le nom formel du type d'union est omis. Ce style de déclaration est connu comme étant une union anonyme. Le membre nommé value est un type d'union décrit par la déclaration précédente mais ce type d'union lui-même ne possède pas de nom car il n'a pas vocation à être utilisé autrement. Le membre data_type de la struct est affecté à une valeur qui indique le membre de l'union valide pour chaque objet du type struct kstat_named. Un ensemble de jetons de préprocesseur en C est défini pour les valeurs de data_type. Par exemple, le jeton KSTAT_DATA_CHAR est égal à 0 et indique que le membre value.c se trouve à l'endroit où est actuellement enregistrée la valeur.

L'Exemple 7–3 montre comment accéder à l'union kstat_named.value en assurant le suivi d'un processus utilisateur. Il est possible d'échantillonner les compteurs kstat à partir d'un processus utilisateur à l'aide de la fonction kstat_data_lookup(3KSTAT) qui renvoie un pointeur à une struct kstat_named. L'utilitaire mpstat(1M) appelle cette fonction à plusieurs reprises pendant son exécution afin d'échantillonner les dernières valeurs du compteur. Accédez à votre shell, essayez d'exécuter mpstat 1 et observez la sortie. Appuyez sur Control-C dans votre shell pour abandonner mpstat après quelques secondes. Pour observer l'échantillonnage du compteur, nous aimerions activer une sonde qui se déclenche à chaque fois que la commande mpstat appelle la fonction kstat_data_lookup(3KSTAT) dans libkstat. Pour ce faire, nous allons utiliser un nouveau fournisseur de DTrace : pid. Le fournisseur pid vous permet de créer des sondes de manière dynamique au sein de processus utilisateur au niveau des emplacements de symboles C comme les points d'entrée de fonction. Vous pouvez demander au fournisseur pid de créer une sonde au niveau de l'entrée d'une fonction utilisateur et de retourner les sites en décrivant les sondes comme suit :

pidID-processus:nom-objet:nom-fonction:entry
pidID-processus:nom-objet:nom-fonction:return

Par exemple, pour créer une sonde dans l'ID de processus 12345 qui se déclenche lors de l'entrée dans kstat_data_lookup(3KSTAT), vous écririez la description de sonde suivante :

pid12345:libkstat:kstat_data_lookup:entry

Le fournisseur pid insère l'instrumentation dynamique dans le processus utilisateur spécifié à l'emplacement du programme correspondant à la description de la sonde. L'implémentation de la sonde contraint chaque thread utilisateur atteignant l'emplacement du programme instrumenté à s'interrompre dans le noyau du système d'exploitation et à lancer DTrace, déclenchant ainsi la sonde correspondante. Par conséquent, bien que l'emplacement d'instrumentation soit associé à un processus utilisateur, les prédicats et les actions de DTrace que vous spécifiez continuent de s'exécuter dans le contexte du noyau du système d'exploitation. Le fournisseur pid est décrit plus en détails dans le Chapitre30Fournisseur pid.

Plutôt que de devoir éditer la source de votre programme en D chaque fois que vous souhaitez appliquer votre programme à un processus différent, vous pouvez insérer des identifiants appelés variables de macro dans votre programme. Ces identificateurs sont évalués à chaque compilation et remplacement de votre programme par des arguments de ligne de commande dtrace supplémentaires. Les variables de macro sont spécifiées à l'aide du signe dollar $ suivi d'un identifiant ou d'un chiffre. Si vous exécutez la commande dtrace -s script foo bar baz, le compilateur D définit automatiquement les variables de macro $1, $2, et $3 sur les jetons foo, bar et baz respectivement. Vous pouvez utiliser des variables de macro dans des expressions de programme en D ou des descriptions de sonde. Par exemple, les descriptions de sonde suivantes sont instrumentées quel que soit l'ID de processus spécifié comme argument supplémentaire vers dtrace:

pid$1:libkstat:kstat_data_lookup:entry
{
	self->ksname = arg1;
}

pid$1:libkstat:kstat_data_lookup:return
/self->ksname != NULL && arg1 != NULL/
{
	this->ksp = (kstat_named_t *)copyin(arg1, sizeof (kstat_named_t));
	printf("%s has ui64 value %u\n", copyinstr(self->ksname),
	    this->ksp->value.ui64);
}

pid$1:libkstat:kstat_data_lookup:return
/self->ksname != NULL && arg1 == NULL/
{
	self->ksname = NULL;
}

Les variables de macro et les scripts réutilisables sont décrits plus en détails dans le Chapitre15Scripts. Maintenant que nous savons comment instrumenter des processus utilisateur à l'aide de leur ID de processus, revenons aux unions d'échantillonnage. Ouvrez votre éditeur, tapez le code source de votre exemple complet et enregistrez-le dans un fichier nommé kstat.d:


Exemple 7–3 kstat.d : appels de suivi vers kstat_data_lookup(3KSTAT)

pid$1:libkstat:kstat_data_lookup:entry
{
	self->ksname = arg1;
}

pid$1:libkstat:kstat_data_lookup:return
/self->ksname != NULL && arg1 != NULL/
{
	this->ksp = (kstat_named_t *) copyin(arg1, sizeof (kstat_named_t));
	printf("%s has ui64 value %u\n",
	    copyinstr(self->ksname), this->ksp->value.ui64);
}

pid$1:libkstat:kstat_data_lookup:return
/self->ksname != NULL && arg1 == NULL/
{
	self->ksname = NULL;
}

Accédez à présent à l'un de vos shells et exécutez la commande mpstat 1 pour lancer mpstat(1M) dans un mode dans lequel elle échantillonne les statistiques et les signale à la cadence d'une par seconde. Une fois mpstat en cours d'exécution, exécutez la commande dtrace -q -s kstat.d `pgrep mpstat` dans votre autre shell. Vous obtenez une sortie correspondant aux statistiques en cours d'accès. Appuyez sur Control-C pour abandonner dtrace et revenir à l'invite du shell.


# dtrace -q -s kstat.d `pgrep mpstat`
cpu_ticks_idle has ui64 value 41154176
cpu_ticks_user has ui64 value 1137
cpu_ticks_kernel has ui64 value 12310
cpu_ticks_wait has ui64 value 903
hat_fault has ui64 value 0
as_fault has ui64 value 48053
maj_fault has ui64 value 1144
xcalls has ui64 value 123832170
intr has ui64 value 165264090
intrthread has ui64 value 124094974
pswitch has ui64 value 840625
inv_swtch has ui64 value 1484
cpumigrate has ui64 value 36284
mutex_adenters has ui64 value 35574
rw_rdfails has ui64 value 2
rw_wrfails has ui64 value 2
...
^C
#

Si vous capturez la sortie dans chaque fenêtre de terminal et que vous retirez chaque valeur de la valeur signalée par l'itération précédente par l'intermédiaire des statistiques, vous devriez pouvoir faire correspondre la sortie dtrace avec la sortie de mpstat. Le programme donné en exemple enregistre le pointeur du nom de compteur en entrée dans la fonction de recherche, puis exécute la majeure partie du travail de suivi en retour à partir de kstat_data_lookup(3KSTAT) Les fonctions en D intégrées copyinstr() et copyin() recopient les résultats du processus utilisateur dans DTrace lorsque arg1 (valeur retournée) n'est pas NULL. Une fois que les données de kstat ont été copiées, l'exemple signale la valeur de compteur de ui64 à partir de l'union. Cet exemple simplifié suppose que mpstat échantillonne les compteurs utilisant le membre value.ui64. Essayez, sous forme d'exercice, de recoder kstat.d pour utiliser des prédicats et d'imprimer le membre de l'union correspondant au membre data_type. Vous pouvez également essayer de créer une version de kstat.d qui calcule la différence entre des valeurs de données successives et offre généralement une sortie similaire à mpstat.

Tailles des membres et décalages

Vous pouvez déterminer la taille en octets de tout type ou expression en D, y compris une struct ou une union, à l'aide de l'opérateur sizeof. Vous pouvez appliquer l'opérateur sizeof à une expression ou à un nom d'un type placé entre parenthèses, comme illustré dans les deux exemples suivants :

sizeof expression				sizeof (type-name)

Par exemple, l'expression sizeof (uint64_t) doit retourner la valeur 8 tandis que l'expression sizeof (callinfo.ts) doit retourner la valeur 8 si elle est insérée dans le code source de notre exemple de programme ci-dessus. Le type de retour formel de l'opérateur sizeof est l'alias de type size_t. Ce dernier est défini comme un nombre entier non signé de la même taille qu'un pointeur dans le modèle de données courant et sert à représenter des comptes d'octets. Lorsque l'opérateur sizeof est appliqué à une expression, cette dernière est validée par le compilateur D mais la taille de l'objet qui en résulte est calculée au moment de la compilation et aucun code n'est généré pour l'expression. Vous pouvez utiliser sizeof chaque fois qu'une constante entière est requise.

Vous pouvez utiliser l'opérateur d'accompagnement offsetof pour déterminer le décalage en octets d'un membre de la struct ou de l'union depuis le début du stockage par rapport à n'importe quel objet du type de struct ou d'union. L'opérateur offsetof est utilisé dans une expression semblable à celle-ci :

offsetof (type-name, member-name)

Dans cet exemple, type-name correspond au nom d'un type de struct ou d'union ou d'un alias de type et member-name correspond au nom d'identification d'un membre de cette struct ou union. De même que sizeof, la fonction offsetof renvoie une valeur size_t. Vous pouvez l'utiliser dans un programme en D dès l'instant qu'une constante entière peut être utilisée.

Champs de bit

Le langage D permet également de définir les membres entiers d'une struct ou d'une union constitués d'un nombre arbitraire de bits, les champs de bit. Pour déclarer un champ de bit, vous devez spécifier un type de base de nombre entier signé ou non signé, un nom de membre et un suffixe indiquant le nombre de bits à affecter au champ, comme illustré dans l'exemple suivant :

struct s {
	int a : 1;
	int b : 3;
	int c : 12;
};

La largeur du champ de bit est une constante entière séparée du nom du membre par le symbole : à droite. La largeur du champ de bit doit être positive et doit correspondre à un nombre de bits qui n'excède pas la largeur du type de base de nombre entier correspondant. Il n'est pas possible de déclarer en D les champs de bit dont la largeur est supérieure à 64 bits. Les champs de bit en D assurent la compatibilité et l'accès aux fonctionnalités ANSI-C correspondantes. Les champs de bits sont généralement utilisés dans des situations dans lesquelles le stockage en mémoire est crucial ou lorsque la disposition d'une struct doit correspondre à la disposition d'un registre matériel.

Un champ de bit est un constructeur de compilateur qui automatise la disposition d'un nombre entier et un ensemble de masques pour extraire la valeur des membres. Vous pouvez obtenir le même résultat en définissant simplement les masques vous-même et en utilisant l'opérateur &. Les compilateurs C et D essayent d'empiler les bits aussi efficacement que possible, mais ils sont libres d'exécuter cette action comme ils le souhaitent. Par conséquent, les champs de bit ne garantissent pas l'obtention de dispositions de bit identiques d'un compilateur ou d'une architecture à l'autre. Si une disposition de bit stable est requise, vous devez construire les masques de bit vous-même et extraire les valeurs à l'aide de l'opérateur &.

Vous pouvez accéder à un membre de champ de bit en spécifiant simplement son nom avec les opérateurs “.” ou -> comme tout autre membre d'une struct ou d'une union. Le champ de bit est automatiquement promu vers le type de nombre entier le plus grand à utiliser dans les expressions. Le stockage des champs de bit ne s'alignant pas sur une limite d'octets ou leur taille ne correspondant pas à un nombre entier rond, vous ne pouvez pas appliquer les opérateurs sizeof ou offsetof à un membre de champ de bit. Le compilateur D ne vous permet pas non plus de récupérer l'adresse d'un membre de champ de bit à l'aide de l'opérateur &.