Guía de seguimiento dinámico de Solaris

Capítulo 7 Estructuras y uniones

Los grupos de variables relacionadas se pueden agrupar en objetos de datos compuestos que se denominan estructuras y uniones. Puede definir estos objetos en D creando nuevas definiciones de tipos para ellos. Puede utilizar los nuevos tipos para cualquier variable de D, incluidos los valores de matrices asociativas. Este capítulo muestra la sintaxis y semántica para crear y manipular estos tipos compuestos y los operadores de D que interactúan con ellos. La sintaxis de las estructuras y uniones se muestra utilizando varios programas de ejemplo que demuestran la utilización de los proveedores fbt y pid de DTrace.

Estructuras

La palabra clave de D struct, forma reducida de estructura, se utiliza para introducir un nuevo tipo compuesto por un grupo de otros tipos. El nuevo tipo de estructura se puede utilizar como el tipo para las matrices y variables de D, lo que permite definir grupos de variables relacionadas con un único nombre. Las estructuras de D son iguales que la construcción correspondiente en C y C++. Si ha utilizado el lenguaje de programación Java, considere una estructura D como una clase, pero sólo con miembros de datos y sin ningún método.

Supongamos que desea crear un programa de seguimiento de llamadas del sistema más sofisticado en D que registre un número de aspectos acerca de cada llamada del sistema de read(2) y write(2) ejecutada por el comando shell, como el tiempo transcurrido, el número de llamadas y el recuento de bytes más grande transmitido como un argumento. Puede escribir una cláusula D para registrar estas propiedades en tres matrices asociativas independientes, como se muestra en el siguiente ejemplo:

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

Sin embargo, esta cláusula es ineficaz debido a que DTrace debe crear tres matrices asociativas por separado y guardar copias separadas de los valores de tupla idénticos para que probefunc busque cada uno de ellos. En su lugar, puede ahorrar espacio y facilitar la lectura y mantenimiento del programa utilizando una estructura. En primer lugar, declare un nuevo tipo de estructura en la parte superior del archivo origen del programa:

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 */
};

La palabra clave struct va seguida de un identificador opcional que se utiliza para hacer referencia a nuestro nuevo tipo, que se denomina struct callinfo. A continuación, los miembros de la estructura se incluyen en un conjunto de llaves { } y toda la declaración se termina con un punto y coma (; ). Cada miembro de la estructura se define utilizando la misma sintaxis que una declaración de variables de D, con el tipo del miembro indicado en primer lugar seguido de un identificador que nombra al miembro y otro punto y coma (;).

En sí misma, la declaración de la estructura define el nuevo tipo; no crea ninguna variable ni asigna ningún almacenamiento en DTrace. Una vez declarada, puede utilizar struct callinfo como un tipo en el resto del programa D, y cada variable de tipo struct callinfo guardará una copia de las cuatro variables descritas por la plantilla de la estructura. Los miembros se ordenarán en la memoria de acuerdo con la lista de miembros, con un espacio de relleno introducido entre los miembros, ya que esto es necesario con fines de alineación de los objetos de datos.

Puede utilizar los nombres de los identificadores de miembros para acceder a los valores individuales de los miembros utilizando el operador “.” escribiendo una expresión con el siguiente formato:

variable-name.member-name

El siguiente ejemplo es un programa mejorado que utiliza el nuevo tipo de estructura. Acceda al editor y escriba el siguiente programa D y guárdelo en un archivo que se llame rwinfo.d:


Ejemplo 7–1 rwinfo.d: Recuperar estadísticas de read(2) y 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);
}

Después de escribir el programa, ejecute dtrace -q -s rwinfo.d, especificando uno de los procesos de la shell. A continuación, escriba unos cuantos comandos en la shell y, cuando haya terminado de escribir los comandos shell, pulse Control-C en el terminal dtrace para iniciar el sondeo END e imprimir los resultados:


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

Punteros a las estructuras

La utilización de punteros que hagan referencia a la estructura es una situación habitual en C y D. Puede utilizar el operador -> para acceder a los miembros de la estructura mediante un puntero. Si una struct s cuenta con un miembro m y tiene un puntero a esta estructura que se llama sp (es decir, sp es una variable del tipo struct s *), puede utilizar el operador * para en primer lugar anular la referencia del puntero sp para poder acceder al miembro:

struct s *sp;

(*sp).m

o puede utilizar el operador -> como un atajo a esta notación. Los siguientes dos fragmentos de D son equivalentes en significado si sp es un puntero a una estructura:

(*sp).m				sp->m

