Pré-processador C – Parte 1

Pré-processador C
Este post faz parte da série Pré-processador C

Olá, caro leitor! Este artigo tem o intuito de expor, de maneira introdutória, alguns recursos do pré-processador C. Desse modo, o artigo foi estruturado para apresentar alguns exemplos básicos e algumas aplicações para você, leitor, entender melhor o assunto. Vamos lá!

Afinal, o que é o Pré-processador C?

Durante o processo de compilação de um programa você pode instruir o compilador sobre como realizar determinada tarefa. Uma das formas de fazer isso no próprio código-fonte é utilizando os recursos do pré-processador C. Especificamente, o pré-processador C é uma ferramenta utilizada antes do estágio de compilação para realizar uma transformação textual no código-fonte.

O Pré-processador C

As instruções dadas para o compilador são definidas a partir das diretivas do pré-processador. O padrão C ANSI define as seguintes diretivas:

#if
#ifdef
#ifndef
#else
#elif
#endif
#include
#define
#undef
#line
#error
#pragma

Essas diretivas possibilitam instruir o compilador a realizar as seguintes tarefas:

  • Incluir arquivos de cabeçalho (.h – header);
  • Compilação condicional;
  • Definir constantes e/ou macros;
  • Expansão de macros;
  • Controle e diagnóstico do processo de compilação.

Cabe frisar que essas diretivas não fazem parte da linguagem C, contudo fazem parte de uma ferramenta muito útil para o desenvolvimento de programas em C.

Formato das diretivas do Pré-processador C

Convém observar que todas as diretivas do pré-processador C são iniciadas com o símbolo ‘#’ seguida do nome da diretiva e devem, obrigatoriamente, ser declaradas em linhas diferentes.

Como regra para utilização das diretivas temos que:

  • O símbolo # deve ser o primeiro caractere (exceto o espaço em branco) da linha;
  • Toda diretiva é terminada ao final de cada linha, a não ser que a linha seja finalizada pelo símbolo backslash “\”;
  • Com exceção da diretiva #pragma todas as diretivas podem ser utilizadas em qualquer parte do programa.

Incluindo arquivos – #include

Modularizar e reutilizar códigos são práticas comuns e de grande importância. Para isso é necessário instruir o compilador a incluir arquivos-fonte no processo de compilação. No pré-processador C essa tarefa é realizada pela diretiva #include.

A diretiva #include instrui o compilador a ler e compilar um arquivo-fonte. De forma geral o #include determina o nome do arquivo que será processado. Por exemplo, considere o famoso Hello World: 

#include <stdio.h>
#include <stdlib.h>

int main()
{
  printf("Hello world!\n");
  return 0;
}

Ao processar o arquivo, a diretiva #include instrui o compilador a ler e compilar o arquivo stdio.h. Mas afinal, onde está localizado o arquivo stdio.h?

Para responder essa pergunta é necessário antes verificar a sintaxe da diretiva. Basicamente existem duas maneiras de incluir um arquivo. Uma é utilizando os símbolos ‘<’ e ‘>’, já a outra é utilizando aspas. Veja: 

#include <stdio.h>
#include “stdio.h”

Ao utilizar os símbolos de maior e menor você instrui o compilador a procurar o arquivo nos diretórios do sistema, neste caso, definido pelo compilador. Já na outra abordagem, o arquivo é procurado no diretório do código-fonte (diretório de trabalho).

Definindo macros – #define

A diretiva #define é utilizada para definir um nome (identificador, denominado macro) que mantém um valor associado. No estágio inicial da compilação o identificador é substituído pelo seu valor.

Considere o exemplo básico para piscar um LED no Arduino:

// the setup function runs once when you press reset or power the board
void setup() {
  // initialize digital pin 13 as an output.
  pinMode(13, OUTPUT);
}

// the loop function runs over and over again forever
void loop() {
  digitalWrite(13, HIGH); // turn the LED on (HIGH is the voltage level)
  delay(1000);            // wait for a second
  digitalWrite(13, LOW);  // turn the LED off by making the voltage LOW
  delay(1000);            // wait for a second
}

No código de exemplo, o valor 13 refere-se ao pino de saída que aciona um LED. É possível observar também que esse valor é repetido em outras partes do programa.

Uma aplicação prática do #define é para reduzir a repetição de valores e assim evitar possíveis erros também. Neste caso é válido criar um #define que possui o valor 13, como abaixo:

#define LED_PIN     13
#define LED_ON      HIGH
#define LED_OFF     LOW
#define BLINK_DELAY 1000

// the setup function runs once when you press reset or power the board
void setup() {
  // initialize digital pin as an output.
  pinMode(LED_PIN, OUTPUT);
}

