Arduino: const vs #define

Longe vão os tempos em que os computadores possuíam quantidades irrisórias de memória RAM quando comparadas com os dias de hoje. Falo sim em quantidades na ordem dos kbytes de memória.

Actualmente os programadores descuram um pouco essa afinação e optimização na alocação de memória das suas aplicações, mas ainda existe um pequeno grupo onde ainda é necessário optimizar ao pormenor a alocação de memória, refiro-me então às áreas da robótica e electrónica, de um modo geral na utilização de micro-controladores.

#define

De uma forma simples podemos dizer que o #define é uma directiva do pré-processador do compilador que de certo modo torna mais simples a definição de uma variável estática que será usada múltiplas vezes, ou seja, no caso de ser necessário alterar o valor dessa mesma variável estática basta mudar o valor do #define ao invés de andar a alterar todas as variáveis ao longo do código.

Por exemplo:

#define Pin 10

void setup()
{
  pinMode(Pin, OUTPUT);
}

void loop() 
{
  digitalWrite(Pin, HIGH);
  delay(500);
  digitalWrite(Pin, LOW); 
  delay(500);
}

O que realmente acontece ao compilar o código acima é o seguinte:

#define Pin 10

void setup() 
{
  pinMode(10, OUTPUT);
}

void loop() 
{
  digitalWrite(10, HIGH);
  delay(500);
  digitalWrite(10, LOW); 
  delay(500);
}

A utilização do #define é uma funcionalidade muito útil e poderosa, não havendo na realidade perdas de performance quando comparado com a definição de uma variável do tipo inteiro para definir o Pin.

Compilando o programa usando o #define obtenho:

Arduino: usando o define

#const

De uma forma geral o que o modificador #const faz é “dizer” ao compilador que a variável (ou ponteiro) não pode ser alterado no decorrer do código. No entanto continua a ser uma variável e dependendo de onde seja usada pode ou não consumir memória RAM.

Mas como o IDE usado pelo Arduino e seu compilador avr-gcc é inteligente o suficiente para saber que uma variável precedida pelo modificador #const não pode ser alterada dentro do programa activo este irá tentar deixá-la fora da memória RAM.

Pegando do exemplo dado anteriormente e usando o modificador #const o código ficaria assim:

#const int Pin = 10;

void setup()
{
  pinMode(Pin, OUTPUT);
}

void loop() 
{
  digitalWrite(Pin, HIGH);
  delay(500);
  digitalWrite(Pin, LOW); 
  delay(500);
}

O que realmente acontece ao compilar o código acima é o seguinte:

#const int Pin = 10;

void setup()
{
  pinMode(10, OUTPUT);
}

void loop() 
{
  digitalWrite(Pin, HIGH);
  delay(500);
  digitalWrite(Pin, LOW); 
  delay(500);
}

Mas será que a utilização do modificador #const irá consumir mais ou menos memória RAM que o modificador #define?

 Compilando o programa usando o #const obtenho:

Arduino: usando const

O que é certo é que nenhum dos casos consome qualquer memória RAM, mas não se deixem enganar pelo valor Binary sketch size: 1.076 bytes apresentado pelo IDE do Arduino, pois esse valor refere-se ao HEX file do código e não tem uma relação directa com a memória RAM utilizada na realidade.

Para averiguarmos com exactidão a quantidade de memória RAM ocupada podemos usar a ferramenta AVR-SIZE disponibilizada pelo IDE do Arduino.

AVR-SIZE

A ferramenta avr-size irá dizer-nos realmente a quantidade de memória RAM estática que o programa está a usar.

O processo de avaliar a quantidade de memória RAM estática utilizada remete-nos para o uso da consola, mas primariamente temos de navegar até a pasta C:\Program Files (x86)\Arduino\hardware\tools\avr\bin e encontrar o avr-size.

De seguida vamos navegar até ao ficheiro C:\Users[Nome_utilizador]\AppData\Local\Temp\build[...][nome_Sketch].cpp.elf.

E de seguida vamos correr o avr-size com o ficheiro nome_Sketch.cpp.elf e verificar o resultado.

 O avr-size irá retornar as seguintes informações:

  • text – Não é mais que a memória flash usada para o código
  • data – Representa a memória RAM estática realmente usada pelas variáveis inicializadas pelo programa
  • bss – Representa a memória RAM estática realmente usada pelas variáveis inicializadas a zero pelo programa
  • dec & hex – Representa o somatório da memória RAM e flash utilizadas pelo programa

Usando #define:

Arduino: memória usando define

Usando #const: 

Arduino: memória usando const

Como podemos verificar o valor apresentado no campo data que representa a memória RAM estática realmente usada pelas variáveis inicializadas pelo programa é 0 (zero), logo podemos concluir que ambos a utilização de ambos os modificadores resulta na utilização da mesma quantidade de memória RAM estática.

O que usar? #define ou #const?

A real questão que se coloca é qual dos modificadores deveremos usar, e é seguramente a questão mais complicada de responder, pois ambas as opções são válidas e ambas ocupam o mesmo espaço de memória RAM estática no decorrer do programa.

Existe quem considere a utilização do #define como uma má prática de programação pelo facto de estar mais propícia a erros de codificação, como por exemplo no pedaço de código seguinte:

Código correcto:

#define Pin 10

void setup()
{
  pinMode(Pin, OUTPUT);
}

void loop() 
{
  digitalWrite(Pin, HIGH);
  delay(500);
  digitalWrite(Pin, LOW); 
  delay(500);
}

Código com erro:

#define Pin 10

void setup()
{
  pinMode(3;, OUTPUT);
}

void loop() 
{
  digitalWrite(Pin, HIGH);
  delay(500);
  digitalWrite(Pin, LOW); 
  delay(500);
}

Quando tentássemos compilar o pedaço de código com erro obteríamos um erro do tipo sketch_feb08a:6: error: expected `;’ before ‘)’ token por parte do compilador.

Conclusão

A principal conclusão que podemos retirar é de que desde que o compilador avr-gcc é o suficiente inteligente para manter uma variável precedida pelo modificador #const fora da memória RAM estática do programa a sua utilização poderá ser uma vantagem. No entanto a “substituição” da variável precedida pelo modificador #define cumpre igualmente a sua função mas está mais propícia à ocorrência de erros de codificação.

Publicado na edição 44 (PDF) da Revista PROGRAMAR.