Conhecendo a macro container_of

funções X macros compilação condicional Diagnóstico

Olá, caro leitor! Neste artigo apresentarei algumas operações com ponteiros e estruturas que podem ser bem úteis para estruturar um projeto. Como solução ao problema que será abordado, apresentarei uma macro muito utilizada no Linux: A macro container_of. Tal macro serve para obter o endereço de uma estrutura que contém um campo do qual apenas conhecemos seu endereço. Dessa maneira, o artigo foi estruturado em duas partes: A primeira descreve um estudo de caso, já a segunda apresenta em detalhes a macro container_of.

Estudo de Caso de container_of

Considere, por exemplo, que uma estrutura t_device foi definida no seu projeto. Essa estrutura é utilizada como base para todos os dispositivos que são utilizados. Além disso, uma segunda estrutura contém informações específicas de um dispositivo chamado led. Nesse caso, a estrutura t_led contém dois elementos específicos mais a estrutura dev, do tipo t_device.

Por definição, todo módulo do seu projeto tem uma função chamada DoSomething. Tal função recebe como argumento um endereço para uma estrutura do tipo t_device.

De fato, como temos o endereço do dado, podemos acessá-lo normalmente. Mas, e se ao invés de acessar o tipo dado t_device você quisesse acessar a estrutura t_led? Isso é válido pois dev é membro de t_led. É claro que podemos aplicar aquelas boas práticas de codificação para obter o resultado desejado… mas, vamos ver como isso é feito no Linux.

Considere que a função DoSomething é chamada passando como argumento o endereço de ‘led.dev‘, representado por ‘ptr‘. Dentro da função, o objetivo é obter um outro ponteiro, porém, apontando para ‘base address‘, isto é, o endereço de led.

Representação das estruturas na memória, para teste da macro container_of
Figura 1: Representação das estruturas na memória.

A macro container_of pode ser utilizada nessa situação. Isto é, para obter o endereço de uma estrutura que contém um campo do qual apenas conhecemos seu endereço, precisamos apenas informar qual o tipo da estrutura que queremos e de qual membro temos o endereço. Dessa maneira, a macro retorna o endereço para o tipo especificado. Cabe ressaltar que um tipo de dado comum também pode ser utilizado.

Para testar isso o seguinte trecho de código pode ser utilizado:

A Macro container_of

O Linux kernel possui um procedimento para tratar o caso apresentado anteriormente. Tal procedimento é realizado pela macro container_of, definida em include/linux/kernel.h.

Além disso, a macro utiliza algumas definições específicas do gcc, como o caso do typeof. Já a macro offsetof é C ANSI, sendo definida em stddef.h. A macro offsetof recebe o tipo de dado composto (TYPE) e o nome de um membro (MEMBER).

Assim, a macro offsetof considera um ponteiro do tipo da estrutura com endereço zero ((TYPE *)0). Dessa maneira, ao acessar um membro ((TYPE *)0)->MEMBER e, em seguida, obtendo seu endereço &(((TYPE *)0)->MEMBER), estaremos calculando o offset desse membro em relação ao endereço base. Pois o endereço do membro menos o endereço base, que é igual a zero, resulta no próprio deslocamento.

Agora, quando a macro container_of é utilizada, o seu retorno será o endereço para o tipo da estrutura especificada. Esse procedimento é realizado em duas etapas e, por isso, possui o código entre chaves (compound statement). container_of(ptr, type, member) ({ …. }). Dessa maneira, o resultado da última operação será atribuído à variável de destino.

Primeiro é definido um ponteiro __mptr do tipo do membro. Esse ponteiro é inicializado com o endereço recebido.

const typeof( ((type *)0)->member ) *__mptr = (ptr);

Depois é realizada a diferença desse endereço (com cast para char *) com o offset do membro.

(TYPE *)( (char *)__mptr – offsetof(TYPE,MEMBER));

Essa última operação já resulta no endereço real da estrutura informada.

Cabe ressaltar que o código não faz verificação de tipo em tempo de execução. Dessa maneira, o desenvolvedor fica responsável por essa especificação. Dito de outra maneira, por ser uma macro, os seus parâmetros não têm especificadores de tipo. Assim, o ponteiro (ptr) recebido não tem seu tipo conhecido. Logo, a primeira linha funciona como um especificador de tipo para o ponteiro e, portanto, o membro especificado deve pertencer ao tipo de dado. Desse modo, o compilador pode detectar incompatibilidade entre as informações passadas na macro. 

Para sabe mais

Essa macro foi escrita inicialmente por Gregh Kroah-Hartman. Neste link, Gregh apresenta um artigo que foi publicado na Linux Journal em Junho de 2003, tópico 110. O propósito era escrever um código mais estável para gravar e configurar dispositivos hot plug. Tal macro facilita a construção e manipulação de projetos que têm como característica a “herança de estruturas”. Exemplo disso é a unificação do sistema de drivers durante o desenvolvimento do Kernel 2.5. A partir desse ponto todos os drivers passaram a usar uma estrutura em comum chamada device (struct struct). Assim, a macro container_of é utilizada para obter o endereço da estrutura que contém essa informação do dispositivo. Alguns padrões comuns no desenvolvimento de drivers são descritos neste link.

Referências

Imagem destacada.

Veja + conteúdo

Fascinado por computação, especialmente na interface entre hardware e software, me engajei na área de sistemas embarcados. Atuo com desenvolvimento de sistemas embarcados e sou docente da Faculdade de Engenharia de Sorocaba.

Para mais informações: https://about.me/fdelunogarcia

Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.

Receba os melhores conteúdos sobre sistemas eletrônicos embarcados, dicas, tutoriais e promoções.

Comentários:
Notificações
Notificar
guest
2 Comentários
recentes
antigos mais votados
Inline Feedbacks
View all comments
Gustavo Laureano Cardoso
Gustavo
26/01/2017 07:15

Ótimo artigo, parabens!

Hugo Borges
Hugo Borges
24/01/2017 08:25

Bem legal! Não conhecia… bem útil pra obter um comportamento parecido com polimorfismo.
Das vezes que precisei fazer algo do tipo, eu sempre deixava um ponteiro void * pro parent.

Talvez você goste:

Séries

Menu