As variáveis ​​globais são más no Arduino?

24

Sou relativamente novo em programação e muitas das práticas recomendadas de codificação que estou lendo efetivamente afirmam que existem muito poucas razões para usar uma variável global (ou que o melhor código não possui globais).

Eu fiz o meu melhor para manter isso em mente, ao escrever um software para criar uma interface do Arduino com um cartão SD, conversar com um computador e executar um controlador de motor.

Atualmente, tenho 46 globais para cerca de 1100 linhas de código "nível iniciante" (nenhuma linha com mais de uma ação). Essa é uma boa proporção ou devo reduzir mais? Além disso, que práticas posso usar para reduzir ainda mais o número de globais?

Estou perguntando isso aqui porque estou especificamente preocupado com as práticas recomendadas para codificação de produtos Arduino, em vez de programação de computadores em geral.

ATE-ENGE
fonte
2
No Arduino, você não pode evitar variáveis ​​globais. Toda declaração de variável fora do escopo de uma função / método é global. Portanto, se você precisar compartilhar valores entre funções, elas deverão ser globais, a menos que você queira passar todos os valores como argumento.
16
@LookAlterno Err, você não pode escrever aulas em Ardunio, já que é apenas C ++ com macros e bibliotecas estranhas? Nesse caso, nem todas as variáveis ​​são globais. E mesmo em C, geralmente é considerado uma boa prática preferir passar variáveis ​​(talvez dentro de estruturas) para funções em vez de ter variáveis ​​globais. Pode ser menos conveniente para um programa pequeno, mas geralmente compensa à medida que o programa se torna maior e mais complexo.
Muzer 11/07
11
@LookAlterno: "Eu evito" e "você não pode" são coisas muito diferentes.
Lightness Races com Monica
2
Na verdade, alguns programadores incorporados banem variáveis ​​locais e requerem variáveis ​​globais (ou variáveis ​​estáticas com escopo de função). Quando os programas são pequenos, pode ser mais fácil analisá-lo como uma simples máquina de estado; porque as variáveis ​​nunca se sobrescrevem (ou seja, como elas empilham e empilham variáveis ​​alocadas).
Rob
11
Variáveis ​​estáticas e globais oferecem a vantagem de conhecer o consumo de memória em tempo de compilação. Com um arduino com memória muito limitada disponível, isso pode ser uma vantagem. É muito fácil para um novato esgotar a memória disponível e experimentar falhas não rastreáveis.
Antipattern

Respostas:

33

Eles não são maus por si só, mas tendem a ser usados ​​em excesso onde não há boas razões para usá-los.

Muitas vezes as variáveis ​​globais são vantajosas. Especialmente porque a programação de um Arduino é muito diferente da programação de um PC.

O maior benefício para variáveis ​​globais é a alocação estática. Especialmente com variáveis ​​grandes e complexas, como instâncias de classe. A alocação dinâmica (o uso de newetc) é desaprovada devido à falta de recursos.

Além disso, você não recebe uma única árvore de chamada como em um programa C normal ( main()função única chamando outras funções) - em vez disso, você efetivamente recebe duas árvores separadas ( setup()chamando funções e depois loop()chamando funções), o que significa que às vezes variáveis ​​globais são as única maneira de atingir seu objetivo (ou seja, se você quiser usá-lo em ambos setup()e loop()).

Portanto, não, eles não são maus, e em um Arduino eles têm mais e melhores usos do que em um PC.

Majenko
fonte
Ok, e se for algo que eu estou usando apenas loop()(ou em várias funções chamadas loop())? seria melhor configurá-los de uma maneira diferente do que defini-los no início?
ATE-ENGE
11
Nesse caso, eu provavelmente os definiria em loop () (talvez como staticse precisasse deles para manter seu valor entre as iterações) e os passaria pelos parâmetros de função na cadeia de chamadas.
Majenko
2
Essa é uma maneira, ou a passe como referência: void foo(int &var) { var = 4; }e foo(n);- nagora é 4.
Majenko
11
As classes podem ser alocadas à pilha. É certo que não é bom colocar grandes instâncias na pilha, mas ainda assim.
JAB
11
@JAB Qualquer coisa pode ser alocada por pilha.
Majenko
18

É muito difícil dar uma resposta definitiva sem ver seu código real.

Variáveis ​​globais não são ruins e geralmente fazem sentido em um ambiente incorporado, onde você normalmente faz muito acesso a hardware. Você tem apenas quatro UARTS, apenas uma porta I2C, etc. Portanto, faz sentido usar globais para variáveis ​​vinculadas a recursos de hardware específicos. E, de fato, a biblioteca núcleo Arduino faz isso: Serial, Serial1, etc, são variáveis globais. Além disso, uma variável que representa o estado global do programa geralmente é global.

