¿Son malvadas las variables globales en Arduino?

24

Soy relativamente nuevo en programación y muchas de las mejores prácticas de codificación que estoy leyendo efectivamente indican que hay muy pocas buenas razones para usar una variable global (o que el mejor código no tiene globales).

He hecho todo lo posible para tener esto en cuenta, al escribir software para hacer una interfaz Arduino con una tarjeta SD, hablar con una computadora y ejecutar un controlador de motor.

Actualmente tengo 46 globales para aproximadamente 1100 líneas de "nivel de principiante", código (ninguna línea tiene más de una acción). ¿Es esta una buena relación o debería considerar reducirla más? Además, ¿qué prácticas puedo emplear para reducir aún más el número de globales?

Lo pregunto aquí porque me interesan específicamente las mejores prácticas para la codificación de productos Arduino en lugar de la programación de computadoras en general.

ATE-ENGE
fuente
2
En Arduino no puedes evitar las variables globales. Cada declaración de variable fuera del alcance de una función / método es global. Por lo tanto, si necesita compartir valores entre funciones, deben ser globales, a menos que desee pasar cada valor como argumento.
16
@LookAlterno Err, ¿no puedes escribir clases en Ardunio, ya que es solo C ++ con macros y bibliotecas extrañas? Si es así, no todas las variables son globales. E incluso en C, generalmente se considera la mejor práctica preferir pasar variables (quizás dentro de estructuras) a funciones en lugar de tener variables globales. Puede ser menos conveniente para un programa pequeño, pero generalmente vale la pena a medida que el programa se hace más grande y más complejo.
Muzer
11
@LookAlterno: " Evito " y "no puedes" son cosas muy diferentes.
Lightness compite con Monica el
2
En realidad, algunos programadores integrados prohíben las variables locales y requieren variables globales (o variables estáticas con ámbito de función). Cuando los programas son pequeños, puede facilitar su análisis como una simple máquina de estados; porque las variables nunca se sobrescriben entre sí (es decir, como se apilan y apilan las variables asignadas).
Rob
1
Las variables estáticas y globales ofrecen la ventaja de conocer el consumo de memoria en tiempo de compilación. Con un arduino con memoria disponible muy limitada, esto puede ser una ventaja. Es bastante fácil para un novato agotar la memoria disponible y experimentar fallas imposibles de rastrear.
antipatrtern

Respuestas:

33

No son malvados per se, pero tienden a ser utilizados en exceso cuando no hay una buena razón para usarlos.

Hay muchas ocasiones en que las variables globales son ventajosas. Sobre todo porque programar un Arduino es, bajo el capó, muy diferente a programar una PC.

El mayor beneficio para las variables globales es la asignación estática. Especialmente con variables grandes y complejas, como instancias de clase. La asignación dinámica (el uso de newetc.) está mal vista debido a la falta de recursos.

Además, no obtiene un solo árbol de llamadas como lo hace en un programa C normal ( main()función única que llama a otras funciones); en cambio, obtiene efectivamente dos árboles separados ( setup()funciones de llamada, luego loop()funciones de llamada), lo que significa que a veces las variables globales son única forma de lograr su objetivo (es decir, si desea usarlo en ambos setup()y loop()).

Entonces no, no son malvados, y en un Arduino tienen más y mejores usos que en una PC.

Majenko
fuente
Ok, ¿qué pasa si es algo que solo estoy usando loop()(o en múltiples funciones llamadas loop())? ¿Sería mejor configurarlos de una manera diferente que definirlos al principio?
ATE-ENGE
1
En ese caso, probablemente los definiría en loop () (tal vez como staticsi los necesitara para retener su valor a través de las iteraciones) y pasarlos a través de los parámetros de la función en la cadena de llamadas.
Majenko
2
Esa es una forma, o páselo como referencia: void foo(int &var) { var = 4; }y foo(n);- nahora es 4.
Majenko
1
Las clases se pueden asignar por pila. Es cierto que no es bueno poner grandes instancias en la pila, pero aún así.
JAB
1
@JAB Cualquier cosa se puede asignar en pila.
Majenko
18

Es muy difícil dar una respuesta definitiva sin ver su código real.

Las variables globales no son malas, y a menudo tienen sentido en un entorno incrustado en el que normalmente se accede mucho al hardware. Tiene solo cuatro UARTS, solo un puerto I2C, etc. Por lo tanto, tiene sentido usar globales para variables vinculadas a recursos de hardware específicos. Y de hecho, la biblioteca central Arduino hace eso: Serial, Serial1, etc, son variables globales. Además, una variable que representa el estado global del programa suele ser global.

