Guia de rastreamento dinâmico Solaris

Capítulo 8 Definições de tipo e de constante

Este capítulo descreve como declarar alias de tipo e constantes nomeadas em D. Este capítulo também discute o gerenciamento do espaço de nome e do tipo de D para identificadores e tipos de sistemas operacionais e programas.

Typedef

A palavra-chave typedef é usada para declarar um identificador como um alias de um tipo existente. Como todas as declarações de tipo de D, a palavra-chave typedef é usada fora das cláusulas do teste em uma declaração no formato:

typedef existing-type new-type ;

onde tipo existente é uma declaração de qualquer tipo e tipo novo é um identificador a ser usado como alias desse tipo. Por exemplo, a declaração:

typedef unsigned char uint8_t;

é usada internamente pelo compilador de D para criar o alias do tipo uint8_t . Os alias de tipo podem ser usados em qualquer lugar que um tipo normal possa ser usado, como o tipo de uma variável ou valor de matriz de associação ou membro de tupla. Você também pode combinar typedef com declarações mais elaboradas como a definição de uma nova struct:

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

Neste exemplo, struct foo é definida como o mesmo tipo que o seu alias, foo_t. Os cabeçalhos do sistema C do Solaris geralmente usam o sufixo _t para indicar um alias de typedef.

Enumerações

Definir nomes simbólicos para constantes em um programa facilita a legibilidade e simplifica o processo de manutenção do programa no futuro. Um método é definir uma enumeração que associa um conjunto de inteiros a um conjunto de identificadores chamados enumeradores, que o compilador reconhece e substitui pelo valor de inteiro correspondente. Uma enumeração é definida usando-se uma declaração como:

enum colors {
	RED,
	GREEN,
	BLUE
};

O primeiro enumerador na enumeração, RED, recebe o valor zero e cada identificador subseqüente recebe o próximo valor de inteiro. Você também pode especificar um valor de inteiro explícito para qualquer enumerador usando como sufixo um sinal de igual e uma constante de inteiro, como no exemplo a seguir:

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

O compilador atribui o valor 10 ao enumerador BLUE porque ele não tem valor especificado e o enumerador anterior está definido como 9. Quando a enumeração é definida, os enumeradores podem ser usados em qualquer lugar de um programa em D em que uma constante de inteiro possa ser usada. Além disso, a enumeração enum colors também é definida como um tipo que seja equivalente a um int. O compilador de D permitirá que uma variável do tipo enum seja usada em qualquer lugar em que um int possa ser usado, e permitirá que qualquer valor de inteiro seja atribuído a uma variável do tipo enum. Você também pode omitir o nome da enum na declaração se o nome do tipo não for necessário.

Os enumeradores são visíveis em todas as cláusulas e declarações subseqüentes no seu programa, sendo assim, você não pode definir o mesmo identificador de enumerador em mais de uma enumeração. Entretanto, você pode definir mais de um enumerador que tenha o mesmo valor na mesma enumeração ou em enumerações diferentes. Você também pode atribuir inteiros que não possuam um enumerador correspondente a uma variável do tipo da enumeração.

A sintaxe da enumeração de D é a mesma que a sintaxe correspondente em ANSI-C. D também fornece acesso a enumerações definidas no kernel do sistema operacional e seus módulos carregáveis, mas esses enumeradores não são globalmente visíveis no programa em D. Os enumeradores do kernel só ficam visíveis quando usados como argumento para um dos operadores de comparação binários, quando comparados a um objeto do tipo de enumeração correspondente. Por exemplo, a função uiomove(9F) possui um parâmetro do tipo enum uio_rw definido da seguinte maneira:

enum uio_rw { UIO_READ, UIO_WRITE };

Os enumeradores UIO_READ e UIO_WRITE normalmente não são visíveis no seu programa em D, mas você pode promover sua visibilidade global comparando um deles a um valor do tipo enum uio_rw, conforme mostrado na cláusula do seguinte exemplo:

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

Este exemplo rastreia chamadas à função uiomove(9F) para solicitações de gravação, comparando args[2], uma variável do tipo enum uio_rw, ao enumerador UIO_WRITE. Como o argumento do lado esquerdo é um tipo de enumeração, o compilador de D pesquisa a enumeração ao tentar resolver o identificador do lado direito. Esse recurso protege seus programas em D contra conflitos de nome de identificador acidentais com a grande coleção de enumerações definidas no kernel do sistema operacional.

In-lines

As constantes nomeadas de D também podem ser definidas usando-se diretivas in-line, que fornecem um meio mais geral de criar identificadores que são substituídos por expressões ou valores predefinidos durante a compilação. As diretivas embutidas são uma forma de substituição lexical mais eficiente que a diretiva #define fornecida pelo pré-processador de C porque um tipo real é atribuído à substituição e ela é realizada usando-se a árvore de sintaxe compilada e não simplesmente um conjunto de símbolos lexicais. Uma diretiva in-line é especificada usando-se uma declaração da seguinte forma:

inline type name = expression ;

onde tipo é uma declaração de tipo de um tipo existente, nome é qualquer identificador válido de D que não tenha sido definido anteriormente como uma variável in-line ou global e expressão é qualquer expressão válida de D. Quando a diretiva in-line é processada, o compilador de D substitui a forma compilada da expressão de cada instância subseqüente de nome na origem do programa. Por exemplo, o programa em D a seguir faria o rastreio da seqüência "olá" e do valor de inteiro 123:

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

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