DTrace proporciona varias variables incorporadas que son punteros a las estructuras, incluidas curpsinfo y curlwpsinfo. Estos punteros hacen referencia a las estructuras psinfo y lwpsinfo respectivamente, y su contenido proporciona una instantánea de información acerca del estado del proceso actual y el proceso ligero (LWP) asociado con el subproceso que ha iniciado el sondeo actual. Un LWP Solaris es la representación del núcleo de un subproceso de usuario, a partir del cual se crean las interfaces de los subprocesos de Solaris y POSIX. Para mayor comodidad, DTrace exporta esta información en el mismo formato que los archivos del sistema de archivos /proc, /proc/pid/psinfo y /proc/pid/lwps/lwpid/lwpsinfo. Las estructuras /proc las utilizan herramientas de depuración y supervisión como ps(1), pgrep(1) y truss(1), se definen en el archivo de cabecera del sistema <sys/procfs.h> y se describen en la página de comando man proc(4). A continuación se muestran algunas expresiones de ejemplo utilizando curpsinfo, sus tipos y sus significados:

curpsinfo->pr_pid

pid_t

Id. de proceso actual 

curpsinfo->pr_fname

char []

nombre de archivo ejecutable 

curpsinfo->pr_psargs

char []

Argumentos iniciales de la línea de comandos 

Debe revisar toda la definición de la estructura más adelante examinando el archivo de cabecera <sys/procfs.h> y las descripciones correspondientes en proc(4). El siguiente ejemplo utiliza el miembro pr_psargs para identificar un proceso de interés haciendo coincidir los argumentos de la línea de comandos.

Las estructuras se utilizan frecuentemente para crear estructuras de datos complejas en los programas C, de forma que la capacidad de describir y hacer referencia a las estructuras desde D también proporciona una potente capacidad para observar las operaciones internas del núcleo del sistema operativo Solaris y sus interfaces del sistema. Además de utilizar la estructura curpsinfo mencionada anteriormente, el siguiente ejemplo examina algunas estructuras del núcleo, así como observa la relación entre el controlador ksyms(7D) y las solicitudes read(2). El controlador utiliza dos estructuras comunes, conocidas como uio(9S) y iovec(9S), para responder a las solicitudes que se leerán desde el archivo del dispositivo de caracteres /dev/ksyms.

La estructura uio, a la que se accede utilizando el nombre struct uio o el alias de tipo uio_t, se describe en la página de comando man uio(9S) y se utiliza para describir una solicitud de E/S que implique la copia de datos entre el núcleo y un proceso de usuario. A su vez uio contiene una matriz de una o más estructuras iovec(9S) cada una de las cuales describe una parte de las E/S solicitadas, en el caso de que se soliciten varias partes utilizando las llamadas de sistema readv(2) o writev(2). Una de las rutinas de la interfaz del controlador del dispositivo (DDI) del núcleo que funciona con struct uio es la función uiomove(9F), que pertenece a una familia de funciones que utilizan los controladores de núcleo para responder a las solicitudes read(2) del proceso del usuario y copiar los datos de nuevo a los procesos del usuario.

El controlador ksyms administra un archivo de dispositivo de caracteres que se llama /dev/ksyms, que parece ser un archivo ELF que contiene información acerca de la tabla de símbolos del núcleo, pero que en realidad es una ilusión creada por el controlador utilizando un conjunto de módulos que se han cargado en la actualidad en el núcleo. El controlador utiliza la rutina uiomove(9F) para responder a las solicitudes read(2). El siguiente ejemplo muestra que los argumentos y las llamadas a read(2) desde /dev/ksyms coinciden con las llamadas realizadas por el controlador a uiomove(9F) para copiar los resultados de nuevo en el espacio de direcciones del usuario en la ubicación especificada en read(2).

Podemos utilizar la utilidad strings(1) con la opción -a para forzar un conjunto de lecturas desde /dev/ksyms. Intente ejecutar strings -a /dev/ksyms en la shell y ver el resultado que produce. En un editor, escriba la primera cláusula de la secuencia de comandos de ejemplo y guárdela en un archivo que se llame ksyms.d:

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

La primera cláusula utiliza la expresión curpsinfo->pr_psargs para acceder y hacer coincidir los argumentos de la línea de comandos de nuestro comando strings(1) de forma que la secuencia de comandos selecciona las solicitudes deread(2) correctas antes de realizar el seguimiento de los argumentos. Tenga en cuenta que si se utiliza el operador == con un argumento a la izquierda que se encuentre en una matriz de char y un argumento de sección derecha que sea una cadena, el compilador D deduce que el argumento de la izquierda se debe asignar a una cadena y se debería realizar una comparación de cadenas. Escriba y ejecute a continuacióndtrace -q -s ksyms.d en una shell y a continuación, escriba el comando strings -a /dev/ksyms en otra shell. A medida que se ejecuta strings(1), verá el resultado de DTrace, que deberá ser parecido al siguiente ejemplo:


# 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
#

Este ejemplo se puede ampliar utilizando una técnica de programación de D común para realizar el seguimiento de un subproceso desde su solicitud read(2) inicial hasta el núcleo. Tras introducir el núcleo en syscall::read:entry, la siguiente secuencia de comandos define una variable de interés de subproceso local que indica que este subproceso es de interés, y borra este indicador en syscall::read:return. Una vez que se ha definido el indicador, se puede utilizar como un predicado en otros sondeos de funciones del núcleo del instrumento como uiomove(9F). El proveedor de seguimiento de límites de las funciones de DTrace ( fbt) publica los sondeos de entrada y devolución en las funciones definidas en el núcleo, incluidas las de DDI. Escriba el siguiente código fuente que utiliza el proveedor fbt para instrumentar uiomove(9F) y de nuevo guárdelo en el archivo ksyms.d:


Ejemplo 7–2 ksyms.d: Relación entre seguimiento read(2) y 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 cláusula final del ejemplo utiliza la variable de subproceso local self->watched para identificar cuándo un subproceso de núcleo de interés accede a la rutina de DDI uiomove(9F). Una vez allí, la secuencia de comandos utiliza la matriz args incorporada para acceder al cuarto argumento (args[3]) en uiomove(), que es un puntero a struct uio que representa la solicitud. El compilador D asocia automáticamente cada miembro de la matriz args con el tipo correspondiente al prototipo de la función C para la rutina de núcleo instrumentada. El miembro uio_iov contiene un puntero a struct iovec para la solicitud. Una copia de este puntero se guarda para utilizarla en nuestra variable de cláusula local this->iov. En la instrucción final, la secuencia de comandos anula la referencia de hits->Ivo para acceder a los miembros de iovec iov_len e iov_base, que representan la longitud en bytes y la dirección base de destino de uiomove(9F), respectivamente. Estos valores deben coincidir con los parámetros de entrada de la llamada del sistema read(2) emitida en el controlador. Vaya a al shell y ejecute dtrace -q -s ksyms.d y, a continuación, vuelva a introducir el comando strings -a /dev/ksyms en otra shell. Deberá ver un resultado parecido al siguiente ejemplo:


# 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
#

Las direcciones y los procesos serán distintos en su resultado, pero deberá ver que los argumentos de entrada de read(2)coinciden con los parámetros enviados a uiomove(9F) por el controlador ksyms.

Uniones

Las uniones son otra clase de tipo compuesto compatibles con ANSI-C y D, y están relacionadas con las estructuras. Una unión es un tipo compuesto en el que se define un conjunto de miembros de distintos tipos y los objetos de los miembros ocupan la misma región de almacenamiento. Por tanto, una unión es un objeto de tipo variable, donde un miembro sólo es válido en un momento dado en función de la forma en la que se haya asignado la unión. Normalmente se utiliza otra variable o parte de estado para indicar el miembro de unión que es válido en la actualidad. El tamaño de una unión es el tamaño del miembro más grande, y la alineación de memoria utilizada para la unión es la alineación máxima requerida por los miembros de la unión.

La estructura kstat de Solaris define una estructura que contiene una unión que se utiliza en el siguiente ejemplo para ilustrar y observar las uniones de C y D. La estructura kstat se utiliza para exportar un conjunto de contadores con nombre que representan las estadísticas del núcleo, como la utilización de la memoria y el rendimiento de E/S. La estructura se utiliza para implementar utilidades como mpstat(1M) e iostat(1M). Esta estructura utiliza struct kstat_named para representar un contador con nombre y su valor y se define de la siguiente manera:

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 declaración que se examina se abrevia con fines ilustrativos. Puede encontrar una definición completa de la estructura en el archivo de cabecera <sys/kstat.h> y se describe en kstat_named(9S). La declaración anterior es válida en ANSI-C y D, y define una estructura que contiene un valor de unión como uno de sus miembros con miembros de varios tipos, en función del tipo del contador. Tenga en cuenta que dado que la propia unión se declara dentro de otro tipo, struct kstat_named, se omite un nombre formal para el tipo de unión. Este estilo de declaración se conoce como una unión anónima. El miembro que se denomina value es de un tipo de unión descrito en la declaración precedente, pero este tipo de unión en sí no tiene nombre, ya que no necesita utilizarse en ningún otro lugar. El miembro de la estructura data_type tiene asignado un valor que indica el miembro de la unión que es válido para cada objeto de tipo struct kstat_named. Un conjunto de símbolos de preprocesador de lenguaje C se define para los valores de data_type. Por ejemplo, el símbolo KSTAT_DATA_CHAR es igual a cero e indica que el miembro value.c es donde está guardado el valor en la actualidad.

