Uso de variáveis ​​globais em sistemas embarcados

17

Comecei a escrever o firmware do meu produto e sou um novato aqui. Eu passei por muitos artigos sobre não usar variáveis ​​ou funções globais. Existe algum limite para o uso de variáveis ​​globais em um sistema de 8 bits ou é um 'Não-Não' completo. Como devo usar variáveis ​​globais no meu sistema ou devo evitá-las completamente?

Gostaria de receber conselhos valiosos de vocês sobre este tópico para tornar meu firmware mais compacto.

Rookie91
fonte
Esta pergunta não é exclusiva para sistemas embarcados. Uma duplicata pode ser encontrada aqui .
Lundin
@Lundin Do seu link: "Hoje em dia isso só importa em ambientes incorporados, onde a memória é bastante limitada. Algo a saber antes de você assumir que o incorporado é o mesmo que outros ambientes e assumir que as regras de programação são as mesmas em todos os aspectos".
endolith 02/02
O staticescopo do arquivo @endolith não é o mesmo que "global", veja minha resposta abaixo.
Lundin

Respostas:

31

Você pode usar variáveis ​​globais com êxito, desde que tenha em mente as diretrizes do @ Phil. No entanto, aqui estão algumas maneiras legais de evitar seus problemas sem tornar o código compilado menos compacto.

  1. Use variáveis ​​estáticas locais para o estado persistente que você deseja acessar apenas dentro de uma função.

    #include <stdint.h>
    void skipper()
    {
        static uint8_t skip_initial_cycles = 5;
        if (skip_initial_cycles > 0) {
            skip_initial_cycles -= 1;
            return;
        }
        /* ... */
    }
  2. Use uma estrutura para manter as variáveis ​​relacionadas juntas, para tornar mais claro onde elas devem ser usadas e onde não.

    struct machine_state {
         uint8_t level;
         uint8_t error_code;
    } machine_state;
    
    struct led_state {
        uint8_t red;
        uint8_t green;
        uint8_t blue;
    } led_state;
    
    void machine_change_state()
    {
        machine_state.level += 1;
        /* ... */
        /* We can easily remember not to use led_state in this function. */
    }
    
    void machine_set_io()
    {
        switch (machine_state.level) {
        case 1:
            PIN_MACHINE_IO_A = 1;
            /* ... */
        }
    }
  3. Use variáveis ​​estáticas globais para tornar as variáveis ​​visíveis apenas no arquivo C atual. Isso evita o acesso acidental por código em outros arquivos devido a conflitos de nomes.

    /* time_machine.c */
    static uint8_t current_time;
    /* ... */
    
    /* delay.c */
    static uint8_t current_time; /* A completely separate variable for this C file only. */
    /* ... */

Como nota final, se você estiver modificando uma variável global dentro de uma rotina de interrupção e lendo-a em outro lugar:

  • Marque a variável volatile.
  • Verifique se ele é atômico para a CPU (ou seja, 8 bits para uma CPU de 8 bits).

OU

  • Use um mecanismo de bloqueio para proteger o acesso à variável.
richarddonkin
fonte
vars voláteis e / ou atômicos não ajudarão a evitar erros, você precisará de algum tipo de bloqueio / semáforo ou para mascarar brevemente as interrupções ao escrever na variável.
você
3
Essa é uma definição bastante restrita de "funciona bem". Meu argumento era que declarar algo volátil não impede conflitos. Além disso, a sua 3ª exemplo não é uma grande idéia - ter dois separados globals com o mesmo nome é, no mínimo, tornando o código mais difícil de entender / manter.
você
11
@JohnU Você não deve usar voláteis para evitar condições de corrida, de fato isso não vai ajudar. Você deve usar volátil para evitar erros perigosos de otimização do compilador comuns em compiladores de sistemas embarcados.
Lundin
2
@ JohnU: O uso normal de volatilevariáveis ​​é permitir que o código seja executado em um contexto de execução para permitir que o código em outro contexto de execução saiba que algo aconteceu. Em um sistema de 8 bits, um buffer que armazena um número de bytes de potência de dois não superior a 128 pode ser gerenciado com um byte volátil, indicando o número total de bytes de vida útil colocados no buffer (mod 256) e outro indicando o número de bytes de vida útil retirados, desde que apenas um contexto de execução coloque dados no buffer e apenas um retire dados dele.
Supercat
2
@ JohnU: Embora possa ser possível usar alguma forma de bloqueio ou desativar temporariamente as interrupções para gerenciar o buffer, ele realmente não é necessário ou útil. Se o buffer tivesse que conter 128-255 bytes, a codificação teria que mudar um pouco e, se tivesse que suportar mais do que isso, provavelmente seria necessário desativar as interrupções, mas em um sistema de 8 bits os buffers tendem a ser pequenos; sistemas com buffers maiores geralmente podem fazer gravações atômicas maiores que 8 bits.
Supercat
24