Atualmente, tenho 46 globais para cerca de 1100 linhas de [código]. Esta é uma boa proporção [...]

Não é sobre os números. A pergunta certa que você deve se perguntar é, para cada um desses globais, se faz sentido tê-lo no escopo global.

Ainda assim, 46 globais parecem um pouco altos para mim. Se alguns deles mantiverem valores constantes, qualifique-os como const: o compilador geralmente otimizará seu armazenamento. Se alguma dessas variáveis ​​for usada apenas em uma única função, torne-a local. Se você deseja que seu valor persista entre as chamadas para a função, qualifique-o como static. Você também pode reduzir o número de globais "visíveis" agrupando variáveis ​​em uma classe e tendo uma instância global dessa classe. Mas faça isso apenas quando fizer sentido juntar as coisas. Fazer uma GlobalStuffclasse grande por ter apenas uma variável global não ajudará a tornar seu código mais claro.

Edgar Bonet
fonte
11
Está bem! Eu não sabia staticse tenho uma variável que é usada apenas em uma função, mas recebo um novo valor toda vez que uma função é chamada (como var=millis()), devo fazer isso static?
ATE-ENGE
3
Não. staticÉ apenas para quando você precisa manter o valor. Se a primeira coisa que você faz na função é definir o valor da variável para o horário atual, não há necessidade de manter o valor antigo. Tudo o que a estática faz nessa situação está desperdiçando memória.
Andrew
Essa é uma resposta muito abrangente, expressando os dois pontos a favor e o ceticismo em relação aos globais. +1
underscore_d
6

O principal problema com variáveis ​​globais é a manutenção de código. Ao ler uma linha de código, é fácil encontrar a declaração de variáveis ​​passadas como parâmetro ou declaradas localmente. Não é tão fácil encontrar uma declaração de variáveis ​​globais (geralmente requer e IDE).

Quando você tem muitas variáveis ​​globais (40 já são muitas), fica difícil ter um nome explícito que não seja muito longo. Usar o espaço para nome é uma maneira de esclarecer o papel das variáveis ​​globais.

Uma maneira ruim de imitar namespaces em C é:

static struct {
    int motor1, motor2;
    bool sensor;
} arm;

No processador intel ou arm, o acesso a variáveis ​​globais é mais lento que outras variáveis. Provavelmente é o oposto no arduino.

BOC
fonte
2
No AVR, os globais estão na RAM, enquanto a maioria dos locais não estáticos é alocada nos registros da CPU, o que torna seu acesso mais rápido.
Edgar Bonet
11
O problema não é realmente encontrar a declaração; ser capaz de entender rapidamente o código é importante, mas, em última análise, secundário ao que ele faz - e aí o verdadeiro problema é descobrir qual varmint em qual parte desconhecida do seu código é capaz de usar a declaração global para fazer coisas estranhas com um objeto que você deixou de fora a céu aberto para que todos possam fazer o que quiserem.
Underscore_d
5

Embora eu não os usasse na programação para um PC, para o Arduino eles têm alguns benefícios. Mais se já foi dito:

  • Sem uso de memória dinâmica (criando lacunas no espaço de pilha limitado de um Arduino)
  • Disponível em qualquer lugar, portanto, não há necessidade de passá-los como argumentos (que custa espaço na pilha)

Além disso, em alguns casos, especialmente em termos de desempenho, pode ser bom usar variáveis ​​globais, em vez de criar elementos quando necessário:

  • Para diminuir as lacunas no espaço de heap
  • Alocar e / ou liberar dinamicamente memória pode levar um tempo substancial
  • As variáveis ​​podem ser 'reutilizadas', como uma lista de elementos de uma lista global que são usados ​​por vários motivos, por exemplo, uma vez como um buffer para o SD e mais tarde como um buffer temporário de strings.
Michel Keijzers
fonte
5

Como em tudo (exceto os gotos que são verdadeiramente maus), os globais têm seu lugar.

por exemplo, se você possui uma porta serial de depuração ou um arquivo de log no qual precisa gravar de qualquer lugar, geralmente faz sentido torná-lo global. Da mesma forma, se você tiver algumas informações críticas sobre o status do sistema, torná-las globais geralmente é a solução mais fácil. Não faz sentido ter um valor que você precisa passar para todas as funções do programa.

Como outros já disseram, o 46 parece muito por apenas mais de 1000 linhas de código, mas sem saber os detalhes do que você está fazendo, é difícil dizer se você os está usando ou não.

No entanto, para cada global, faça a si mesmo algumas perguntas importantes:

O nome é claro e específico para que eu não tente usar acidentalmente o mesmo nome em outro lugar? Caso contrário, mude o nome.

Isso precisa mudar sempre? Caso contrário, considere torná-lo uma const.

