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 utiliza para almacenar el valor 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 supe sobre el lenguaje C, una "variable global estática" no es visible para ningún otro archivo, o simplemente, las funciones / variables globales estáticas no se pueden usar fuera del archivo actual.
Entonces, ¿cómo puede PORTB
ser visible esta palabra clave 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?
static
los globales son visibles dentro de la unidad de compilación individual completa, y no se exportan más allá de eso. Son muy parecidos a losprivate
miembros de una clase en OOP. Es decir, todas las variables que deben compartirse entre diferentes funciones dentro de una unidad de compilación, pero no se supone que sean visibles fuera de ese cu realmente deberían serstatic
. Esto también reduce el "golpeteo" del espacio de nombres global del programa.Respuestas:
La palabra clave 'static' 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 las cosas, pero esos efectos son en su mayoría inútiles (a menos que le guste excavar en 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 la variable o el nombre de la función se puedan vincular con el mismo nombre que se encuentra en otras unidades de compilación. Por lo tanto, puede considerar que 'static' requiere que la variable o el nombre se encuentren dentro de la unidad de compilación actual, mientras que 'extern' permite el enlace de unidades de compilación cruzada.
Vida estática
La duración estática significa que la variable existe a lo largo de la duración del programa (por mucho tiempo que sea). Cuando usa 'estática' 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, cada vez que lo he experimentado, que la variable se crea antes de que comience main () y no se destruye después. Ni siquiera cuando la ejecución de la función se completa y vuelve a su llamador. Y al igual que las variables de vida útil estáticas declaradas fuera de las funciones, se inicializan en el mismo momento, antes de que main () comience, a un cero semántico (si no se proporciona ninguna inicialización) oa un valor explícito específico, si se proporciona.
Esto es diferente de las variables de función de tipo 'automático', que se crean nuevas (o, como si fueran nuevas) cada vez que se ingresa la función y luego se destruyen (o, como si se destruyeron) 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 de un cuerpo de 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 de duración estática definidas dentro de las funciones tienen el mismo alcance que otras variables 'automáticas' definidas dentro de los cuerpos de las funciones: alcance de funciones.
Resumen
Entonces, la palabra clave 'estática' tiene contextos diferentes 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 más en el lenguaje era más importante (que los argumentos de lo contrario).
(Todas las variables declaradas fuera de las funciones tienen una vida útil estática y no necesitan la palabra clave 'static' para que eso sea cierto. Por lo tanto, este tipo de palabra clave se liberó para usarse allí para significar algo completamente diferente: 'visible solo en una sola compilación unidad. 'Es un truco, de algún tipo.)
Nota específica
La palabra 'estática' aquí debe interpretarse en el sentido de que el vinculador no intentará hacer coincidir múltiples ocurrencias 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 generalmente es una dirección) de PORTB. Entonces, el enlazador recibe la dirección y no necesita encontrar una. Si tuviera dos unidades de compilación usando esta línea, cada una terminaría apuntando al mismo lugar, de todos modos. Entonces 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? ¿Quejar? ¿O? (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).
Es más fácil etiquetarlo como 'estático', evitando que el enlazador se preocupe por los conflictos, y simplemente culpe por cualquier error en las direcciones que no coinciden con quien cambió la dirección a algo que no debería ser.
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 generalmente no (vea la nota de viñeta a continuación) crea un objeto.
Las siguientes son definiciones y declaraciones:
Las siguientes no son definiciones, sino solo declaraciones:
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 "por lo general", aconsejado. En algunos casos, una declaración puede crear un objeto y, por lo tanto, el vinculador la promueve a una definición (nunca el compilador). Por lo tanto, incluso en este raro caso, el compilador C todavía piensa que la declaración es solo una declaración. Es la fase de enlace que hace 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 vinculadas, el vinculador 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. Solo se puede determinar en el momento del enlace, si se promueve una declaración de este tipo.
El compilador es consciente de que "static int a;" no puede ser promovido por el enlazador durante el enlace, por lo que este hecho es una definición en tiempo de compilación .
fuente
extern
, y sería la forma más adecuada de hacerlo en C: declarar la variableextern
en un archivo de encabezado para incluirla varias veces en el programa y definirla en algún archivo sin 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 pueden referirse diferentes cu's. Por lo tanto, el uso destatic
here es una especie de acceso directo que tomaron para evitar necesitar otro archivo .c además del archivo de encabezado.static
s 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 fuente donde 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 dentro de él.
fuente
Files included by using the #include preprocessor directive become part of the compilation unit.
cuando incluya su archivo de encabezado (.h) en un archivo .c, piense en ello como insertar 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.Trataré de resumir los comentarios y la respuesta de @ JimmyB con un ejemplo explicativo:
Supongamos este conjunto de archivos:
static_test.c:
static.h:
no_static.h:
static_src.c:
Puede compilar y ejecutar el código usando
gcc -o static_test static_src.c static_test.c -DUSE_STATIC=1; ./static_test
para usar el encabezado estático ogcc -o static_test static_src.c static_test.c -DUSE_STATIC=0; ./static_test
para usar el encabezado no estático.Tenga en cuenta que aquí hay dos unidades de compilación: static_src y static_test. Cuando usa la versión estática del encabezado (
-DUSE_STATIC=1
), habrá una versión devar
ysay_hello
disponible para cada unidad de compilación, es decir, ambas unidades pueden usarlas, pero compruebe que aunque lavar_add_one()
función incremente suvar
variable, cuando la función principal imprime suvar
variable , sigue siendo 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:Espero que esto pueda ayudarte a aclarar este asunto.
fuente
#include pic.h
más o menos significa "copiar el contenido de pic.h en el archivo actual". Como resultado, cada archivo que incluyepic.h
obtiene su propia definición local dePORTB
.Quizás se pregunte por qué no existe una definición global única de
PORTB
. La razón es bastante simple: solo puede definir una variable global en una fichero C, por lo que si desea utilizarPORTB
en múltiples archivos en su proyecto, lo que se necesitapic.h
con una declaración dePORTB
ypic.c
con su definición . Permitir que cada archivo C defina su propia copiaPORTB
facilita la creación de código, ya que no tiene que incluir en los archivos de su proyecto que no escribió.Una ventaja adicional de las variables estáticas frente a las globales es que obtienes menos conflictos de nombres. El archivo AC que no usa ninguna característica de hardware MCU (y por lo tanto no incluye
pic.h
) puede usar el nombrePORTB
para su propio propósito. No es que sea una buena idea hacerlo a propósito, pero cuando desarrolle, por ejemplo, una biblioteca matemática independiente de MCU, se sorprenderá de lo fácil que es reutilizar accidentalmente un nombre que es utilizado por una de las MCU.fuente
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 el estándar C. 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 fueron diseñados para admitir C.
El uso de la
static
palabra clave aquí es confuso porque nunca se usaría destatic
esa manera en el código normal. Para una variable global, usaríaextern
en el encabezado, nostatic
. 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 PORTBstatic
ayuda a engañar al compilador para que haga lo correcto.Cuando se usa en el alcance del archivo,
static
limita el alcance de la variable o función a ese archivo. "Archivo" significa el archivo C y cualquier cosa copiada por el preprocesador. Cuando usa #include, está copiando el código en su archivo C. Es por eso que usarstatic
un 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,
static
siempre significa lo mismo: asignación estática con alcance limitado. Esto es lo que sucede con las variables antes y después de ser declaradasstatic
:Lo que lo hace confuso es que el comportamiento predeterminado de las variables depende de dónde estén definidas.
fuente
La razón por la que el archivo principal puede ver la definición de puerto "estático" se debe a 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 igual para que pueda colocar sus definiciones de variables en cualquiera de los dos.
Normalmente, un archivo de encabezado contiene referencias "externas" a 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 coinciden con el hardware real. Por lo tanto, una definición real (no externa) debía existir en alguna parte.
Solo puedo adivinar por qué Microchip Corporation 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 vinculador 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 vinculador no se quejará.
fuente