As razões pelas quais você não gostaria de usar variáveis ​​globais em um sistema de 8 bits são as mesmas que você não gostaria de usá-las em qualquer outro sistema: elas dificultam o raciocínio sobre o comportamento do programa.

Somente programadores ruins ficam presos a regras como "não usam variáveis ​​globais". Bons programadores entendem o motivo por trás das regras e tratam as regras mais como diretrizes.

Seu programa é fácil de entender? Seu comportamento é previsível? É fácil modificar partes sem quebrar outras? Se a resposta para cada uma dessas perguntas for afirmativa , você está no caminho de um bom programa.

Phil Frost
fonte
11
O que o @MichaelKaras disse - entender o que essas coisas significam e como elas afetam as coisas (e como elas podem morder você) é o importante.
você
5

Você não deve evitar completamente o uso de variáveis ​​globais ("globais" para abreviar). Mas você deve usá-los criteriosamente. Os problemas práticos com o uso excessivo de globais:

  • Globais são visíveis em toda a unidade de compilação. Qualquer código na unidade de compilação pode modificar um global. As consequências de uma modificação podem surgir em qualquer lugar em que essa global for avaliada.
  • Como resultado, os globais tornam o código mais difícil de ler e entender. O programador sempre deve ter em mente todos os lugares onde o global é avaliado e atribuído.
  • O uso excessivo de globais torna o código mais propenso a defeitos.

É uma boa prática adicionar um prefixo g_ao nome das variáveis ​​globais. Por exemplo g_iFlags,. Quando você vê a variável com o prefixo no código, reconhece imediatamente que é global.

Nick Alexeev
fonte
2
A bandeira não precisa ser global. O ISR pode chamar uma função que tenha uma variável estática, por exemplo.
Phil Geada
+1 Eu nunca ouvi falar desse truque antes. Eu removi esse parágrafo da resposta. Como a staticbandeira se tornaria visível para o main()? Você está sugerindo que a mesma função que possui o staticpode devolvê-lo para o main()posterior?
Nick Alexeev
Essa é uma maneira de fazer isso. Talvez a função use o novo estado para definir e retorne o estado antigo. Existem muitas outras maneiras. Talvez você tenha um arquivo de origem com uma função para definir o sinalizador e outro para obtê-lo, com uma variável global estática mantendo o estado do sinalizador. Embora tecnicamente seja um "global" pela terminologia C, seu escopo é limitado apenas àquele arquivo, que contém apenas as funções que você precisa saber. Ou seja, seu escopo não é "global", o que é realmente o problema. C ++ fornece mecanismos adicionais de encapsulamento.
Phil Frost
4
Alguns métodos para evitar globais (como acessar o hardware apenas por meio de drivers de dispositivo) podem ser muito ineficientes para um ambiente de 8 bits com muita falta de recursos.
Spehro Pefhany
11
@SpehroPefhany Bons programadores entendem o motivo por trás das regras e tratam as regras mais como diretrizes. Quando as diretrizes estão em conflito, o bom programador pesa a balança com cuidado.
Phil Frost
4

A vantagem das estruturas de dados globais no trabalho incorporado é que elas são estáticas. Se todas as variáveis ​​necessárias forem globais, você nunca ficará sem memória acidentalmente quando as funções forem inseridas e houver espaço para elas na pilha. Mas então, nesse ponto, por que ter funções? Por que não uma grande função que lida com toda a lógica e processos - como um programa BASIC sem GOSUB permitido. Se você levar essa idéia longe o suficiente, terá um programa típico de linguagem assembly a partir dos anos 70. Eficiente e impossível de manter e solucionar problemas.

Portanto, use globais criteriosamente, como variáveis ​​de estado (por exemplo, se todas as funções precisarem saber se o sistema está em estado de interpretação ou execução) e outras estruturas de dados que precisam ser vistas por muitas funções e, como @PhilFrost diz, é o comportamento de suas funções previsíveis? Existe a possibilidade de preencher a pilha com uma string de entrada que nunca termina? Estes são assuntos para o design de algoritmos.

Observe que estática tem um significado diferente dentro e fora de uma função. /programming/5868947/difference-between-static-variable-inside-and-outside-of-a-function

/programming/5033627/static-variable-inside-of-a-function-in-c

C. Towne Springer
fonte
11
Muitos compiladores de sistemas embarcados alocam variáveis ​​automáticas estaticamente, mas sobrepõem variáveis ​​usadas por funções que não podem estar no escopo simultaneamente; isso geralmente gera um uso de memória que é igual ao pior caso possível para um sistema baseado em pilha no qual todas as sequências de chamadas estaticamente possíveis podem de fato ocorrer.
Supercat
4