Actualmente tengo 46 globales para aproximadamente 1100 líneas de [código]. ¿Es esta una buena relación [...]

No se trata de los números. La pregunta correcta que debe hacerse es, para cada uno de estos globales, si tiene sentido tenerlo en el ámbito global.

Aún así, 46 global me parece un poco alto. Si algunos de estos tienen valores constantes, califíquelos como const: el compilador generalmente optimizará su almacenamiento. Si alguna de esas variables solo se usa dentro de una sola función, conviértala en local. Si desea que su valor persista entre llamadas a la función, califíquelo como static. También podría reducir el número de globales "visibles" al agrupar variables dentro de una clase y tener una instancia global de esta clase. Pero solo hazlo cuando tenga sentido armar las cosas. Hacer una gran GlobalStuffclase en aras de tener solo una variable global no ayudará a aclarar su código.

Edgar Bonet
fuente
1
¡Okay! No sabía staticsi tengo una variable que se usa solo en una función, pero obtengo un nuevo valor cada vez que se llama a una función (como var=millis()) ¿debería hacer eso static?
ATE-ENGE
3
No. statices solo para cuando necesita mantener el valor. Si lo primero que hace en la función es establecer el valor de la variable a la hora actual, no es necesario mantener el valor anterior. Todo lo que hace la estática en esa situación es desperdiciar memoria.
Andrew
Esta es una respuesta muy completa, que expresa ambos puntos a favor y el escepticismo sobre los globales. +1
underscore_d
6

El principal problema con las variables globales es el mantenimiento del código. Al leer una línea de código, es fácil encontrar la declaración de variables pasadas como parámetro o declaradas localmente. No es tan fácil encontrar la declaración de variables globales (a menudo requiere e IDE).

Cuando tiene muchas variables globales (40 ya es mucho), se hace difícil tener un nombre explícito que no sea demasiado largo. Usar el espacio de nombres es una forma de aclarar el papel de las variables globales.

Una forma pobre de imitar espacios de nombres en C es:

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

En el procesador Intel o Arm, el acceso a las variables globales es más lento que otras variables. Probablemente sea lo contrario en arduino.

BOC
fuente
2
En AVR, los globales están en RAM, mientras que la mayoría de los locales no estáticos se asignan a los registros de la CPU, lo que hace que su acceso sea más rápido.
Edgar Bonet
1
El problema no es realmente encontrar la declaración; ser capaz de comprender rápidamente el código es importante pero, en última instancia, secundario a lo que hace, y allí el verdadero problema es encontrar qué alimaña en qué parte desconocida de su código puede usar la declaración global para hacer cosas raras con un objeto que dejó fuera al aire libre para que todos puedan hacer lo que quieran.
underscore_d
5

Aunque no los usaría al programar para una PC, para Arduino tienen algunos beneficios. La mayoría si ya se ha dicho:

  • Sin uso de memoria dinámica (creando brechas en el espacio limitado de almacenamiento dinámico de un Arduino)
  • Disponible en todas partes, por lo que no es necesario pasarlos como argumentos (lo que cuesta espacio en la pila)

Además, en algunos casos, especialmente en términos de rendimiento, puede ser bueno usar variables globales, en lugar de crear elementos cuando sea necesario:

  • Para disminuir las brechas en el espacio del montón
  • Asignar y / o liberar memoria dinámicamente puede llevar mucho tiempo
  • Las variables se pueden "reutilizar", como una lista de elementos de una lista global que se utilizan por múltiples razones, por ejemplo, una vez como un búfer para la SD y más tarde como un búfer de cadena temporal.
Michel Keijzers
fuente
5

Como con todo (excepto los gotos que son verdaderamente malvados) los globales tienen su lugar.

por ejemplo, si tiene un puerto serie de depuración o un archivo de registro en el que necesita poder escribir desde cualquier lugar, a menudo tiene sentido hacerlo global. Del mismo modo, si tiene alguna información crítica sobre el estado del sistema, hacerla global es a menudo la solución más fácil. No tiene sentido tener un valor que debe pasar a todas las funciones del programa.

Como otros han dicho, 46 ​​parece mucho para solo un poco más de 1000 líneas de código, pero sin conocer los detalles de lo que está haciendo, es difícil decir si ha terminado de usarlos o no.

Sin embargo, para cada global, hágase algunas preguntas importantes:

¿Es el nombre claro y específico para que no intente usar el mismo nombre en otro lugar accidentalmente? Si no cambia el nombre.

