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

[wpseo_breadcrumb]
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
Privacy Settings saved!
Configurações de Privacidade

Entenda quais dados e informações usamos para ter melhor entrega de conteúdo personalizado para você.

These cookies are necessary for the website to function and cannot be switched off in our systems.

Para usar este site, usamos os seguintes cookies tecnicamente exigidos

  • wordpress_test_cookie
  • wordpress_logged_in_
  • wordpress_sec

Decline all Services
Accept all Services