Variáveis ​​globais devem ser usadas apenas para um estado verdadeiramente global. O uso de uma variável global para representar algo como, por exemplo, a latitude do limite norte do mapa só funcionará se houver um único "limite norte do mapa". Se, no futuro, o código precisar trabalhar com vários mapas com limites diferentes do norte, provavelmente será necessário reformular o código que usa uma variável global para o limite norte.

Em aplicativos típicos de computador, muitas vezes não há razão específica para supor que nunca haverá mais do que uma coisa. Em sistemas embarcados, no entanto, essas premissas geralmente são muito mais razoáveis. Embora seja possível que um programa de computador típico possa ser chamado para oferecer suporte a vários usuários simultâneos, a interface do usuário de um sistema embarcado típico será projetada para operação por um único usuário interagindo com seus botões e tela. Como tal, a qualquer momento, ele terá um único estado de interface do usuário. Projetar o sistema para que vários usuários possam interagir com vários teclados e monitores exigiria muito mais complexidade e levaria muito mais tempo para implementar do que projetá-lo para um único usuário. Se o sistema nunca for chamado a oferecer suporte a vários usuários, qualquer esforço extra investido para facilitar esse uso será desperdiçado. A menos que seja provável que o suporte a vários usuários seja necessário, provavelmente seria mais prudente arriscar descartar o código usado para uma interface de usuário único, caso o suporte a múltiplos usuários seja necessário, do que gastar mais tempo adicionando suporte ao usuário que provavelmente nunca será necessário.

Um fator relacionado aos sistemas incorporados é que, em muitos casos (especialmente envolvendo interfaces com o usuário), a única maneira prática de oferecer suporte a mais de uma coisa seria usar vários encadeamentos. Na ausência de outra necessidade de multiencadeamento, provavelmente é melhor usar um design simples de thread único do que aumentar a complexidade do sistema com multiencadeamento que provavelmente nunca será realmente necessário. Se a adição de mais de uma coisa exigiria uma enorme reformulação do sistema, não importará se também é necessário reformular o uso de algumas variáveis ​​globais.

supercat
fonte
Portanto, manter variáveis ​​globais que não se chocam entre si não será um problema. por exemplo: Day_cntr, week_cntr etc para os dias de contagem e semana respectively.And eu confio que não se deve usar deliberadamente tanto de variáveis globais que se assemelham a mesma finalidade e deve definir claramente those.Thanks muito para a resposta esmagadora :).
Rookie91
-1

Muitas pessoas estão confusas sobre esse assunto. A definição de uma variável global é:

Algo acessível de qualquer lugar do seu programa.

Isso não é o mesmo que variáveis ​​de escopo de arquivo , que são declaradas pela palavra-chave static. Essas não são variáveis ​​globais, são variáveis ​​privadas locais.

 int x; // global variable
 static int y; // file scope variable

 void some_func (void) {...} // added to demonstrate that the variables above are at file scope.

Você deve usar variáveis ​​globais? Existem alguns casos em que é bom:

Em todos os outros casos, você nunca deve usar variáveis ​​globais. Nunca há uma razão para fazê-lo. Em vez disso, use variáveis ​​de escopo de arquivo , o que é perfeitamente adequado.

Você deve se esforçar para escrever módulos de código autônomos e independentes projetados para executar uma tarefa específica. Dentro desses módulos, as variáveis ​​internas do escopo do arquivo devem residir como membros de dados privados. Esse método de design é conhecido como orientação a objetos e amplamente reconhecido como bom design.

Lundin
fonte
Por que o voto negativo?
usar o seguinte comando
2
Eu acho que "variável global" também pode ser usada para descrever alocações para um segmento global (não texto, pilha ou pilha). Nesse sentido, as variáveis ​​estáticas e funcionais do arquivo são / podem ser "globais". No contexto desta questão, é um pouco claro que global está se referindo ao segmento de escopo e não à alocação (embora seja possível que o OP não soubesse disso).
Paul A. Clayton
11
@ PaulA.Clayton Eu nunca ouvi falar de um termo formal chamado "segmento de memória global". Suas variáveis ​​terminarão em um dos seguintes locais: registradores ou empilhamento (alocação em tempo de execução), heap (alocação em tempo de execução), segmento .data (variáveis ​​de armazenamento estático explicitamente inicializadas), segmento .bss (variáveis ​​de armazenamento estático zeradas), .rodata (lido constantes) ou .text (parte do código). Se eles acabarem em outro lugar, é uma configuração específica do projeto.
Lundin 29/01
11
@ PaulA.Clayton Eu suspeito que o que você chama de "segmento global" é o .datasegmento.
Lundin