¿Esto necesita cambiar alguna vez? Si no es así, considere hacerlo una constante.

¿Es necesario que esto sea visible en todas partes o es solo global para que el valor se mantenga entre las llamadas a funciones? Considere hacerlo local a la función y use la palabra clave static.

¿Las cosas realmente irán mal si esto se cambia por un código cuando no estoy teniendo cuidado? por ejemplo, si tiene dos variables relacionadas, por ejemplo, nombre y número de identificación, que son globales (consulte la nota anterior sobre el uso de global cuando necesita información en casi todas partes), entonces, si una de ellas cambia sin que ocurran otras cosas desagradables. Sí, podrías tener cuidado, pero a veces es bueno imponer un poco de cuidado. p. ej. colóquelos en un archivo .c diferente y luego defina funciones que establezcan ambos al mismo tiempo y le permitan leerlos. Luego solo incluye las funciones en el archivo de encabezado asociado, de esa manera el resto del código solo puede acceder a las variables a través de las funciones definidas y, por lo tanto, no puede estropear las cosas.

- Actualización - Acabo de darme cuenta de que había preguntado sobre las mejores prácticas específicas de Arduino en lugar de la codificación general y esta es más una respuesta de codificación general. Pero, sinceramente, no hay mucha diferencia, las buenas prácticas son buenas prácticas. La estructura startup()y loop()de Arduino significa que tienes que usar globals un poco más que otras plataformas en algunas situaciones, pero eso realmente no cambia mucho, siempre terminas buscando lo mejor que puedes hacer dentro de las limitaciones de la plataforma sin importar lo que pase. la plataforma es

Andrés
fuente
No sé nada sobre Arduinos, pero hago mucho desarrollo de escritorio y servidor. Hay un uso aceptable (en mi humilde opinión) para gotos y es romper los bucles anidados, es mucho más limpio y fácil de entender que las alternativas.
Persistencia
Si crees que gotoson malvados, mira el código de Linux. Podría decirse que son tan malvados como los try...catchbloques cuando se usan como tales.
Dmitry Grigoryev
5

¿Son malvados? Tal vez. El problema con los globales es que se puede acceder a ellos y modificarlos en cualquier momento mediante cualquier función o código que se ejecute, sin restricciones. Esto puede conducir a situaciones que son, digamos, difíciles de rastrear y explicar. Por lo tanto, es deseable minimizar la cantidad de glóbulos, si es posible volver a poner la cantidad a cero.

¿Se pueden evitar? Casi siempre si. El problema con Arduino es que te obligan a adoptar este enfoque de dos funciones en el que te asumen a ti setup()y a ti loop(). En este caso particular, no tiene acceso al alcance de la función de llamada de estas dos funciones (probablemente main()). Si lo hubiera hecho, podría deshacerse de todos los globales y utilizar a los locales en su lugar.

Imagen de lo siguiente:

int main() {
  setup();

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

Esto es probablemente más o menos como se ve la función principal de un programa Arduino. Entonces, las variables que necesita tanto en setup()la loop()función como en la función se declararían preferiblemente dentro del alcance de la main()función en lugar del alcance global. Luego podrían hacerse accesibles a las otras dos funciones pasándolas como argumentos (utilizando punteros si es necesario).

Por ejemplo:

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

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

Tenga en cuenta que en este caso también necesita cambiar la firma de ambas funciones.

Como esto podría no ser factible ni deseable, realmente veo solo una forma de eliminar la mayoría de los globales de un programa Arduino sin modificar la estructura del programa forzado.

Si no recuerdo mal, puede usar C ++ perfectamente mientras programa para Arduino, en lugar de C. Si aún no está familiarizado (todavía) con OOP (Programación Orientada a Objetos) o C ++, puede tomar un tiempo acostumbrarse y algunos leyendo.

Mi propuesta sería crear una clase de Programa y crear una única instancia global de esta clase. Una clase debe considerarse el modelo para los objetos.

Considere el siguiente programa de ejemplo:

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 hemos librado de casi todos los globales. Las funciones en las que comenzaría a agregar la lógica de su aplicación serían las funciones Program::setup()y Program::loop(). Estas funciones tienen acceso a las variables miembro específicas de la instancia myFirstSampleVariabley mySecondSampleVariablemientras que las funciones tradicionales setup()y loop()no tienen acceso ya que estas variables se han marcado como privadas de clase. Este concepto se llama encapsulación de datos u ocultación de datos.

Enseñarle OOP y / o C ++ está un poco fuera del alcance de la respuesta a esta pregunta, así que me detendré aquí.

Para resumir: se deben evitar los globals y casi siempre es posible reducir drásticamente la cantidad de globals. También cuando estás programando para Arduino.

Lo más importante es que espero que mi respuesta sea algo útil para usted :)

