Guía de seguimiento dinámico de Solaris

Capítulo 8 Definiciones de tipos y constantes

Este capítulo describe cómo declarar los alias de tipo y las constantes con nombre en D. Este capítulo también analiza la gestión de espacios de nombres y de tipos de D para los tipos e identificadores de sistema operativo y programas.

Typedef

La palabra clave typedef se utiliza para declarar un identificador como un alias para un tipo existente. Al igual que todas las declaraciones de tipo de D, la palabra clave typedef se utiliza fuera de las cláusulas de sondeo en una declaración con el formato:

typedef existing-type new-type ;

donde existing-type es cualquier declaración de tipo y new-type es un identificador que se utilizará como el alias para este tipo. Por ejemplo, la declaración:

typedef unsigned char uint8_t;

se utiliza internamente por el compilador de D para crear el alias de tipo uint8_t. Los alias de tipo se pueden utilizar en cualquier lugar donde se pueda utilizar un tipo normal, como el tipo de una variable o el valor o tupla miembro de una matriz asociativa. También puede combinar typedef con declaraciones más elaboradas como la definición de una nueva struct:

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

En este ejemplo, struct foo se define como el mismo tipo que su alias, foo_t. Las cabeceras de sistema C de Solaris utilizan a menudo el sufijo _t para indicar un alias typedef.

Enumeraciones

La definición de nombres simbólicos para las constantes en un programa facilita la lectura y simplifica el proceso de mantenimiento del programa en el futuro. Un método es definir una enumeración, que asocia un conjunto de números enteros con un conjunto de identificadores denominados enumeradores que el compilador reconoce y sustituye por el valor del número entero correspondiente. Una enumeración se define utilizando una declaración como:

enum colors {
	RED,
	GREEN,
	BLUE
};

El primer enumerador de la enumeración, RED, tiene asignado el valor cero y cada siguiente identificador tiene asignado el siguiente valor de número entero. También puede especificar un valor de número entero explícito para cualquier enumerador añadiéndole un sufijo con un signo igual y una constante de número entero, como en el siguiente ejemplo:

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

El compilador asigna el valor 10 al enumerador BLUE porque no tiene ningún valor especificado y porque el enumerador previo se establece en 9. Tras definirse una enumeración, los enumeradores se pueden utilizar en cualquier parte de un programa D en que se pueda utilizar una constante entera. Además, la enumeración enum colors también se define como un tipo que es equivalente a un int. El compilador de D también permitirá utilizar una variable de tipo enum en cualquier lugar donde se pueda utilizar un int, y permitirá asignar cualquier valor de número entero a una variable del tipo enum. También puede omitir el nombre enum en la declaración si el nombre de tipo no es necesario.

Los enumeradores son visibles en todas las siguientes cláusulas y declaraciones del programa, por lo que no puede definirse el mismo identificador de enumerador en más de una enumeración. Sin embargo, puede definir más de un enumerador que tenga el mismo o distinto valor en la misma enumeración o en una enumeración diferente. También puede asignar números enteros que no tengan un enumerador correspondiente a una variable del tipo enumeración.

La sintaxis de la enumeración de D es igual a la sintaxis correspondiente en ANSI-C. D también proporciona acceso a las enumeraciones definidas en el núcleo del sistema operativo y sus módulos cargables, pero estos enumeradores no son visibles globalmente en el programa D. Los enumeradores de núcleo sólo son visibles cuando se utilizan como un argumento para uno de los operadores de comparación binarios al compararlos con un objeto del tipo enumeración correspondiente. Por ejemplo, la función uiomove(9F) tiene un parámetro de tipo enum uio_rw definido de la siguiente manera:

enum uio_rw { UIO_READ, UIO_WRITE };

Los enumeradores UIO_READ y UIO_WRITE no se ven normalmente en el programa D, pero puede promocionarlos a la visibilidad global comparando un valor de tipo enum uio_rw, como se muestra en la siguiente cláusula de ejemplo:

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

Este ejemplo realiza un seguimiento de las llamadas a la función uiomove(9F) para las solicitudes de escritura comparando args[2], una variable de tipo enum uio_rw, con el enumerador UIO_WRITE. Debido a que el argumento de la izquierda es del tipo enumeración, el compilador de D busca la enumeración cuando intenta resolver el identificador de la derecha. Esta función protege los programas D contra los conflictos no detectados de nombres de identificadores con una larga lista de enumeraciones definidas en el núcleo del sistema operativo.

Inline

Las constantes con nombres de D también se pueden definir utilizando las directivas inline, que proporcionan un medio más general de crear identificadores que se sustituyen por valores o expresiones predefinidos durante la compilación. Las directivas inline son una forma más potente de sustitución léxica que la directiva #define proporcionada por el preprocesador de C, porque la sustitución tiene asignada un tipo actual y se realiza utilizando el árbol de sintaxis compilado y no simplemente un conjunto de símbolos léxicos. Las directivas inline se especifican utilizando una declaración con el siguiente formato:

inline type name = expression ;

donde type es una declaración de un tipo existente, name es cualquier identificador de D válido que no se haya definido anteriormente como una variable inline o global y expression es cualquier expresión válida de D. Una vez que se ha procesado la directiva inline, el compilador de D sustituye la forma compilada de expression de cada instancia siguiente de name en el código fuente del programa. Por ejemplo, el siguiente programa D realizará un seguimiento de la cadena "hello" y el valor de número entero 123:

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

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

Se puede utilizar un nombre inline en cualquier lugar donde se pueda utilizar una variable global del tipo correspondiente. Si la expresión inline se puede evaluar en una constante de cadena o de número entero en el momento de la compilación, el nombre inline también se puede utilizar en contextos que requieran expresiones de constantes, como dimensiones de matrices escalares.

