Concepto de palabra clave estática desde la perspectiva de C incrustado

static volatile unsigned char   PORTB   @ 0x06;

Esta es una línea de código en un archivo de encabezado de microcontrolador PIC. El @operador se usa para almacenar el valor de PORTB dentro de la dirección 0x06, que es un registro dentro del controlador PIC que representa PORTB. Hasta este punto, tengo una idea clara.

Esta línea se declara como una variable global dentro de un archivo de encabezado ( .h). Entonces, por lo que llegué a saber sobre el lenguaje C, una "variable global estática" no es visible para ningún otro archivo o, simplemente, las variables/funciones globales estáticas no se pueden usar fuera del archivo actual.

Entonces, ¿cómo puede esta palabra clave PORTBser visible para mi archivo fuente principal y muchos otros archivos de encabezado que creé manualmente?

En mi archivo fuente principal, solo agregué el archivo de encabezado. #include pic.h¿Tiene esto algo que ver con mi pregunta?

no hay problema con la pregunta, pero me temo que la sección SE es incorrecta.
static se usa normalmente dentro de una función para especificar que la variable se crea una vez y mantiene su valor de una ejecución de una función a la siguiente. una variable global es una que se crea fuera de cualquier función para que sea visible en todas partes. static global realmente no tiene sentido.
@Finbarr Incorrecto. staticlos globales son visibles dentro de toda la unidad de compilación única y no se exportan más allá de eso. Son muy parecidos privatea los miembros de una clase en programación orientada a objetos. Es decir, cada variable que debe compartirse entre diferentes funciones dentro de una unidad de compilación pero que no se supone que sea visible fuera de esa cu debería serlo realmente static. Esto también reduce el "golpeteo" del espacio de nombres global del programa.
Re "El operador @ se usa para almacenar el valor PORTB dentro de la dirección 0x06" . ¿En realidad? ¿No es más como "El operador @ se usa para almacenar la variable "PORTB" en la dirección de memoria absoluta 0x06" ?

Respuestas (6)

La palabra clave 'estático' en C tiene dos significados fundamentalmente diferentes.

alcance limitante

En este contexto, 'static' se empareja con 'extern' para controlar el alcance de una variable o nombre de función. Estático hace que el nombre de la variable o función esté disponible solo dentro de una sola unidad de compilación y solo disponible para el código que existe después de la declaración/definición dentro del texto de la unidad de compilación.

Esta limitación en sí misma solo significa algo si y solo si tiene más de una unidad de compilación en su proyecto. Si solo tiene una unidad de compilación, entonces todavía hace cosas, pero esos efectos son en su mayoría inútiles (a menos que le guste profundizar en los archivos de objetos para leer lo que generó el compilador).

Como se señaló, esta palabra clave en este contexto se empareja con la palabra clave 'extern', que hace lo contrario, al hacer que el nombre de la variable o función se pueda vincular con el mismo nombre que se encuentra en otras unidades de compilación. Por lo tanto, puede considerar que 'estático' requiere que la variable o el nombre se encuentre dentro de la unidad de compilación actual, mientras que 'externo' permite la vinculación de unidades de compilación cruzada.

Vida útil estática

