Pré-processador C: X macros – Parte 4

funções X macros compilação condicional Diagnóstico
Este post faz parte da série Pré-processador C. Leia também os outros posts da série:

Olá, caro leitor! No último artigo da série Pré-processador C vamos analisar algumas formas de realizar a geração automática de código a partir de uma técnica conhecida como X macros. Para mais informações sobre o pré-processador veja os outros artigos da série, listados no final do artigo. Vamos lá!

Expansão de macros – X macros

X macros é o nome dado para uma técnica de programação que faz uso do pré-processador para construir um mecanismo de geração automática de código.

Para entender essa técnica é necessário ter conhecimento sobre a estrutura básica do pré-processador C e principalmente do mecanismo de definição de macros. Antes de apresentar a ideia geral da técnica, vou utilizar como exemplo um código que apresenta uma tabela de mensagens.

Do código é importante observar que a enumeração e a declaração das mensagens estão relacionadas. Uma pequena alteração na ordem da enumeração ou da declaração das mensagens pode alterar de forma significativa a execução do programa. Para os casos onde temos a definição de muitas informações que estão relacionadas, o recurso de geração automática de código pode ser muito útil.

A ideia da técnica X macros é agregar todos os dados relacionados em uma macro para depois utilizar os recursos de expansão de macros do pré-processador para gerar código automaticamente. Esse procedimento pode ser realizado de duas formas. A primeira é mais simples e serve como base para entender a segunda abordagem.

Primeira abordagem de X macros

De início vamos considerar que em algum ponto do programa exista uma macro com identificador INIT_MESSAGE, que recebe como parâmetro todas as informações que necessitamos. Em um arquivo chamado Messages.h essa macro é utilizada para definir todas as informações que precisamos.

No arquivo Messages.h temos o conteúdo mostrado abaixo.

Não vamos utilizar aqui o conceito de Header Guards, pois a ideia é que toda vez que o arquivo Messages.h for incluído o seu conteúdo seja copiado, isto é, as chamadas de macro INIT_MESSAGE serão copiadas para o local onde a diretiva #include “Messages.h” foi utilizada.

Voltando ao código de exemplo, podemos definir o enum da seguinte forma:

No trecho de código mostrado acima ocorre a definição da macro INIT_MESSAGE que possui dois parâmetros. Na sequência o conteúdo do arquivo Messages.h é copiado para o local do #include. O resultado disso é mostrado abaixo.

Vale lembrar que quando a macro for utilizada o seu identificador será substituído pelo seu valor, nesse caso a expansão da macro resultaria no primeiro parâmetro seguido da vírgula.

Convém observar que a macro é removida logo após a sua utilização. Já para inicializar a tabela de strings o mesmo processo pode ser realizado!

Agora a macro definida é expandida utilizando somente o parâmetro MESSAGE.

E o resultado final será equivalente à tabela de mensagens do código de exemplo.

Convém observar que para adicionar ou remover uma mensagem fica muito mais simples e as informações localizadas em um único local.

Para saber quantas mensagens foram definidas podemos utilizar uma estrutura composta por variáveis de um byte para representar cada mensagem definida.

O resultado da expansão das macros é uma estrutura com elementos de um byte com nome definido pelo parâmetro ID.

Já a macro que representa a quantidade de mensagens pode ser definida da seguinte forma.

O código final é mostrado abaixo.

Segunda abordagem de X macros

Na segunda abordagem não é necessário utilizar um arquivo com as chamadas de macro. Nesse caso, a chamada de macro será realizada por outra macro.

Observe que as chamadas de macro INIT_MESSAGE serão realizadas na expansão da macro INIT_MESSAGES. Outro ponto importante é que INIT_MESSAGE é um parâmetro da macro INIT_MESSAGES.

Agora podemos definir o enum da seguinte forma.

O procedimento é parecido com o mostrado na primeira abordagem. Para configurar os elementos da enumeração a macro INIT_MESSAGES é utilizada e o argumento passado é o identificador EXPAND_ENUM. A expansão da macro INIT_MESSAGES resulta no código mostrado abaixo.

Já a expansão da macro EXPAND_ENUM resulta no código mostrado abaixo.

Para definir a tabela de strings uma nova macro é criada.

A expansão da macro INIT_MESSAGES resulta no código abaixo.

E por fim.

Da mesma forma como mostrado na primeira abordagem, a quantidade de mensagens pode ser obtida a partir do tamanho de uma struct.

O código final demonstrado na segunda abordagem é mostrado logo abaixo.

Conclusão