La expresión inline se valida para los errores de sintaxis como parte de la evaluación de la directiva. El tipo de resultado de expresión debe ser compatible con el tipo definido por inline, de acuerdo con las mismas reglas utilizadas para el operador de asignación de D (=). Una expresión inline no debe hacer referencia al identificador inline: las definiciones recursivas no están permitidas.

Los paquetes de software DTrace instalan una serie de archivos de código fuente de D en el directorio de sistema /usr/lib/dtrace que contienen directivas inline que puede utilizar en sus programas D. Por ejemplo, la biblioteca signal.d incluye directivas con el formato:

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

Estas definiciones de inline proporcionan acceso al conjunto actual de los nombres de señal de Solaris descritos en signal(3HEAD). De forma similar, la biblioteca errno.d contiene directivas inline para las constantes errno de C descritas en Intro(2).

De modo predeterminado, el compilador de D incluye automáticamente todos los archivos de la biblioteca de D proporcionados, por lo que puede utilizar estas definiciones en cualquier programa D.

Espacios de nombre de tipo

Esta sección analiza los espacios de nombre de D y los problemas de los espacios de nombre relacionados con los tipos. En los lenguajes tradicionales como ANSI-C, la visibilidad de tipo se determina atendiendo a si el tipo está anidado dentro de una función u otra declaración. Los tipos declarados en el ámbito exterior de un programa C están asociados con un espacio de nombre global y son visibles en todo el programa. Los tipos definidos en los archivos de cabecera de C se incluyen normalmente en este ámbito exterior. A diferencia de estos lenguajes, D proporciona acceso a tipos de varios ámbitos exteriores.

D es un lenguaje que facilita la observación dinámica en varias capas de una pila de software, incluido el núcleo del sistema operativo, un conjunto de módulos de núcleo asociados y procesos de usuarios en ejecución en el sistema. Un único programa D puede instanciar sondeos para recopilar datos de varios módulos de núcleo y otras entidades de software que se hayan compilado en objetos binarios independientes. Por tanto, puede haber más de un tipo de datos del mismo nombre, tal vez con distintas definiciones, en el universo de tipos disponibles para DTrace y el compilador de D. Para gestionar esta situación, el compilador de D asocia cada tipo con un espacio de nombre identificado por el objeto de programa que lo contiene. Se puede acceder a los tipos de un objeto de programa concreto especificando el nombre de programa y el operador de ámbito de comilla invertida (`) en cualquier nombre de tipo.

Por ejemplo, si un módulo de núcleo que se llame foo contiene la siguiente declaración de tipo de C:

typedef struct bar {
	int x;
} bar_t;

a continuación, a los tipos struct bar y bar_t se podría acceder desde D utilizando los nombres de tipo:

struct foo`bar				foo`bar_t

El operador de comilla invertida se puede utilizar en cualquier contexto donde sea apropiado un nombre de tipo, incluido cuando se especifica el tipo para las declaraciones de variable de D o expresiones de función en las cláusulas de sondeo de D.

El compilador de D también proporciona dos espacios de nombre de tipo incorporados que utilizan los nombres de C y D respectivamente. El espacio de nombre de tipo de C se rellena inicialmente con los tipos intrínsecos estándar de ANSI-C como int. Además, las definiciones de tipo adquiridas utilizando el preprocesador de C cpp(1) utilizando la opción dtrace -C se procesarán por el ámbito de C y se agregarán al mismo. Como resultado, puede incluir archivos de cabecera de C que contengan las declaraciones de tipo que estén visibles en otro espacio de nombre de tipo sin provocar un error de compilación.

El nombre de espacio de tipo de D se rellena inicialmente con valores intrínsecos de tipo de D como int y string así como los alias de tipo de D incorporados como uint32_t. Cualquier declaración de tipo nuevo que aparezca en el código fuente del programa D se agrega automáticamente al espacio de nombre de tipo de D. Si crea un tipo complejo como struct en el programa D que consista en tipos de miembro de otros espacios de nombre, la declaración copiará los tipos de miembro en el espacio de nombre de D.

Cuando el compilador de D encuentra una declaración de tipo que no especifica un espacio de nombre explícito utilizando el operador de comilla invertida, el compilador busca un conjunto activo de espacios de nombre de tipo para encontrar una coincidencia utilizando el nombre de tipo especificado. El espacio de nombre de C siempre se busca primero, seguido del espacio de nombre de D. Si el nombre de tipo no se encuentra en el espacio de nombre de C o D, los espacios de nombre de tipo de los módulos de núcleo activos se buscan en orden ascendente por el Id. de módulo de núcleo. Esta ordenación garantiza que los objetos binarios que forman el núcleo se buscan antes que cualquier módulo de núcleo cargable, pero no garantiza ninguna propiedad de orden entre los módulos cargables. Deberá utilizar el operador de ámbito al acceder a los tipos definidos en los módulos de núcleo cargables para evitar conflictos de nombres de tipo con otros módulos de núcleo.

El compilador de D utiliza la información de depuración de ANSI-C proporcionada con los módulos de núcleo de Solaris para acceder automáticamente a los tipos asociados con el código fuente del sistema operativo sin necesidad de acceder a los archivos de inclusión de C correspondientes. Es posible que la información de depuración simbólica no esté disponible para todos los módulos de núcleo de su sistema. El compilador de D notificará un error si intenta acceder a un tipo en el espacio de nombre de un módulo que no cuenta con información de depuración de C comprimida prevista para utilizarse con DTrace.