// the loop function runs over and over again forever
void loop() {
  digitalWrite(LED_PIN, LED_ON); // turn the LED on (HIGH is the voltage level)
  delay(BLINK_DELAY);
  digitalWrite(LED_PIN, LED_OFF); // turn the LED off by making the voltage LOW
  delay(BLINK_DELAY);
}

Ao processar o arquivo o identificador LED_PIN é substituído pelo seu valor, neste caso, o valor 13. Desse modo se o projeto é alterado de forma que o LED seja acionado por outro pino, bastaria alterar o valor da macro.

Observe também que é possível expandir macros em vários níveis como no caso da definição:

#define LED_ON HIGH

Nesse caso, LED_ON tem como valor a macro HIGH que, por sua vez, é definida como na biblioteca Arduino.h:

#define HIGH 0x1

As macros não ficam limitadas apenas em relacionar nomes e valores. A partir das macros também é possível definir trechos de código e até mesmo receber parâmetros.

Definindo macros com parâmetros – #define

Para explicar esse tópico vou utilizar o exemplo da rotina MAX. A rotina MAX é utilizada para definir o maior valor entre dois valores informados:

int maior, N1 = 5, N2 = 10;

if (A > B)
{
  maior = A;
}
else
{
  maior = B;
}

ou ainda, no modo if-inline: 

int maior, N1 = 5, N2 = 10;

maior = (N1 > N2) ? N1 : N2;

O trecho de código pode ser convertido em uma macro que recebe os dois valores:

#define MAX(A,B) ( (A) > (B) ? (A):(B))

E sua aplicação no código fica assim: 

int maior, N1 = 5, N2 = 10;

maior = MAX(N1,N2);

Por fazer simplesmente a substituição da macro pelo seu valor, alguns cuidados devem ser tomados. Convém observar que os parâmetros devem sempre ser utilizados entre parênteses. Considere que MAX tivesse sido definido como:

#define MAX(A,B) A > B ? A : B

e:

maior = MAX(N1+2,N2);

Durante a reposição da macro pelo seu valor, o código gerado seria:

maior = N1+2 > N2 ? N1 + 2 : N2;

Seria gerado o valor errado!

Definindo macros com várias linhas – #define

Para definir uma macro em mais de uma linha o símbolo ‘\’ (backslash) deve ser utilizado no final da linha. Esse símbolo índica que a definição da macro continua na próxima linha. 

#define ABORT(status)\
          printf("...");\
          exit(status);

Geralmente as macros com mais de uma instrução são criadas como blocos de código estruturados por um do {} while (0). Considere o exemplo abaixo. 

if (<condição>)
  ABORT(10);
else
{

}

O processo de compilação seria encerrado com a seguinte mensagem:

error: 'else' without a previous 'if'.

Isso ocorre porque a substituição da macro transforma o código sem utilizar os delimitadores de bloco {}. 

if (<condição>)
  printf("...");
  exit(status);
else
{

}

Assim uma macro que utiliza o formato do {} while (0) compilaria corretamente. 

#define ABORT(status) \
          do {\
            printf("...");\
            exit(status);\
          } while(0)

Removendo definições – #undef

Para remover uma definição é necessário utilizar a diretiva #undef. A remoção torna-se necessária para que seja possível definir uma outra macro com o mesmo identificador. 

#define LED_PIN 13

//…
//…

#undef LED_PIN

Operadores # e ##

O pré-processador C ainda inclui recursos para converter parâmetros da macro em uma string constante ou ainda realizar a concatenação de tokens.