El tiempo de vida estático significa que la variable existe a lo largo de la duración del programa (cualquiera que sea). Cuando usa 'estático' para declarar/definir una variable dentro de una función, significa que la variable se crea en algún momento antes de su primer uso ( lo que significa que, cada vez que lo experimenté, la variable se crea antes de que se inicie main() y no se destruye después. Ni siquiera cuando se completa la ejecución de la función y regresa a su llamador. Y al igual que las variables estáticas de por vida declaradas fuera de las funciones, se inicializan en el mismo momento, antes de que comience main(), a un cero semántico (si no se proporciona inicialización) o a un valor explícito especificado, si se proporciona.

Esto es diferente de las variables de función de tipo 'auto', que se crean nuevas (o, como si fueran nuevas) cada vez que se ingresa la función y luego se destruyen (o, como si fueran destruidas) cuando la función sale.

A diferencia del impacto de aplicar 'estática' en una definición de variable fuera de una función, que afecta directamente su alcance, declarar una variable de función (dentro del cuerpo de una función, obviamente) como 'estática' no tiene impacto en su alcance . El alcance está determinado por el hecho de que se definió dentro de un cuerpo de función. Las variables estáticas de por vida definidas dentro de las funciones tienen el mismo alcance que otras variables 'automáticas' definidas dentro de los cuerpos de las funciones: alcance de la función.

Resumen

Entonces, la palabra clave 'estática' tiene diferentes contextos con lo que equivale a "significados muy diferentes". La razón por la que se usó de dos maneras, como esta, fue para evitar usar otra palabra clave. (Hubo una larga discusión al respecto). Se consideró que los programadores podían tolerar el uso y el valor de evitar otra palabra clave en el lenguaje era más importante (que los argumentos de otra manera).

(Todas las variables declaradas fuera de las funciones tienen una duración estática y no necesitan la palabra clave 'estático' para que eso sea cierto. Así que esto liberó la palabra clave que se usará allí para significar algo completamente diferente: 'visible solo en una sola compilación unidad'. Es una especie de truco.)

Nota específica

carácter estático volátil sin firmar PORTB @ 0x06;

La palabra 'estático' aquí debe interpretarse en el sentido de que el enlazador no intentará hacer coincidir varias apariciones de PORTB que se pueden encontrar en más de una unidad de compilación (suponiendo que su código tenga más de una).

Utiliza una sintaxis especial (no portátil) para especificar la "ubicación" (o el valor numérico de la etiqueta, que suele ser una dirección) de PORTB. Entonces, el enlazador recibe la dirección y no necesita encontrar una para ella. Si tuviera dos unidades de compilación usando esta línea, cada una terminaría apuntando al mismo lugar de todos modos. Así que no hay necesidad de etiquetarlo como 'externo' aquí.

Si hubieran usado 'externo', podría plantear un problema. El enlazador entonces podría ver (e intentaría hacer coincidir) múltiples referencias a PORTB encontradas en múltiples compilaciones. Si todos ellos especifican una dirección como esta, y las direcciones NO son las mismas por alguna razón [¿error?], entonces, ¿qué se supone que debe hacer? ¿Quejarse? ¿O? (Técnicamente, con 'externo', la regla general sería que solo UNA unidad de compilación especificaría el valor y las otras no).

Simplemente es más fácil etiquetarlo como 'estático', evitando que el enlazador se preocupe por los conflictos, y simplemente culpe a quien haya cambiado la dirección a algo que no debería ser por cualquier error por direcciones no coincidentes.

De cualquier manera, la variable se trata como si tuviera una 'vida útil estática'. (Y 'volátil'.)

Una declaración no es una definición , pero todas las definiciones son declaraciones.

En C, una definición crea un objeto. También lo declara. Pero una declaración por lo general (vea la nota de viñeta a continuación) no crea un objeto.

Las siguientes son definiciones y declaraciones:

static int a;
static int a = 7;
extern int b = 5;
extern int f() { return 10; }

Las siguientes no son definiciones, sino solo declaraciones:

extern int b;
extern int f();

Tenga en cuenta que las declaraciones no crean un objeto real. Solo declaran los detalles al respecto, que el compilador puede usar para ayudar a generar el código correcto y proporcionar mensajes de advertencia y error, según corresponda.

  • Arriba, digo "generalmente", deliberadamente. En algunos casos, una declaración puede crear un objeto y, por lo tanto, el enlazador (nunca el compilador) la promueve a una definición. Por lo tanto, incluso en este raro caso, el compilador de C aún piensa que la declaración es solo una declaración. Es la fase del enlazador la que realiza las promociones necesarias de alguna declaración. Tenga esto en cuenta cuidadosamente.

    En los ejemplos anteriores, si resulta que solo hay declaraciones para un "extern int b;" en todas las unidades de compilación enlazadas, el enlazador tiene la responsabilidad de crear una definición. Tenga en cuenta que este es un evento de tiempo de enlace. El compilador es completamente inconsciente durante la compilación. Sólo puede determinarse en el momento del enlace, si se puede promover una declaración de este tipo.

    El compilador es consciente de que "static int a;" el enlazador no puede promoverlo en tiempo de enlace, por lo que en realidad es una definición en tiempo de compilación .

Gran respuesta, +1! Solo un punto menor: podrían haber usado extern, y sería la forma C más adecuada de hacerlo: declarar la variable externen un archivo de encabezado para que se incluya varias veces en el programa y definirla en algún archivo que no sea de encabezado para compilar y vinculado exactamente una vez. Después de todo, PORTB se supone que es exactamente una instancia de la variable a la que se pueden referir diferentes cu. Entonces, el uso de staticaquí es una especie de atajo que tomaron para evitar necesitar otro archivo .c además del archivo de encabezado.
También me gustaría señalar que una variable estática declarada dentro de una función no cambia entre las llamadas de función, lo que puede ser útil para funciones que necesitan retener algún tipo de información de estado (lo he usado específicamente para este propósito en el pasado).
@Peter Creo que dije eso. ¿Pero tal vez no tan bien como te hubiera gustado?
@JimmyB No, no podrían haber usado 'externo', en su lugar, para las declaraciones de variables de función que se comportan como lo hace 'estático'. 'extern' ya es una opción para declaraciones de variables (no definiciones) dentro de cuerpos de funciones y tiene un propósito diferente: proporcionar acceso en tiempo de enlace a variables definidas fuera de cualquier función. Pero es posible que yo también esté malinterpretando tu punto.
El enlace @JimmyB Extern definitivamente sería posible, aunque no sé si es "más apropiado". Una consideración es que el compilador puede emitir un código más optimizado si la información se encuentra en la unidad de traducción. Para escenarios integrados, guardar ciclos en cada declaración de IO puede ser un gran problema.
Para agregar: recuerde que la directiva #include es el preprocesador, y esto podría crear múltiples PORTB en toda la compilación cuando no lo detectan los protectores de inclusión . Esto es lo que cuenta como ocurrencias múltiples.
@CortAmmon Creo que es la forma "más adecuada" porque, en este caso, la staticforma solo funciona porque se basa en la extensión (no portátil) ( @ 0x06;) que permite asignar múltiples instancias (estáticas) de una variable a una sola memoria ubicación. Usar externeso no sería una preocupación.
@jonk De hecho, creo que no me entendiste. Tenga en cuenta que no estamos hablando de declaraciones/definiciones de variables dentro de funciones, sino globales . Observe el significado diferente de staticpara variables globales en comparación con variables locales de función.
"Técnicamente, con 'externo', la regla general sería que solo UNA unidad de compilación especificaría el valor y las demás no deberían". - Esto debería dejar claro que el @ 0x06es parte de la definición de la variable, no de su declaración. Solo puede haber una definición de una variable (no estática) en un programa, cualquier otra cosa produce un error en el momento del enlace (o antes). Puede declarar la variable (no estática) varias veces a través de extern, pero solo puede haber una definición.
@JimmyB Dejaré sus comentarios para que otros los interpreten. He escrito un compilador de C antes. Sé un poco más que muchos de esa experiencia. Pero no estoy seguro de lo que estás diciendo. Sin embargo, puede ser el tiempo que tengo que pasar tratando de analizarlo. Cuando tengo un momento y la inclinación, puedo tratar de entenderlo. Independientemente, creo que lo que escribí sigue siendo fiel a mis experiencias y desearía haber seguido sus motivaciones y comentarios.
@JimmyB Y sí, soy MUY consciente de la diferencia entre declaraciones y definiciones, especialmente en lo que respecta a variables globales y externas. De nuevo, no entiendo tu problema con mi escritura. Pero me encantaría mejorar lo que escribí, lo que significa que me encantaría entender tus críticas (si eso es lo que son).
Realmente no quise criticar :) Y después de volver a leer tu respuesta, noté que realmente eras tú quien señalaba los diferentes significados de static. Mi punto es solo que para lo que se usa la declaración estática (y definición) en el archivo de encabezado en este caso, es decir, hacer referencia a la misma variable/ubicación de memoria de múltiples archivos .c, solo funciona debido a la (no portátil , no- estándar) @ 0x06;especificador de dirección, mientras que la forma "adecuada" de C sería usar externen la(s) declaración(es) y una definición separada y única de la variable. Sin el @ 0x06especificador de dirección, cada
@JimmyB Si me lee detenidamente, debería ver dónde digo exactamente lo que creo que está diciendo. Mira más cerca del final. ¿Quizás el tercer párrafo desde abajo?
la instancia estática de la variable se asignaría a su propia ubicación de memoria separada, que se evita con extern.
Vuelva a leerlo ahora, el punto en el que no estoy de acuerdo es el párrafo que comienza con "Si hubieran usado 'externo', podría plantear un problema". - externno habría planteado un problema, porque se asegura de que siempre haya una sola definición de la variable.
@JimmyB No estoy diciendo que 'externo' sea un problema. De hecho, usted y yo estamos de acuerdo exactamente en que no es un problema. Siempre y cuando solo definas una vez, por supuesto. Estoy hablando de por qué el autor puede haber elegido un camino diferente, usando 'estático' en su lugar. Eso es todo.