Um nome in-line pode ser usado em qualquer lugar que uma variável global do tipo correspondente pode ser usada. Se a expressão in-line puder ter como valor uma constante de seqüências ou de inteiro na compilação, então o nome in-line também poderá ser usado em contextos que requeiram expressões de constante, como dimensões de matriz escalar.

A expressão in-line é validada quanto a erros de sintaxe como parte da avaliação da diretiva. O tipo de resultado da expressão deve ser compatível com o tipo definido pela diretiva in-line, de acordo com as mesmas regras usadas para o operador de atribuição de D (=). Uma expressão in-line não pode fazer referência ao próprio identificador in-line: definições recursivas não são permitidas.

Os pacotes de software do DTrace instalam inúmeros arquivos de origem de D no diretório do sistema /usr/lib/dtrace que contém diretivas in-line que você pode usar em seus programas em D. Por exemplo, a biblioteca signal.d inclui diretivas no formato:

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

Estas definições in-line fornecem acesso ao conjunto atual de nomes de sinal do Solaris descritos em signal(3HEAD). Semelhantemente, a biblioteca errno.d contém diretivas in-line das constantes errno de C descritas em Intro(2).

Por padrão, o compilador de D inclui automaticamente todos os arquivos de biblioteca de D fornecidos para que você possa usar estas definições em qualquer programa em D.

Espaços de nome de tipo

Esta seção discute os espaços de nome de D e as questões de espaço de nome relacionadas a tipos. Em linguagens tradicionais como ANSI-C, a visibilidade do tipo é determinada pelo fato de um tipo estar ou não aninhado em uma função ou outra declaração. Os tipos declarados no escopo externo de um programa em C são associados a um espaço de nome global único e são visíveis em todo o programa. Os tipos definidos nos arquivos de cabeçalho de C são geralmente incluídos nesse escopo externo. Ao contrário dessas linguagens, D fornece acesso a tipos de vários escopos externos.

A linguagem D facilita a observação dinâmica em várias camadas de uma pilha de software, incluindo o kernel do sistema operacional, um conjunto associado de módulos carregáveis do kernel e processos do usuário em execução no sistema. Um único programa em D pode instanciar testes para coletar dados de vários módulos do kernel ou outras entidades de software compiladas em objetos binários independentes. Portanto, pode haver mais de um tipo de dados do mesmo nome, talvez com definições diferentes, no universo de tipos disponíveis para o DTrace e o compilador de D. Para gerenciar essa situação, o compilador de D associa cada tipo a um espaço de nome identificado pelo objeto do programa que o contém. Tipos de um objeto de programa em particular podem ser acessados especificando-se o nome do objeto e o operador de escopo de aspa invertida (`) em qualquer nome de tipo.

Por exemplo, se um módulo do kernel denominado foo contiver a seguinte declaração do tipo C:

typedef struct bar {
	int x;
} bar_t;

então os tipos struct bar e bar_t poderiam ser acessados de D usando-se os nomes de tipo:

struct foo`bar				foo`bar_t

O operador de aspa invertida pode ser usado em qualquer contexto em que um nome de tipo seja apropriado, incluindo a especificação do tipo de declarações de variável de D ou expressões de difusão em cláusulas de teste de D.

O compilador de D também fornece dois espaços de nome de tipo incorporados especiais que usam os nomes C e D respectivamente. O espaço de nome de tipo de C é inicialmente preenchido com os tipos intrínsecos padrão de ANSI-C como int. Além disso, as definições de tipo obtidas usando-se o pré-processador de C cpp(1) e a opção dtrace -C serão processadas pelo escopo de C e adicionadas nele. Como resultado, você pode incluir arquivos de cabeçalho de C, contendo declarações de tipo que já estão visíveis, em outro espaço de nome de tipo sem causar um erro de compilação.

O espaço de nome do tipo de D é preenchido inicialmente com elementos intrínsecos do tipo de D como int e string, assim como os alias de tipo de D incorporados como uint32_t. Todas as novas declarações de tipo que aparecerem na origem do programa em D serão automaticamente adicionadas ao espaço de nome de tipo D. Se você criar um tipo complexo como uma struct no seu programa em D, consistindo em tipos de membro de outros espaços de nome, os tipos de membro serão copiados para o espaço de nome de D pela declaração.

Quando o compilador de D encontra uma declaração de tipo que não especifica um espaço de nome explícito usando o operador de aspa invertida, o compilador pesquisa o conjunto de espaços de nome de tipo ativo para localizar uma correspondência usando o nome de tipo especificado. O espaço de nome de C sempre é pesquisado primeiro, seguido pelo espaço de nome de D. Se o nome do tipo não for encontrado no espaço de nome de C ou D, os espaços de nome de tipo dos módulos ativos do kernel serão pesquisados em ordem crescente por ID do módulo do kernel. Essa ordenação garante que os objetos binários que formam o kernel do núcleo sejam pesquisados antes de quaisquer módulos carregáveis do kernel, mas não garante quaisquer propriedades de ordenação entre os módulos carregáveis. Você deve usar o operador de escopo ao acessar tipos definidos em módulos carregáveis do kernel para evitar conflitos de nome de tipo com outros módulos do kernel.

O compilador de D usa informações de depuração compactadas de ANSI-C fornecidas com os módulos do kernel do núcleo do Solaris para acessar automaticamente os tipos associados ao código-fonte do sistema operacional sem a necessidade de acessar os arquivos de inclusão correspondentes de C. Essas informações de depuração simbólicas podem não estar disponíveis para todos os módulos do kernel no seu sistema. O compilador de D irá reportar um erro se você tentar acessar um tipo dentro do espaço de nome de um módulo que não possua as informações de depuração compactadas de C destinadas para uso com o DTrace.