Nesse artigo foi abordada uma técnica de programação que utiliza o pré-processador C como agente para geração automática de código. Fica demonstrado que a técnica X macros possibilita reduzir o processo de repetição de código o que pode ser uma fonte geradora de erros.

Não se pode deixar de fazer algumas comparações com a codificação direta. O trabalho que é facilitado pelo pré-processador é compensado pela legibilidade do código? O tempo para estruturar o mecanismo de geração de código é menor que o tempo perdido na manutenção do código? Acredito que essas questões podem ser respondidas somente pelo programador ou equipe de desenvolvimento frente à aplicação desta técnica em um determinado problema. E você, qual a sua opinião?

Demonstrada essa técnica, chega ao fim a série de artigos sobre o Pré-processador C! Para aprimorar a técnica pesquise mais sobre Metaprogramação utilizando o pré-processador C.

Referências

Fonte da imagem destacada: http://listamaze.com/top-10-programming-languages-for-job-security/

Outros artigos da série

<< Pré-processador C: Diagnóstico – Parte 3

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.

Home » Software » Pré-processador C: X macros – Parte 4
Comentários:
Notificações
Notificar
guest
8 Comentários
recentes
antigos mais votados
Inline Feedbacks
View all comments
Haroldo Amaral
Haroldo Amaral
08/12/2015 11:48

Fernando, é possível criar nomes alternativos para funções, por exemplo para manter compatibilidade com versões antigas? Por exemplo:

// função original
int funcao_original(int a, int b);

// chamada padrão
funcao_original(1, 1);

// chamada alternativa
nome_alternativo(1, 1);

Abraço

Fernando Deluno Garcia
Fernando Deluno Garcia
Reply to  Haroldo Amaral
10/12/2015 12:40

Olá, Haroldo.

Não sei se entendi direito sua pergunta. Você quer que a função criada tenha um determinado nome e possa ser chamada por outro?

Uma forma de fazer isso seria criando um #define

#if VERSAO == X

#define NOME_ALTERNATIVO nome_alternativo1

#elif VERSAO == Y

#define NOME_ALTERNATIVO nome_alternativo2

#else

#error Definir o nome alternativo da função …..

#endif

#define MINHA_FUNCAO NOME_ALTERNATIVO

Leonardo José Consoni
Leonardo José Consoni
26/09/2015 14:49

Muito obrigado pelos artigos, em especial esse último, que me forneceu uma ferramenta muito útil para o meu projeto: Com a intenção de simular algo como namespaces em C, estava experimentando usar a seguinte construção: static int MeuNamespace_Funcao1( const char* ); static void MeuNamespace_Funcao2( int, double ); const struct { int (*Funcao1)( const char* ); void (*Funcao2)( int, double ); } MeuNamespace = { .Funcao1 = MeuNamespace_Funcao1, .Funcao2 = MeuNamespace_Funcao2 }; static void FuncaoInterna( int ); Que, a medida que a interface aumentava, me fazia arcar com muita repetição de código e vários erros de compilação por descuido. Com algo… Leia mais »

Leonardo José Consoni
Leonardo José Consoni
Reply to  Leonardo José Consoni
26/09/2015 14:52

Minha única frustração foi não ter conseguido colocar todo o processo dentro de uma única macro… por enquanto acho que a única forma seria podendo usar um #define dentro de outro, o que o preprocessador não permite

Fernando Deluno Garcia
Fernando Deluno Garcia
Reply to  Leonardo José Consoni
27/09/2015 20:21

Olá, Leonardo.

Obrigado pelo retorno!

Para este caso a técnica x macros reduz bastante o trabalho de repetição. Já fiz algo parecido em um sistema dividido em módulos. Ficou bem fácil adicionar/remover funcionalidades.

Quando você fala em utilizar uma macro, significa chamar a macro para realizar todas as configurações necessárias (criar uma determinada macro, usá-la e removê-la)?

Leonardo José Consoni
Leonardo José Consoni
Reply to  Fernando Deluno Garcia
27/09/2015 20:52

Algo como conseguir gerar todo esse código com uma única function-like macro do tipo:

CREATE_INTERFACE( MeuNamespace,
FUNCTION ( int, Funcao1, const char* ),
FUNCTION ( void, Funcao2, int, double ) )

Não encontrei um meio ainda, mas nesse caso é mais um capricho pra condensar o código

Cesar Junior
Cesar Junior
22/09/2015 08:41

Parabéns pelos artigos! Muito bom.

Fernando Deluno Garcia
Fernando Deluno Garcia
Reply to  Cesar Junior
24/09/2015 09:11

Olá, Cesar.

Fico feliz que tenha gostado da série!

Talvez você goste:

Séries



Outros da Série

Menu