O processo de conversão para uma string constante é chamado de Stringication e é realizado dentro de uma macro a partir do símbolo cerquilha (#). No exemplo abaixo a macro MSG_FORMAT converte o parâmetro type em uma string. 

#define MSG_FORMAT(type, arg) #type ": " arg
#define ERROR   1
#define WARNING 2
#define INFO    3

int main()
{
  // a string resultante será: "ERROR"": ""teste1"
  printf("%s\r\n", MSG_FORMAT(ERROR, "teste1"));
  // a string resultante será: "WARNING "": ""teste2"
  printf("%s\r\n", MSG_FORMAT(WARNING, "teste2"));
  // a string resultante será: "INFO "": ""teste3"

  printf("%s\r\n", MSG_FORMAT(INFO, "teste3"));

  return 0;
}

Já a concatenação é realizada a partir da sequência ##. No exemplo abaixo a macro MSG_TYPE é utilizada para definir os elementos de uma enumeração. O parâmetro type é concatenado no final do texto _MSG

#define ERROR   1
#define WARNING 2
#define INFO    3
#define MSG_TYPE(type) MSG_##type = type

typedef enum
{
  MSG_TYPE(ERROR),   //a conversão da macro resulta em MSG_ERROR = 1
  MSG_TYPE(WARNING), //a conversão da macro resulta em MSG_WARNING = 2
  MSG_TYPE(INFO),    //a conversão da macro resulta em MSG_INFO = 3
} MessageType;

Conclusão

Neste artigo foi apresentado de maneira introdutória alguns recursos do pré-processador C. Como se pode ver o pré-processador expande os recursos do ambiente de programação tornando-se uma ferramenta essencial para o desenvolvimento de programas em linguagem C.

Na segunda parte deste artigo serão apresentados os recursos do pré-processador que possibilitam realizar a compilação condicional de trechos de código.

Saiba mais: Série “Pré-processador C”

– Pré-processador C – Parte 1

Pré-processador C: Compilação condicional – Parte 2

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

– Pré-processador C: X macros – Parte 4

Referências

Livro: C, completo e total – 3ª edição revista e atualizada. Herbert Schildt.

https://www.arduino.cc/en/Reference/DigitalWrite

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

Pré-processador C

Pré-processador C: Compilação condicional – Parte 2
Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.
Comentários:
Notificações
Notificar
14 Comentários
recentes
antigos mais votados
Inline Feedbacks
View all comments
Rafael Dias
Rafael Dias
01/09/2015 13:04

muito bom!

Só tem um pequeno erro no começo:

“Toda diretiva é terminada ao final de cada linha, a não ser que a linha seja finalizada pelo símbolo backslash (/);”
o backslach é o “”.

Tanto que você o utiliza no decorrer do texto.

Fernando Deluno Garcia
Fernando Deluno Garcia
Reply to  Rafael Dias
01/09/2015 13:48

Olá, Rafael.

obrigado pela correção!

Leandro Poloni Dantas
31/08/2015 13:56

Fernando,
Muito bom seu artigo. Vou recomendá-lo para meus alunos.

Fernando Deluno Garcia
Fernando Deluno Garcia
Reply to  Leandro Poloni Dantas
01/09/2015 09:17

Olá, Leandro.

Fico feliz que tenha gostado. Espero que seja proveitoso para os seus alunos!

Obrigado pelo retorno!

Rafael
Rafael
31/08/2015 12:34

Muito bom o artigo Fernando.
São detalhes que fazem a diferença…

Sempre tive dificuldade em quando usar certas coisas, mas com esse artigo começa a clarear a aplicação de cada coisa…

Agradeço por compartilhar seu conhecimento, no aguardo do próximo

Fernando Deluno Garcia
Fernando Deluno Garcia
Reply to  Rafael
01/09/2015 09:13

Olá, Rafael.

Realmente os detalhes fazem a diferença. Muitas vezes apenas utilizamos algo mas não sabemos o significado. Nos próximos artigos vou mostrar alguns recursos que podem ajudar bastante no desenvolvimento de aplicações.

Obrigado pelo retorno!

Marcos
Marcos
31/08/2015 11:40

Simples e objetivo, muito bom

Fernando Deluno Garcia
Fernando Deluno Garcia
Reply to  Marcos
01/09/2015 09:08

Obrigado, Marcos!

Haroldo Amaral
Haroldo Amaral
27/08/2015 15:18

Excelente artigo Fernando.
Aguardando os próximos!

Fernando Deluno Garcia
Fernando Deluno Garcia
Reply to  Haroldo Amaral
28/08/2015 10:01

Olá, Haroldo.

Muito obrigado pelo retorno!

Lenio Rodrigues
Lenio Rodrigues
26/08/2015 13:39

Ótimo conteúdo caro Fernando ! no aguardo da segunda parte ! muito grato !

Fernando Deluno Garcia
Fernando Deluno Garcia
Reply to  Lenio Rodrigues
26/08/2015 16:48

Olá, Lenio.
Obrigado pelo retorno!

Fico feliz que tenha gostado.

Cesar Junior
Cesar Junior
26/08/2015 08:45

Parabéns pelo texto. No aguardo das outras partes.

Fernando Deluno Garcia
Fernando Deluno Garcia
Reply to  Cesar Junior
26/08/2015 16:46

Obrigado, Cesar.
Espero que goste dos próximos!

Home » Software » Linguagem C » Pré-processador C – Parte 1

EM DESTAQUE

WEBINARS

VEJA TAMBÉM

JUNTE-SE HOJE À COMUNIDADE EMBARCADOS

Talvez você goste:
Nenhum resultado encontrado.