Uso de variables globales en sistemas embebidos

17

Empecé a escribir firmware para mi producto y soy un novato aquí. Revisé muchos artículos sobre no usar variables o funciones globales. ¿Existe algún límite para usar variables globales en un sistema de 8 bits o es un completo 'No-No'? ¿Cómo debo usar variables globales en mi sistema o debería evitarlas por completo?

Me gustaría recibir valiosos consejos de ustedes sobre este tema para hacer que mi firmware sea más compacto.

Novato91
fuente
Esta pregunta no es exclusiva de los sistemas integrados. Un duplicado se puede encontrar aquí .
Lundin
@Lundin Desde su enlace: "En estos días eso solo importa en entornos integrados donde la memoria es bastante limitada. Algo que debe saber antes de asumir que incrustado es lo mismo que otros entornos y asumir que las reglas de programación son las mismas en todos los ámbitos".
endolito
El staticalcance del archivo @endolith no es lo mismo que "global", vea mi respuesta a continuación.
Lundin

Respuestas:

31

Puede utilizar las variables globales con éxito, siempre y cuando tenga en cuenta las pautas de @ Phil. Sin embargo, aquí hay algunas buenas maneras de evitar sus problemas sin hacer que el código compilado sea menos compacto.

  1. Use variables estáticas locales para el estado persistente al que solo desea acceder dentro de una función.

    #include <stdint.h>
    void skipper()
    {
        static uint8_t skip_initial_cycles = 5;
        if (skip_initial_cycles > 0) {
            skip_initial_cycles -= 1;
            return;
        }
        /* ... */
    }
  2. Use una estructura para mantener juntas las variables relacionadas, para que quede más claro dónde se deben usar y dónde no.

    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 variables estáticas globales para hacer que las variables sean visibles solo dentro del archivo C actual. Esto evita el acceso accidental por código en otros archivos debido a conflictos de nombres.

    /* 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, si está modificando una variable global dentro de una rutina de interrupción y la está leyendo en otro lugar:

  • Marcar la variable volatile.
  • Asegúrese de que sea atómico para la CPU (es decir, 8 bits para una CPU de 8 bits).

O

  • Use un mecanismo de bloqueo para proteger el acceso a la variable.
richarddonkin
fuente
los vars volátiles y / o atómicos no lo ayudarán a evitar errores, necesita algún tipo de bloqueo / semáforo, o para enmascarar brevemente las interrupciones al escribir en la variable.
John U
3
Esa es una definición bastante limitada de "trabajar bien". Mi punto era que declarar algo volátil no evita conflictos. Además, su tercera ejemplo no es una gran idea - que tiene dos separados globales con el mismo nombre es por lo menos hacer que el código sea más difícil de entender / mantener.
John U
1
@JohnU No debes usar volátiles para evitar las condiciones de carrera, de hecho, eso no ayudará. Debe usar volátiles para evitar errores peligrosos de optimización del compilador comunes en los compiladores de sistemas integrados.
Lundin
2
@JohnU: El uso normal de las volatilevariables es permitir que el código se ejecute en un contexto de ejecución para que el código en otro contexto de ejecución sepa que algo ha sucedido. En un sistema de 8 bits, un búfer que contendrá un número de bytes de potencia de dos no mayor que 128 se puede administrar con un byte volátil que indica el número total de bytes de vida útil puestos en el búfer (mod 256) y otro que indica el número de bytes de vida útil extraídos, siempre que solo un contexto de ejecución coloque datos en el búfer y solo uno los extraiga.
supercat
2
@JohnU: Si bien es posible usar alguna forma de bloqueo o deshabilitar temporalmente las interrupciones para administrar el búfer, realmente no es necesario ni útil. Si el búfer tuviera que contener 128-255 bytes, la codificación tendría que cambiar ligeramente, y si tuviera que retener más que eso, probablemente sería necesario desactivar las interrupciones, pero en un sistema de 8 bits los búferes son pequeños; Los sistemas con memorias intermedias más grandes generalmente pueden realizar escrituras atómicas mayores de 8 bits.
supercat
24

Las razones por las que no querría usar variables globales en un sistema de 8 bits son las mismas que no desearía usar en ningún otro sistema: dificultan el razonamiento sobre el comportamiento del programa.

Solo los malos programadores se obsesionan con reglas como "no usar variables globales". Los buenos programadores entienden la razón detrás de las reglas, luego las tratan más como pautas.

¿Su programa es fácil de entender? ¿Es predecible su comportamiento? ¿Es fácil modificar partes de él sin romper otras partes? Si la respuesta a cada una de estas preguntas es , entonces está en camino a un buen programa.

Phil Frost
fuente
1
Lo que dijo @MichaelKaras: entender lo que significan estas cosas y cómo afectan las cosas (y cómo pueden morderte) es lo importante.
John U
5

No debe evitar por completo el uso de variables globales ("globales" para abreviar). Pero, debes usarlos juiciosamente. Los problemas prácticos con el uso excesivo de globals:

  • Globales son visibles en toda la unidad de compilación. Cualquier código en la unidad de compilación puede modificar un global. Las consecuencias de una modificación pueden surgir en cualquier lugar donde se evalúe este global.
  • Como resultado, los globales hacen que el código sea más difícil de leer y comprender. El programador siempre tiene que tener en cuenta todos los lugares donde se evalúa y asigna el global.
  • El uso excesivo de globals hace que el código sea más propenso a defectos.

Es una buena práctica agregar un prefijo g_al nombre de las variables globales. Por ejemplo, g_iFlags. Cuando veas la variable con el prefijo en el código, inmediatamente reconocerás que es global.

Nick Alexeev
fuente
2
La bandera no tiene que ser global. El ISR podría llamar a una función que tiene una variable estática, por ejemplo.
Phil Frost
+1 No he oído hablar de ese truco antes. He eliminado ese párrafo de la respuesta. ¿Cómo se staticharía visible la bandera para el main()? ¿Estás insinuando que la misma función que tiene elstatic puede devolverla a la main()posterior?
Nick Alexeev
Esa es una forma de hacerlo. Quizás la función toma el nuevo estado para establecer y devuelve el estado anterior. Hay muchas otras formas. Tal vez tenga un archivo fuente con una función para establecer el indicador, y otro para obtenerlo, con una variable global estática que contenga el estado del indicador. Aunque técnicamente esta es una terminología "global" de C, su alcance se limita solo a ese archivo, que contiene solo las funciones que necesitan saber. Es decir, su alcance no es "global", lo cual es realmente el problema. C ++ proporciona mecanismos de encapsulación adicionales.
Phil Frost
44
Algunos métodos para evitar los problemas globales (como acceder al hardware solo a través de controladores de dispositivos) pueden ser demasiado ineficientes para un entorno de 8 bits que carece de recursos.
Spehro Pefhany
1
@SpehroPefhany Los buenos programadores entienden la razón detrás de las reglas, luego las tratan más como pautas.Cuando las pautas están en conflicto, el buen programador pesa la balanza con cuidado.
Phil Frost
4

La ventaja de las estructuras de datos globales en el trabajo integrado es que son estáticas. Si cada variable que necesita es global, nunca se quedará sin memoria accidentalmente cuando se ingresen las funciones y se haga espacio para ellas en la pila. Pero entonces, en ese punto, ¿por qué tener funciones? ¿Por qué no una gran función que maneja toda la lógica y los procesos, como un programa BÁSICO sin GOSUB permitido? Si lleva esta idea lo suficientemente lejos, tendrá un programa típico en lenguaje ensamblador de la década de 1970. Eficiente e imposible de mantener y solución de problemas.

Por lo tanto, use juiciosamente, como las variables de estado (por ejemplo, si cada función necesita saber si el sistema está en estado de interpretación o ejecución) y otras estructuras de datos que deben ser vistas por muchas funciones y, como dice @PhilFrost, es el comportamiento de sus funciones predecibles? ¿Existe la posibilidad de llenar la pila con una cadena de entrada que nunca termina? Estos son asuntos para el diseño de algoritmos.

Tenga en cuenta que static tiene un significado diferente dentro y fuera de una función. /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
fuente
1
Muchos compiladores de sistemas embebidos asignan variables automáticas estáticamente, pero superponen variables usadas por funciones que no pueden estar dentro del alcance simultáneamente; esto generalmente produce un uso de memoria que es igual al peor de los casos posibles para un sistema basado en pila en el que pueden ocurrir todas las secuencias de llamadas estáticamente posibles.
supercat
4

Las variables globales solo deben usarse para un estado verdaderamente global. El uso de una variable global para representar algo como, por ejemplo, la latitud del límite norte del mapa solo funcionará si solo puede haber un "límite norte del mapa". Si en el futuro el código tuviera que funcionar con múltiples mapas que tienen diferentes límites norteños, el código que usa una variable global para el límite norte probablemente tendrá que ser modificado.

En las aplicaciones informáticas típicas, a menudo no hay una razón particular para suponer que nunca habrá más de una. Sin embargo, en los sistemas integrados, tales suposiciones son a menudo mucho más razonables. Si bien es posible que se requiera que un programa de computadora típico admita múltiples usuarios simultáneos, la interfaz de usuario de un sistema embebido típico estará diseñada para que la opere un solo usuario que interactúa con sus botones y pantalla. Como tal, en cualquier momento tendrá un solo estado de interfaz de usuario. Diseñar el sistema para que múltiples usuarios puedan interactuar con múltiples teclados y pantallas requeriría mucha más complejidad, y llevaría mucho más tiempo implementarlo, que diseñarlo para un solo usuario. Si nunca se solicita al sistema que admita múltiples usuarios, se desperdiciará cualquier esfuerzo adicional invertido para facilitar dicho uso. A menos que sea probable que se requiera soporte multiusuario, sería más prudente arriesgarse a tener que descartar el código utilizado para una interfaz de usuario único en caso de que se requiera soporte multiusuario, que gastar un tiempo extra agregando soporte al usuario que probablemente nunca sea necesario.

Un factor relacionado con los sistemas embebidos es que, en muchos casos (especialmente cuando se trata de interfaces de usuario), la única forma práctica de soportar tener más de uno de los elementos sería utilizar múltiples subprocesos. En ausencia de alguna otra necesidad de subprocesos múltiples, es probable que sea mejor usar un diseño simple de un solo subproceso que aumentar la complejidad del sistema con subprocesos múltiples que probablemente nunca sea realmente necesario. Si agregar más de uno requeriría un gran rediseño del sistema de todos modos, no importará si también requiere reelaborar el uso de algunas variables globales.

Super gato
fuente
Por lo tanto, mantener variables globales que no choquen entre sí no será un problema correcto. por ejemplo: Day_cntr, week_cntr, etc. para contar días y semanas respectivamente. Y confío en que uno no debe usar deliberadamente muchas variables globales que se asemejan al mismo propósito y deben definirlas claramente. Muchas gracias por la abrumadora respuesta. :)
Novato91
-1

Mucha gente está confundida sobre este tema. La definición de una variable global es:

Algo que es accesible desde todas partes en su programa.

Esto no es lo mismo que las variables de alcance del archivo , que son declaradas por la palabra clave static. Esas no son variables globales, son variables privadas locales.

 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.

¿Deberías usar variables globales? Hay algunos casos en los que está bien:

En cualquier otro caso, no utilizará variables globales, nunca. Nunca hay una razón para hacerlo. En su lugar, use variables de alcance de archivo , lo cual está perfectamente bien.

Debe esforzarse por escribir módulos de código independientes y autónomos diseñados para realizar una tarea específica. Dentro de esos módulos, las variables de alcance del archivo interno deben residir como miembros de datos privados. Este método de diseño se conoce como orientación a objetos y es ampliamente reconocido como un buen diseño.

Lundin
fuente
¿Por qué el voto negativo?
m.Alin
2
Creo que "variable global" también se puede usar para describir asignaciones a un segmento global (no texto, pila o montón). En ese sentido, las variables estáticas de los archivos y las funciones estáticas son / pueden ser "globales". En el contexto de esta pregunta, está algo claro que global se refiere al alcance, no al segmento de asignación (aunque es posible que el OP no lo supiera).
Paul A. Clayton
1
@ PaulA.Clayton Nunca he oído hablar de un término formal llamado "segmento de memoria global". Sus variables terminarán en uno de los siguientes lugares: registros o pila (asignación de tiempo de ejecución), montón (asignación de tiempo de ejecución), segmento .data (variables de almacenamiento estático inicializadas explícitamente), segmento .bss (variables de almacenamiento estático cero), .rodata (lectura -solo constantes) o .text (parte del código). Si terminan en otro lugar, esa es una configuración específica del proyecto.
Lundin
1
@ PaulA.Clayton Sospecho que lo que usted llama "segmento global" es el .datasegmento.
Lundin