Em um artigo publicado em 1973 por Wulf e Shaw [1] foi descrito que o uso indiscriminado de variáveis globais é prejudicial. O problema independe da linguagem de programação: as variáveis globais são danosas desde a linguagem de baixo nível até as mais altas. E os mais jovens acabam aprendendo sobre isso quando entram em contato com comunidades de programação, livro do Jack Ganslee [2] ou errando nos projetos da empresa. Em 2011 houveram problemas com a Toyota Camry, e foi apontado que uma das causas era o uso de 11.000 variáveis globais [3] [4].
Mas qual é exatamente o problema das variáveis globais? Para quem já trabalhou em algum projeto sabe que é fácil chegar a mil ou duas mil linhas de código, ficando pouco a pouco mais difícil de cumprir o fluxograma inicial ou diagrama de estados planejado. Uma das razões é devido ao fato de várias funções e interrupções terem livre acesso às variáveis globais, gerando estados não existentes ao inicialmente planejado.
Para quem viveu numa república durante a faculdade vai entender com o seguinte exemplo: todos têm acesso à geladeira, certo? Você chega de noite, coloca o pudim que comprou dentro da geladeira, e espera o dia seguinte para comê-lo depois do almoço. E o que acontece? O seu “irmão” come o pudim antes porque teve livre acesso a ele. E ainda é provável de você ser chamado de “o vacilão”.
Uma das formas de evitar as variáveis globais é logicamente utilizar as variáveis locais e implementar funções com passagem de valor. Veja o exemplo abaixo, adaptado para o caso do pudim na república:
#include <stdio.h>
// Prototype of Paula’s function.
int paulaEatsPudding(int comida);
int main()
{
// There is a pudding on refrigerator!
int pudding = 1;
pudding = paulaEatsPudding(pudding);
if( pudding == 0 )
printf(“The pudding has been eaten by Paula!”);
// The pudding has been eaten succesfully!
while(1);
}
// Paula’s function to eat pudding.
int paulaEatsPudding(int food)
{
// Eats a piece of food.
food = food – 1;
// Return it to refrigerator, if something remained.
return food;
}
A variável pudding é vista somente dentro de main() e a função paulaEatsPudding() tem acesso à variável. Você pode prever o que vai acontecer com a variável de acordo com a ordem de execução ao estilo polling no main(), porém ao implementar as interrupções no código, o programador começa a coçar a cabeça, uma vez que não é possível fazer passagem de valor às interrupções.
Isso seria a mesma coisa que, para proteger o pudim, a Paula tivesse que esperar em frente a geladeira até a hora que ficar com fome, vigiando se alguém não vá pegar antes dela. Somando a isso, e se um colega de quarto dela pedisse um pedaço também?
Na próxima parte irei mostrar uma solução utilizando-se de modificadores de acesso e flags na linguagem C.
“Bilhete” para limitar o acesso e “sinalizações” para avisar quem foi que comeu
Agora vamos imaginar a seguinte situação: a Paula comprou o pudim (pudding), guardou dentro da geladeira (main.c) e foi para o seu quarto (room.c). Mas a Diana, a sua colega de quarto e melhor amiga, viu o pudim e perguntou para a Paula se poderia comer também, quando estivesse com fome. A Paula, muito boazinha, deixou. Vamos interpretar a fome das duas como se fossem dois bits de uma interrupção de fome (interrupt_Hunger() de room.c). O código ficaria da seguinte maneira:
Arquivo main.c:
#include <stdio.h>
#include <stdbool.h>
#include "room.h"
// Declaring variable visible on refrigerator.
static int pudding = 1;
// There is a Raspberry Pi inside refrigerator that monitors who opened it.
static bool someoneAte = false;
static bool itWasPaula = false;
static bool itWasDiana = false;
int main()
{
allowed_Roommates(&someoneAte, &itWasPaula, &itWasDiana);
enable_Hunger();
while(1)
{
if((someoneAte == true) && (pudding != 0))
{
// Paula comes first because she bought pudding.
if(itWasPaula == true)
{
printf(“Paula ate the pudding!”);
pudding = eatTheFood(pudding);
// Resets the flag.
itWasPaula = false;
}
// Diana comes after, and will eat pudding if still exists.
if((itWasDiana == true) && (pudding != 0))
{
printf(“Diana ate the pudding!”);
pudding = eatTheFood(pudding);
// Resets the flag.
itWasDiana = false;
}
// Resets the flag and clear/enable interrupt.
someoneAte = false;
enable_Hunger();
}
}
}
Arquivo room.h:
// Flags for Paula and Diana hunger interrupt.
#define PAULA 0b00000001
#define DIANA 0b00000010
// Prototypes for the APIs.
extern void allowed_Roommates(bool *addr_smnHngy,
bool *addr_indiv1, bool *addr_indiv2);
extern void enable_Hunger(void);
extern int eatTheFood(int food);
extern void interrupt_Hunger(void);
Arquivo room.c:
#include <stdio.h>
#include <stdbool.h>
#include <hunger.h>
#include "room.h"
// Variable to get interrupt status.
static int hungerStatus;
// Pointers to variables on "main.c" archive.
static bool *someoneIsHungry;
static bool *person1, *person2;
// Sets the pointers for variables on "main.c".
void allowed_Roommates(bool *addr_smnHngy, bool *addr_indiv1, bool *addr_indiv2)
{
// Keeps the address of the hunger interrupt flag.
someoneIsHungry = addr_smnHngy;
*someoneIsHungry = false;
// Keeps the address of the person and attributes “false” indicating that it
// didn’t eat the food yet.
person1 = addr_indiv1;
*person1 = false;
person2 = addr_indiv2;
*person2 = false;
}
// Clears any pending interrupt and enables the hunger.
void enable_Hunger(void)
{
// Functions from “hunger.h” library.
HUNGERClear();
HUNGEREnable();
}
// Function to eat attributed food.
int eatTheFood(int food)
{
// Eats a piece of food.
food = food - 1;
// Return it to refrigerator, if something remained.
return food;
}
// Interrupt that occurs when someone is hungry.
void interrupt_Hunger(void)
{
// Gets interrupt status.
hungerStatus = HUNGERIntStatus()
// Attribute "true" to occur processing on "main()".
*someoneIsHungry = true;
if(hungerStatus & PAULA) *person1 = true;
if(hungerStatus & DIANA) *person2 = true;
}
O modificador static torna a variável global pudding visível somente no arquivo em que é declarado (main.c), e na república ficou estabelecido que somente as duas teriam acesso ao pudim (linha 15 do main.c). Elas podem ficar no quarto delas (room.c) até a hora em que a fome bater (interrupt_Hunger()).
As interrupções somente avisam que algo aconteceu, mas não fazem o processamento complexo de alguma variável. No exemplo, utilizamos as interrupções para manipular somente as flags/sinalizações, para que o processamento dos dados principais (pudding = eatTheFood(pudding);) seja feito no loop do main(). Isso de certa forma evita a descaracterização do fluxo lógico ou a criação de um estado inexistente na máquina de estados, como também segue o princípio de responsabilidade única.
Note também que no interrupt_Hunger() do arquivo room.c não foi usado a comparação “==”, mas sim o operador binário “&”, pois se as duas interrupções ocorressem simultaneamente, na comparação “==” os dois casos if iriam ser falsas. Porém, utilizando o operador binário “&” será verdadeiro nos dois casos. Veja o artigo do Fábio Souza a respeito de manipulação de bits. No loop do main() coloquei o caso da Paula primeiro, pois caso as duas interrupções ocorram, ela irá comer primeiro (porque foi ela quem comprou o pudim), e a Diana comerá se sobrar algum pedaço (o que não vai acontecer no exemplo pois só há um pedaço).
Caso as duas interrupções viessem de registradores diferentes, seria necessário configurar a prioridade das interrupções para não haver conflito de simultaneidade: Caso duas ou mais interrupções ocorram, a de maior prioridade é executada antes e a de menor depois. Se as duas estivessem com fome simultaneamente, a Paula teria a prioridade e comeria o pudim. No caso de firmware multithreads deve-se usar a exclusão mútua (Mutex) para evitar que duas ou mais threads modifiquem uma variável ao mesmo tempo. O Felipe Neves já escreveu um artigo muito legal tratando sobre isso.
Tecnicamente falando, a variável pudding poderia estar dentro do main() sendo uma variável local sem o static. A diferença entre a variável local e a static é que a primeira é apagada do stack da memória RAM quando a função que a usa finaliza o seu serviço. Mas como a variável local estará dentro de main(), permanecerá existindo. No artigo do Fernando é explicado mais detalhadamente sobre o modificador static e mostra um uso alternativo nas funções.
“Uso medicinal” das variáveis globais
Praticamente em duas situações você pode usar as variáveis globais:
- Quando é uma constante: Bits de configuração de registrador, constantes matemáticas como π, valores de referências fixas que nunca mudam. É aconselhável utilizar modificador const ou macros #define por segurança. Fernando Deluno Garcia fez alguns artigos relacionados a isso em linguagem C [9] [10]. Você pode deixar essas constantes listadas em um arquivo header para não poluir visualmente o arquivo principal.
- Valores de referência para todo o programa: Por exemplo, velocidade de um motor que deve servir de referência para muitas funções e módulos. Essas variáveis não podem ser duplicadas e deve ser utilizado o modificador volatile para que o valor se propague pelo fluxo lógico.
Conclusão
Gostaria de deixar claro que esses exemplos não são métodos infalíveis de se evitar bugs no código. Um profissional da área pode perceber falhas nos exemplos acima, mas como é um exemplo generalista, não quis entrar em detalhes para não deixar mais enfadonho para o estudante que está vendo isso pela primeira vez. O ponto principal do artigo é: reduzindo o escopo das variáveis torna o código mais seguro a bugs. A solução básica é utilizando variáveis locais e funções com passagem de valor. Eu quis também mostrar uma solução alternativa para quando conceitos como interrupção e modularização entram em jogo. Por se tratar de uma solução que exige mais da memória, o programador deve tomar cuidado e alocar corretamente o tamanho stack e heap da RAM.
Espero que tenham gostado do artigo, e se você conhece alguma técnica alternativa para evitar as variáveis globais, escreva nos comentários! Deem uma olhada nas referências abaixo, principalmente nos artigos do Embarcados, pois temos bastante material de excelente qualidade, ensinando a escrever códigos profissionais.
Referências
[1] WULF, W.; SHAW, M.; GLOBAL VARIABLE CONSIDERED HARMFUL, Carnegie-Mellon University, Pittsburg, Fev. 1973.
[2] Ganssle, J.; The Art of Designing Embedded Systems, Newnes, 2000.
[3] ”Toyota Unintended Acceleration and the Big Bowl of “Spaghetti” Code” por Safety Research & Strategies INC.
[4] “Editorial: Custo do firmware” por Equipe Embarcados
[5] “Princípio da Responsabilidade Única em Firmwares” por Felipe Lavratti
[6] “Bits em Linguagem C – Conceito e Aplicação” por Fábio Souza
[7] “Como utilizar os semáforos para compartilhar recursos no mbed OS” por Felipe Neves
[8] “Como utilizar os semáforos para compartilhar recursos no mbed OS” por Fernando Deluno Garcia
[9] “Modificadores de Acesso na Linguagem C” por Fernando Deluno Garcia
[10] “Pré-processador C – Parte 1” por Fernando Deluno Garcia
[11] “Better Embedded System SW – Minimize Use of Global Variables” por Phil Koopman
[12] “Ponteiro em C: Definição” por Fernando Deluno Garcia
[13] “Programação Modular em C” por Fernando Deluno Garcia
[14] “Arquitetura de desenvolvimento de software – parte I” por Rodrigo Almeida
[15] “Máquina de estado” por Pedro Bertoleti
[16] “Regras do Contexto de Interrupção em Kernel Space” por Felipe Lavratti











