¿Cuál es la diferencia entre declarar una variable fuera del bucle y declarar el estático dentro del bucle?

9

Estas son dos formas en que puedo mantener una variable fuera del ciclo (o cualquier función).

Primero, puedo declararlo con alcance global fuera del ciclo:

void setup()
{
    Serial.begin(9600);
}

int count = 0;

void loop()
{
    Serial.println(count);
    count++;

    delay(250);
}

También puedo declararlo estático dentro del bucle:

void setup()
{
    Serial.begin(9600);
}

void loop()
{
    static int count = 0;

    Serial.println(count);
    count++;

    delay(250);
}

¿Qué diferencia, si hay alguna, hará esto?

Cybergibbons
fuente

Respuestas:

10

La diferencia más básica es de alcance.

En el primer caso, estás declarando una variable global. Es una variable accesible en todos los ámbitos después de su definición.

void setup()
{
    Serial.begin(9600);
}

void inc();
int count = 0;

void loop()
{
    Serial.println(count);
    count++;

    inc();

    delay(500);
}

void inc() //Can edit the value of count
{
  count=count+1;
};

En el segundo caso, está declarando una variable estática con alcance local. La variable persistirá para todo el programa ejecutado de manera similar a las variables globales, pero será accesible solo en el bloque de código en el que se declara. Este es el mismo ejemplo, con solo un cambio. countahora se declara como una variable estática dentro loop.

void inc();

void loop()
{
    static int count = 0;
    Serial.println(count);
    count++;

    inc();

    delay(500);
}

Esto no se compilará ya que la función inc()no tiene acceso count.

Las variables globales, aunque aparentemente útiles, vienen con algunas trampas. Incluso pueden causar daños cuando se trata de escribir programas que puedan interactuar con el entorno físico. Este es un ejemplo muy básico de algo que es probable que suceda, tan pronto como los programas comiencen a crecer. Una función puede cambiar inadvertidamente el estado de una variable global.

void setup()
{
    Serial.begin(9600);
}
void another_function();
int state=0;

void loop()
{
    //Keep toggling the state
    Serial.println(state);
    delay(250);
    state=state?0:1;

    //Some unrelated function call
    another_function();
}

void another_function()
{
  //Inadvertently changes state
  state=1;

}

Tales casos son muy difíciles de depurar. Sin embargo, este tipo de problema se puede detectar fácilmente, simplemente usando una variable estática.

void setup()
{
    Serial.begin(9600);
}
void another_function();

void loop()
{
    static int state=0;

    //Keep toggling the state
    Serial.println(state);
    delay(250);
    state=state?0:1;

    //Some unrelated function call
    another_function();
}

void another_function()
{
  //Results in a compile time error. Saves time.
  state=1;

}
Asheeshr
fuente
5

Desde una perspectiva funcional, ambas versiones generan el mismo resultado, ya que en ambos casos el valor de countse almacena entre las ejecuciones de loop()(ya sea porque es una variable global o porque está marcado como staticy, por lo tanto, mantiene su valor).

Entonces, la decisión de elegir se reduce a los siguientes argumentos:

  1. En general, en informática, se recomienda mantener sus variables lo más locales posible en términos de alcance . Esto generalmente resulta en un código mucho más claro con menos efectos secundarios y reduce las posibilidades de que otra persona use esa variable global que arruina su lógica). Por ejemplo, en su primer ejemplo, otras áreas lógicas pueden cambiar el countvalor, mientras que en el segundo, solo esa función particular loop()puede hacerlo).
  2. Las variables globales y estáticas siempre ocupan memoria , donde los locales solo lo hacen cuando están dentro del alcance. En los ejemplos anteriores, eso no hace ninguna diferencia (ya que en uno usa una variable global, en el otro una variable estática), pero en programas más grandes y complejos podría y podría ahorrar memoria usando locales no estáticos. Sin embargo : si tiene una variable en un área lógica que se ejecuta con mucha frecuencia, considere hacerla estática o global, ya que de lo contrario pierde un poco de rendimiento cada vez que ingresa el área lógica, ya que lleva un poco de tiempo asignar la memoria para esa nueva instancia variable. Necesita encontrar un equilibrio entre la carga de memoria y el rendimiento.
  3. Otros puntos, como un mejor diseño para el análisis estático u optimización por parte del compilador, también podrían entrar en juego.
  4. En algunos escenarios especiales, puede haber problemas con el orden de inicialización impredecible de los elementos estáticos ( aunque no estoy seguro acerca de ese punto, compare este enlace ).

Fuente: Tema similar en arduino.cc

Philip Allgaier
fuente
El reencuentro nunca debería ser un problema en Arduino ya que no admite concurrencia.
Peter Bloomfield
Cierto. Ese fue un punto más general, pero de hecho no relevante para Arduino. Quité esa parte.
Philip Allgaier
1
¡Siempre existirá una variable estática declarada dentro de un ámbito y utilizará el mismo espacio que una variable global! En el código OP, la única diferencia es qué código puede acceder a la variable. En la receta, la estática estará accesible dentro del mismo alcance.
jfpoilpret
1
@jfpoilpret Eso, por supuesto, es cierto, y veo que la parte respectiva en mi respuesta fue un poco engañosa. Arreglado eso.
Philip Allgaier
2

Ambas variables son estáticas: persisten durante toda la sesión de ejecución. El global es visible para cualquier función si declara, no define, el global, o si la función sigue la definición en la misma unidad de compilación (archivo + incluye).

Mover la definición de countdentro de una función limita su alcance de visibilidad al conjunto de {}es circundantes más cercano y le otorga una vida útil de invocación de función (se crea y destruye a medida que se ingresa y sale de la función). Declararlo statictambién le da una duración de la sesión de ejecución que existe desde el principio hasta el final de la sesión de ejecución, persistiendo en las invocaciones de funciones.

Por cierto: tenga cuidado con el uso de estadísticas inicializadas dentro de una función, ya que he visto que algunas versiones del compilador gnu se equivocan. Se debe crear e inicializar una variable automática con un inicializador en cada entrada de función. Una estática con un inicializador solo debe inicializarse una vez, durante la configuración de la ejecución, antes de que main () tenga el control (tal como lo haría un global). He reiniciado las estadísticas locales en cada entrada de función como si fueran automáticas, lo cual es incorrecto. Prueba tu propio compilador para estar seguro.

JRobert
fuente
No estoy seguro de entender lo que quieres decir con una función que declara un global. ¿Quieres decir como un extern?
Peter Bloomfield
@ PeterR.Bloomfield: No estoy seguro de qué parte de mi publicación está preguntando, pero me refería a los dos ejemplos del OP: el primero, una definición inherentemente global y el segundo, una estática local.
JRobert
-3

Según la documentación de Atmel: "Si se declara una variable global, se asignará una dirección única en la SRAM a esta variable en el momento del enlace del programa".

La documentación completa está aquí (Consejo # 2 para variables globales): http://www.atmel.com/images/doc8453.pdf

Vacío principal
fuente
44
¿No terminarán ambos ejemplos con una dirección única en SRAM? Ambos necesitan persistir.
Cybergibbons
2
Sí, en realidad puede encontrar esa información en el mismo documento en el consejo # 6
jfpoilpret