Isso precisa estar visível em qualquer lugar ou é apenas global para que o valor seja mantido entre as chamadas de função? Portanto, considere torná-lo local para a função e usar a palavra-chave estática.

As coisas realmente vão estragar muito se isso for alterado por um pedaço de código quando eu não estiver sendo cuidadoso? por exemplo, se você tiver duas variáveis ​​relacionadas, como nome e número de identificação, que são globais (consulte a observação anterior sobre o uso global quando você precisar de informações em quase todos os lugares), se uma delas for alterada sem que outras coisas desagradáveis ​​possam acontecer. Sim, você pode ter cuidado, mas às vezes é bom impor um pouco de cuidado. por exemplo, coloque-os em um arquivo .c diferente e, em seguida, defina funções que definem os dois ao mesmo tempo e permitem que você os leia. Você então inclui apenas as funções no arquivo de cabeçalho associado, para que o restante do seu código possa acessar apenas as variáveis ​​através das funções definidas e, portanto, não possa atrapalhar as coisas.

- update - Acabei de perceber que você havia perguntado sobre as melhores práticas específicas do Arduino, em vez da codificação geral, e isso é mais uma resposta de codificação geral. Mas, honestamente, não há muita diferença, boas práticas são boas práticas. A estrutura startup()e loop()do Arduino significa que você precisa usar globals um pouco mais do que outras plataformas em algumas situações, mas isso não muda muito, você sempre acaba buscando o melhor que pode fazer dentro das limitações da plataforma, não importa o que a plataforma é.

Andrew
fonte
Não sei nada sobre o Arduinos, mas desenvolvo muito o desenvolvimento de desktops e servidores. Existe um uso aceitável (IMHO) para se gotoé romper os loops aninhados, é muito mais limpo e fácil de entender do que as alternativas.
Persistence
Se você acha que gotoé mau, confira o código do Linux. Indiscutivelmente, eles são tão maus quanto try...catchblocos quando usados ​​como tais.
Dmitry Grigoryev
5

Eles são maus? Talvez. O problema com os globais é que eles podem ser acessados ​​e modificados a qualquer momento, por qualquer função ou parte do código sendo executada, sem restrições. Isso pode levar a situações difíceis, digamos, de rastrear e explicar. Minimizar a quantidade de globais, se possível, trazer a quantidade de volta a zero, é, portanto, desejável.

Eles podem ser evitados? Quase sempre sim. O problema com o Arduino é que eles o forçam a essa abordagem de duas funções, na qual eles assumem você setup()e você loop(). Nesse caso específico, você não tem acesso ao escopo da função de chamada dessas duas funções (provavelmente main()). Se você tivesse, seria capaz de se livrar de todos os globais e usar os habitantes locais.

Imagine o seguinte:

int main() {
  setup();

  while (true) {
    loop();
  }
  return 0;
}

Provavelmente, isso é mais ou menos o que parece a principal função de um programa Arduino. As variáveis ​​necessárias na função setup()e na loop()função seriam preferencialmente declaradas dentro do escopo da main()função e não no escopo global. Eles poderiam então ser disponibilizados para as outras duas funções, passando-os como argumentos (usando ponteiros, se necessário).

Por exemplo:

int main() {
  int myVariable = 0;
  setup(&myVariable);

  while (true) {
    loop(&myVariable);
  }
  return 0;
}

Observe que, nesse caso, você também precisa alterar a assinatura de ambas as funções.

Como isso pode não ser viável nem desejável, vejo realmente apenas uma maneira de remover a maioria dos globais de um programa Arduino sem modificar a estrutura forçada do programa.

Se bem me lembro, você é perfeitamente capaz de usar C ++ durante a programação do Arduino, em vez de C. Se você ainda não está familiarizado com OOP (Programação Orientada a Objetos) ou C ++, pode levar algum tempo para se acostumar. leitura.

Minha proposta seria criar uma classe de programa e criar uma única instância global dessa classe. Uma classe deve ser considerada o blueprint para objetos.

Considere o seguinte programa de exemplo:

class Program {
public:      
  Program();

  void setup();
  void loop();

private:
  int myFirstSampleVariable;
  int mySecondSampleVariable;
};

Program::Program() :
  myFirstSampleVariable(0),
  mySecondSampleVariable(0)
{

}

void Program::setup() {
  // your setup code goes here
}

void Program::loop() {
  // your loop code goes here
}

Program program; // your single global

void setup() {
  program.setup();
}

void loop() {
  program.loop();
}

Voilà, nos livramos de quase todos os globais. As funções nas quais você começaria a adicionar a lógica do aplicativo seriam as funções Program::setup()e Program::loop(). Essas funções têm acesso às variáveis ​​de membro específicas da instância myFirstSampleVariablee, mySecondSampleVariableenquanto as funções setup()e tradicionais loop()não têm acesso, pois essas variáveis ​​foram marcadas como classe privada. Esse conceito é chamado de encapsulamento ou ocultação de dados.