staticLos correos electrónicos no son visibles fuera de la unidad de compilación actual o "unidad de traducción". Esto no es lo mismo que el mismo archivo .

Tenga en cuenta que incluye el archivo de encabezado en cualquier archivo de origen en el que pueda necesitar las variables declaradas en el encabezado. Esta inclusión hace que el archivo de encabezado sea parte de la unidad de traducción actual y (una instancia de) la variable visible en su interior.

Gracias por su respuesta. "Unidad de compilación", lo siento, no entiendo, ¿puede explicar ese término? Déjame hacerte una pregunta más, incluso si queremos usar variables y funciones escritas dentro de otro archivo, primero debemos INCLUIR ese archivo en nuestro ARCHIVO FUENTE principal. Entonces, ¿por qué la palabra clave "estática volátil" en ese archivo de encabezado?
Gracias por todas las explicaciones, pero todavía no entiendo por qué la palabra clave estática es para variables globales. Escribí un programa que incluye un archivo de encabezado "includer.h" en C++. Dentro de ese archivo identifiqué static int var= 56 y una función static void say_hello(). Después de incluir este archivo en mi archivo fuente principal, puedo usar funciones y variables. Pero en el foro se dice así: "Las variables globales estáticas no son visibles fuera del archivo C en el que están definidas". "Las funciones estáticas no son visibles fuera del archivo C en el que están definidas".
@ElectroVoyager; si incluye el mismo encabezado que contiene una declaración estática en varios archivos fuente c, cada uno de esos archivos tendrá una variable estática con el mismo nombre, pero no son la misma variable .
Desde el enlace @JimmyB: Files included by using the #include preprocessor directive become part of the compilation unit.cuando incluye su archivo de encabezado (.h) en un archivo .c, piense que inserta el contenido del encabezado en el archivo fuente y ahora, esta es su unidad de compilación. Si declara esa variable o función estática en un archivo .c, puede usarlas solo en el mismo archivo, que al final será otra unidad de compilación.
El término correcto es unidad de traducción , no "unidad de compilación". Consulte 5.1.1.1/1: " Un archivo de origen junto con todos los encabezados y archivos de origen incluidos a través de la directiva de preprocesamiento #includese conoce como unidad de traducción de preprocesamiento . Después del preprocesamiento, una unidad de traducción de preprocesamiento se denomina unidad de traducción ".