Ejemplo 7–3 demuestra el acceso a la unión kstat_named.value realizando un seguimiento del proceso del usuario. Los contadores kstat se pueden tomar de un proceso de usuario con la función kstat_data_lookup(3KSTAT), que devuelve un puntero a struct kstat_named. La utilidad mpstat(1M) llama a esta función repetidamente cuando se ejecuta para tomar una muestra de los últimos valores de contador. Vaya a la shell e intente ejecutar mpstat 1 y observe el resultado. Pulse Control-C en la shell para cancelar mpstat tras unos pocos segundos. Para observar un muestreo del contador, nos gustaría habilitar un sondeo que se active cada vez que el comando mpstat llame a la función kstat_data_lookup(3KSTAT) en libkstat. Para ello, vamos a utilizar un nuevo proveedor de DTrace: pid. El proveedor pid permite crear dinámicamente sondeos en los procesos de usuario en las ubicaciones del símbolo C, como los puntos de entrada de función. Puede solicitar al proveedor de pid que cree un sondeo en una entrada de función de usuario y que devuelva sitios escribiendo descripciones de sondeo con el formato:

pidID_proceso:nombre_objeto:nombre_función:entry
pidID_proceso:nombre_objeto:nombre_función:return

Por ejemplo, si desease crear un sondeo en el Id. de proceso 12345 que se active al entrar en kstat_data_lookup(3KSTAT), escribiría la siguiente descripción de sondeo:

pid12345:libkstat:kstat_data_lookup:entry

El proveedor pid inserta una instrumentación dinámica en un proceso de usuario especificado en la ubicación del programa correspondiente a la descripción del sondeo. La implementación del sondeo fuerza a cada subproceso de usuario que llega al programa instrumentado a que se capture en el núcleo del sistema operativo y acceda a DTrace, activando el sondeo correspondiente. Por ello, aunque la ubicación de la instrumentación está asociada con un proceso de usuario, las acciones y predicados de DTrace que especifique se ejecutarán en el contexto del núcleo del sistema operativo. El proveedor de pid se describe más detalladamente en el Capítulo 30Proveedor pid.

en vez de tener que editar el código fuente del programa D cada vez que desea aplicar el programa a distintos procesos, puede insertar en el programa identificadores llamados variables de macro que se evalúan en el momento en que se compila el programa y se sustituyen con los argumentos de línea de comandos dtrace adicionales. Las variables de macro se especifican utilizando un signo de dólar $ seguido de un identificador o número. Si ejecuta el comando dtrace -s script foo bar baz, el compilador de D definirá automáticamente las variables de macro $1, $2 y $3 para que sean los símbolos foo, bar y baz respectivamente. Puede utilizar las variables de macro en expresiones de programa D o en descripciones de sondeos. Por ejemplo, las siguientes descripciones de sondeos muestran que cualquier Id. de proceso se especifica como un argumento adicional a 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;
}

Las variables de macro y las secuencias de comando reutilizables se describen con más detalle en el Capítulo 15Secuencias de comandos. Ahora que conocemos cómo instrumentar los procesos de usuario utilizando su Id. de proceso, volvamos a las uniones de muestra. Acceda al editor y escriba el código fuente de nuestro ejemplo completo y guárdelo en un archivo que se llame kstat.d:


Ejemplo 7–3 kstat.d: realizar seguimiento de llamadas a 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;
}

Ahora vaya a una de las shells y ejecute el comando mpstat 1 para iniciarmpstat(1M) ejecutándose en un modo donde tome muestras de las estadísticas y genere informes de ellas una vez por segundo. Una vez que se esté ejecutando mpstat, ejecute el comando dtrace -q -s kstat.d `pgrep mpstat` en la otra shell. Verá el resultado correspondiente a las estadísticas a las que se está accediendo. Pulse Control-C para cancelar dtrace y volver al indicador del 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 captura el resultado en cada ventana del terminal y resta cada valor del valor notificado por la iteración anterior mediante las estadísticas, debería poder correlacionar el resultado de dtrace con el resultado de mpstat. El programa de ejemplo registra el puntero del nombre del contador en la entrada a la función de búsqueda y a continuación, realiza la mayoría del trabajo de seguimiento sobre la respuesta de kstat_data_lookup(3KSTAT). Las funciones incorporadas de D copyinstr() y copyin() copian los resultados de la función desde el proceso del usuario en DTrace si arg1 (el valor devuelto) no es NULL. Una vez que se hayan copiado los datos de kstat, el ejemplo notifica el valor de contador ui64 desde la unión. Este ejemplo simplificado asume que mpstat toma muestras de contadores que utilizan el miembro value.ui64. Como ejercicio, intente grabar kstat.d para utilizar varios predicados e imprimir el miembro de unión correspondiente al miembro data_type. También puede intentar crear una versión de kstat.d que calcule la diferencia entre los valores de datos sucesivos y que genere un resultado similar al de mpstat.