Ensinar a você OOP e / ou C ++ está um pouco fora do escopo da resposta a esta pergunta, então vou parar por aqui.

Resumindo: os globais devem ser evitados e quase sempre é possível reduzir drasticamente a quantidade de globais. Além disso, quando você está programando para o Arduino.

Mais importante ainda, espero que minha resposta seja um pouco útil para você :)

Arjen
fonte
Você pode definir seu próprio main () no esboço, se preferir. Isto é o que as ações se parece: github.com/arduino/Arduino/blob/1.8.3/hardware/arduino/avr/...
per1234
Um singleton é efetivamente global, apenas vestido de uma maneira extra confusa. Tem as mesmas desvantagens.
31817 Pattwws:
@patstew Você se importa em me explicar como sente que tem as mesmas desvantagens? Na minha opinião, não é possível, pois você pode usar o encapsulamento de dados em seu benefício.
Arjen
@ per1234 Obrigado! Definitivamente, não sou especialista em Arduino, mas suponho que minha primeira sugestão também funcione.
Arjen
2
Bem, ainda é um estado global que pode ser acessado em qualquer lugar do programa, basta acessá-lo através do Program::instance().setup()invés de globalProgram.setup(). Colocar variáveis ​​globais relacionadas em uma classe / struct / namespace pode ser benéfico, especialmente se elas forem necessárias apenas para algumas funções relacionadas, mas o padrão singleton não adiciona nada. Em outras palavras, static Program p;possui armazenamento global e static Program& instance()acesso global, o que equivale a simplesmente Program globalProgram;.
patstew
4

Variáveis ​​globais nunca são más . Uma regra geral contra eles é apenas uma muleta para permitir que você sobreviva o tempo suficiente para ganhar experiência e tomar melhores decisões.

O que é uma variável global é uma suposição inerente de que existe apenas uma coisa (não importa se estamos falando de uma matriz ou mapa global que pode conter várias coisas, que ainda contém a suposição de que há apenas uma lista ou mapeamento, e não várias listas independentes).

Portanto, antes de usar um global, você quer se perguntar: é concebível que eu queira usar mais de uma dessas coisas? Se isso acontecer, você terá que modificar o código para não globalizar essa coisa e provavelmente descobrirá ao longo do caminho que outras partes do seu código dependem dessa suposição de exclusividade. terá que corrigi-los também, e o processo se torna tedioso e propenso a erros. O "não use globais" é ensinado porque geralmente é um custo muito pequeno evitar os globais desde o início e evita o potencial de ter que pagar um grande custo mais tarde.

Mas as suposições simplificadoras permitidas pelas globais também tornam seu código menor, mais rápido e usam menos memória, porque ele não precisa passar uma noção de qual coisa está usando, não precisa ser indireto, não precisa considere a possibilidade da coisa desejada talvez não exista, etc. No incorporado, é mais provável que você fique restrito ao tamanho do código e / ou tempo e / ou CPU de memória do que em um PC, portanto essas economias podem importar. E muitos aplicativos incorporados também têm mais rigidez nos requisitos - você sabe que seu chip possui apenas um de um periférico, o usuário não pode simplesmente conectar outro a uma porta USB ou algo assim.

Outro motivo comum para querer mais do que algo que parece único é o teste - testar a interação entre dois componentes é mais fácil quando você pode simplesmente passar uma instância de teste de algum componente para uma função, enquanto tentar modificar o comportamento de um componente global é uma proposição mais complicada. Mas os testes no mundo incorporado tendem a ser muito diferentes de outros lugares, portanto, isso pode não se aplicar a você. Até onde eu sei, o Arduino não tem nenhuma cultura de teste.

Então vá em frente e use globais quando parecerem valiosos. A polícia de código não irá buscá-lo. Saiba que a escolha errada pode levar a muito mais trabalho para você no caminho, por isso, se você não tiver certeza ...

hobbs
fonte
0

As variáveis ​​globais são más no Arduino?

nada é inerentemente mau, incluindo variáveis ​​globais. Eu o caracterizaria como um "mal necessário" - isso pode facilitar sua vida, mas deve ser abordada com cautela.

Além disso, que práticas posso usar para reduzir ainda mais o número de globais?

use funções de wrapper para acessar suas variáveis ​​globais. pelo menos você o está gerenciando da perspectiva do escopo.

dannyf
fonte
3
Se você usar funções de wrapper para acessar variáveis ​​globais, também poderá colocar suas variáveis ​​dentro dessas funções.
Dmitry Grigoryev