Intentaré resumir los comentarios y la respuesta de @JimmyB con un ejemplo explicativo:

Supongamos que este conjunto de archivos:

prueba_estática.c:

#include <stdio.h>
#if USE_STATIC == 1
    #include "static.h"
#else
    #include "no_static.h"
#endif

void var_add_one();

void main(){

    say_hello();
    printf("var is %d\n", var);
    var_add_one();
    printf("now var is %d\n", var);
}

estática.h:

static int var=64;
static void say_hello(){
    printf("Hello!!!\n");
};

no_static.h:

int var=64;
void say_hello(){
    printf("Hello!!!\n");
};

static_src.c:

#include <stdio.h>

#if USE_STATIC == 1
    #include "static.h"
#else
    #include "no_static.h"
#endif

void var_add_one(){
    var = var + 1;
    printf("Added 1 to var: %d\n", var);
    say_hello();
}

Puede compilar y ejecutar el código usando gcc -o static_test static_src.c static_test.c -DUSE_STATIC=1; ./static_testel encabezado estático o gcc -o static_test static_src.c static_test.c -DUSE_STATIC=0; ./static_testel encabezado no estático.

Tenga en cuenta que aquí hay dos unidades de compilación: static_src y static_test. Cuando utilice la versión estática del encabezado ( ), estará disponible -DUSE_STATIC=1una versión de vary para cada unidad de compilación, es decir, ambas unidades pueden usarlas, pero verifique que aunque la función incrementa su variable, cuando la función principal imprime su variable , todavía es 64:say_hellovar_add_one() var var

$ gcc -o static_test static_src.c static_test.c -DUSE_STATIC=1; ./static_test                                                                                                                       14:33:12
Hello!!!
var is 64
Added 1 to var: 65
Hello!!!
now var is 64

Ahora, si intenta compilar y ejecutar el código, utilizando una versión no estática ( -DUSE_STATIC=0), arrojará un error de enlace debido a la definición de variable duplicada:

$ gcc -o static_test static_src.c static_test.c -DUSE_STATIC=0; ./static_test                                                                                                                       14:35:30
/tmp/ccLBy1s7.o:(.data+0x0): multiple definition of `var'
/tmp/ccV6izKJ.o:(.data+0x0): first defined here
/tmp/ccLBy1s7.o: In function `say_hello':
static_test.c:(.text+0x0): multiple definition of `say_hello'
/tmp/ccV6izKJ.o:static_src.c:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status
zsh: no such file or directory: ./static_test

Espero que esto pueda ayudarte a aclarar este asunto.

#include pic.haproximadamente significa "copiar el contenido de pic.h en el archivo actual". Como resultado, cada archivo que incluye pic.hobtiene su propia definición local de PORTB.

Tal vez se pregunte por qué no existe una única definición global de PORTB. La razón es bastante simple: solo puede definir una variable global en un archivo C, por lo que si desea usarla PORTBen varios archivos en su proyecto, necesitará pic.huna declaración de PORTBy pic.csu definición . Permitir que cada archivo C defina su propia copia PORTBfacilita la creación de código, ya que no tiene que incluir en su proyecto archivos que no escribió.

Un beneficio adicional de las variables estáticas frente a las globales es que obtiene menos conflictos de nombres. El archivo AC que no usa ninguna característica de hardware de MCU (y por lo tanto no incluye pic.h) puede usar el nombre PORTBpara su propio propósito. No es que sea una buena idea hacerlo a propósito, pero cuando desarrolla, por ejemplo, una biblioteca matemática independiente de MCU, se sorprenderá de lo fácil que es reutilizar accidentalmente un nombre que usa una de las MCU.

"te sorprendería lo fácil que es reutilizar accidentalmente un nombre que usa una de las MCU". Me atrevo a esperar que todas las bibliotecas matemáticas usen solo nombres en minúsculas, y que todos los entornos de MCU solo usen mayúsculas para registrarse nombres
@vsz LAPACK para uno está lleno de nombres históricos en mayúsculas.

Ya hay algunas buenas respuestas, pero creo que la causa de la confusión debe abordarse de manera simple y directa:

La declaración PORTB no es C estándar. Es una extensión del lenguaje de programación C que solo funciona con el compilador PIC. La extensión es necesaria porque los PIC no se diseñaron para admitir C.

El uso de la staticpalabra clave aquí es confuso porque nunca se usaría staticde esa manera en código normal. Para una variable global, usaría externen el encabezado, no static. Pero PORTB no es una variable normal . Es un truco que le dice al compilador que use instrucciones de ensamblaje especiales para registrar IO. Declarar PORTB staticayuda a engañar al compilador para que haga lo correcto.

Cuando se usa en el ámbito del archivo, staticlimita el ámbito de la variable o función a ese archivo. "Archivo" significa el archivo C y cualquier cosa copiada en él por el preprocesador. Cuando usa #include, está copiando código en su archivo C. Es por eso que usar staticun encabezado no tiene sentido: en lugar de una variable global, cada archivo que #incluye el encabezado obtendría una copia separada de la variable.

Contrariamente a la creencia popular, staticsiempre significa lo mismo: asignación estática con alcance limitado. Esto es lo que sucede con las variables antes y después de ser declaradas static:

+------------------------+-------------------+--------------------+
| Variable type/location |    Allocation     |       Scope        |
+------------------------+-------------------+--------------------+
| Normal in file         | static            | global             |
| Normal in function     | automatic (stack) | limited (function) |
| Static in file         | static            | limited (file)     |
| Static in function     | static            | limited (function) |
+------------------------+-------------------+--------------------+

Lo que lo hace confuso es que el comportamiento predeterminado de las variables depende de dónde estén definidas.

La razón por la que el archivo principal puede ver la definición de puerto "estático" es por la directiva #include. Esa directiva es equivalente a insertar el archivo de encabezado completo en su código fuente en la misma línea que la directiva misma.

El compilador microchip XC8 trata los archivos .c y .h exactamente de la misma manera, por lo que puede colocar sus definiciones de variables en cualquiera de ellos.

Normalmente, un archivo de encabezado contiene una referencia "externa" a las variables que se definen en otro lugar (generalmente un archivo .c).

Las variables de puerto debían especificarse en direcciones de memoria específicas que coincidieran con el hardware real. Entonces, una definición real (no externa) necesitaba existir en alguna parte.

Solo puedo adivinar por qué la corporación Microchip eligió poner las definiciones reales en el archivo .h. Una suposición probable es que solo querían un archivo (.h) en lugar de 2 (.h y .c) (para facilitar las cosas al usuario).

Pero si coloca las definiciones de variables reales en un archivo de encabezado y luego incluye ese encabezado en varios archivos de origen, el enlazador se quejará de que las variables se definen varias veces.

La solución es declarar las variables como estáticas, luego cada definición se trata como local para ese archivo de objeto y el enlazador no se quejará.