Tamaños de miembros y desplazamientos

Puede determinar el tamaño en bytes de cualquier tipo o expresión de D, incluida una estructura o unión, utilizando el operador sizeof. El operador sizeof se puede aplicar a una expresión o a un nombre de un tipo entre paréntesis, como lo muestran los dos ejemplos siguientes:

sizeof expression				sizeof (type-name)

Por ejemplo, la expresión sizeof (uint64_t) devolverá el valor 8, y la expresión sizeof (callinfo.ts) también devolverá 8 si se inserta en un código fuente de nuestro programa de ejemplo descrito anteriormente. El tipo de devolución formal del operador sizeof es el alias de tipo size_t, que se define como un entero sin signo del mismo tamaño que el puntero en el modelo de datos actual, y se utiliza para representar los recuentos de bytes. Cuando se aplica el operador sizeof a una expresión, el compilador de D la valida pero el tamaño del objeto resultante se calcula en el momento de la compilación y no se genera ningún código para la expresión. Puede utilizar sizeof en cualquier lugar que se requiera una constante de número entero.

Puede utilizar el operador acompañante offsetof para determinar el desplazamiento en bytes de un miembro de una estructura o unión desde el inicio del almacenamiento asociado con cualquier objeto del tipo de estructura o unión. El operador offsetof se utiliza en una expresión con el siguiente formato:

offsetof (type-name, member-name)

Aquí, type-name es el nombre de cualquier tipo de estructura o unión o alias de tipo, y member-name es el identificador que nombra a un miembro de dicha estructura o unión. De forma parecida a sizeof, offsetof devuelve un size_t y se puede utilizar en cualquier lugar de un programa en D en el que se pueda utilizar una constante de enteros.

Campos de bits

D también permite la definición de miembros de una unión o estructura de enteros de un número arbitrario de bits, que se conoce como campos de bits. Un campo de bits se declara especificando un tipo base de entero con signo o sin él, un nombre de miembro y un sufijo que indica el número de bits que se van a asignar al campo, como se muestra en el siguiente ejemplo:

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

El ancho del campo de bits es una constante de número entero separada del nombre del miembro por un punto. El ancho del campo de bits debe ser positivo y debe ser un número de bits inferior al ancho del tipo base de entero correspondiente. Es posible que los campos de bits más grandes de 64 bits no se declaren en D. Los campos de bits de D proporcionan compatibilidad y acceso a la capacidad correspondiente de ANSI-C. Normalmente, se utilizan en situaciones en las que el almacenamiento en memoria es un elemento preciado o cuando un diseño de estructura debe coincidir con el diseño de un registro de hardware.

Un campo de bits es una construcción de compilador que automatiza el diseño de un entero y un conjunto de máscaras para extraer los valores del miembro. Se puede obtener el mismo resultado definiendo simplemente las máscaras y utilizando el operador &. Los compiladores de C y D intentan empaquetar los bits de la manera más eficaz posible, sin embargo, pueden realizar esta operación en cualquier orden o forma que deseen. Por este motivo, no se garantiza que los campos de bits produzcan diseños de bits idénticos en distintas arquitecturas o compiladores. Si requiere un diseño de bits estable, deberá crear las máscaras de bits y extraer los valores utilizando el operador &.

Se accede a un miembro de campo de bits simplemente especificando su nombre en combinación con los operadores “.” o ->, al igual que cualquier otra estructura o miembro de una unión. El campo de bits se asigna automáticamente al siguiente tipo de entero más grande para utilizarlo en cualquier expresión. Debido a que es posible que el almacenamiento del campo de bits no se alinee en un límite de bytes o que sea un numero exacto de bytes en tamaño, no se debe aplicar los operadores sizeof o offsetof a un miembro de un campo de bits. El compilador de D también prohíbe tomar la dirección de un miembro del campo de bits utilizando el operador &.