Arjen
fuente
Puede definir su propio main () en el boceto si lo prefiere. Así es como se ve el stock: github.com/arduino/Arduino/blob/1.8.3/hardware/arduino/avr/…
per1234
Un singleton es efectivamente un global, solo vestido de una manera extra confusa. Tiene los mismos inconvenientes.
patstew
@patstew ¿Te importaría explicarme cómo sientes que tiene los mismos inconvenientes? En mi opinión, no es así, ya que puede utilizar la encapsulación de datos para su beneficio.
Arjen
@ per1234 Gracias! Definitivamente no soy un experto en Arduino, pero supongo que mi primera sugerencia podría funcionar también en ese momento.
Arjen
2
Bueno, todavía es un estado global al que se puede acceder desde cualquier lugar del programa, solo tienes que acceder a él en Program::instance().setup()lugar de hacerlo globalProgram.setup(). Poner variables globales relacionadas en una clase / estructura / espacio de nombres puede ser beneficioso, especialmente si solo son necesarias para un par de funciones relacionadas, pero el patrón singleton no agrega nada. En otras palabras, static Program p;tiene almacenamiento global y static Program& instance()tiene acceso global, lo que equivale a lo simple Program globalProgram;.
patstew
4

Las variables globales nunca son malas . Una regla general en contra de ellos es solo una muleta para permitirte sobrevivir el tiempo suficiente para obtener la experiencia para tomar mejores decisiones.

Lo que es una variable global es una suposición inherente de que solo hay una cosa (no importa si estamos hablando de una matriz o mapa global que podría contener varias cosas, que aún contiene la suposición de que solo hay una lista o mapeo, y no múltiples independientes).

Entonces, antes de hacer uso de un global, desea preguntarse: ¿es concebible que alguna vez quiera usar más de una de estas cosas? Si resulta ser cierto en el futuro, tendrá que modificar el código para no globalizar esa cosa, y probablemente descubrirá en el camino que otras partes de su código dependen de esa suposición de singularidad, por lo que Tendré que arreglarlos también, y el proceso se vuelve tedioso y propenso a errores. Se enseña "No usar globals" porque generalmente es un costo bastante pequeño evitar los globals desde el principio, y evita la posibilidad de tener que pagar un costo elevado más adelante.

Pero los supuestos simplificadores que permiten los globales también hacen que su código sea más pequeño, más rápido y use menos memoria, ya que no tiene que pasar por alto la noción de qué cosa está usando, no tiene que hacer indirección, no tiene que considere la posibilidad de que lo deseado tal vez no exista, etc. En el embebido, es más probable que se vea limitado por el tamaño del código y / o el tiempo y / o la memoria de la CPU que en una PC, por lo que estos ahorros pueden ser importantes. Y muchas aplicaciones integradas también tienen más rigidez en los requisitos: usted sabe que su chip solo tiene uno de ciertos periféricos, el usuario no puede simplemente enchufar otro en un puerto USB o algo así.

Otra razón común para querer más de uno de algo que parece único es la prueba: probar la interacción entre dos componentes es más fácil cuando puede pasar una instancia de prueba de algún componente a una función, mientras que tratar de modificar el comportamiento de un componente global es Una propuesta más complicada. Pero las pruebas en el mundo integrado tienden a ser muy diferentes a las de otros lugares, por lo que esto puede no aplicarse a usted. Hasta donde yo sé, Arduino no tiene cultura de prueba alguna.

Así que adelante y use globals cuando parezca que valen la pena. El código policial no vendrá a buscarte. Solo sé que la elección incorrecta podría llevarte a mucho más trabajo en el futuro, así que si no estás seguro ...

hobbs
fuente
0

¿Son malvadas las variables globales en Arduino?

nada es inherentemente malo, incluidas las variables globales. Lo caracterizaría como un "mal necesario": puede hacer que su vida sea mucho más fácil, pero debe abordarse con precaución.

Además, ¿qué prácticas puedo emplear para reducir aún más el número de globales?

use funciones de contenedor para acceder a sus variables globales. así que al menos lo estás manejando desde la perspectiva del alcance.

dannyf
fuente
3
Si usa funciones de contenedor para acceder a variables globales, también puede colocar sus variables dentro de estas funciones.
Dmitry Grigoryev