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 PORTB
ser 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?
La palabra clave 'estático' en C tiene dos significados fundamentalmente diferentes.
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.
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.
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.)
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'.)
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 .
extern
, y sería la forma C más adecuada de hacerlo: declarar la variable extern
en 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 static
aquí es una especie de atajo que tomaron para evitar necesitar otro archivo .c además del archivo de encabezado.static
forma 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 extern
eso no sería una preocupación.static
para variables globales en comparación con variables locales de función.@ 0x06
es 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.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 extern
en la(s) declaración(es) y una definición separada y única de la variable. Sin el @ 0x06
especificador de dirección, cadaextern
.extern
no habría planteado un problema, porque se asegura de que siempre haya una sola definición de la variable.static
Los 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.
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.#include
se 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_test
el encabezado estático o gcc -o static_test static_src.c static_test.c -DUSE_STATIC=0; ./static_test
el 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=1
una versión de var
y 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_hello
var_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.h
aproximadamente significa "copiar el contenido de pic.h en el archivo actual". Como resultado, cada archivo que incluye pic.h
obtiene 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 PORTB
en varios archivos en su proyecto, necesitará pic.h
una declaración de PORTB
y pic.c
su definición . Permitir que cada archivo C defina su propia copia PORTB
facilita 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 PORTB
para 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.
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 static
palabra clave aquí es confuso porque nunca se usaría static
de esa manera en código normal. Para una variable global, usaría extern
en 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 static
ayuda a engañar al compilador para que haga lo correcto.
Cuando se usa en el ámbito del archivo, static
limita 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 static
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 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á.
Gommer
finbarr
jimmyb
static
los globales son visibles dentro de toda la unidad de compilación única y no se exportan más allá de eso. Son muy parecidosprivate
a 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 realmentestatic
. Esto también reduce el "golpeteo" del espacio de nombres global del programa.Pedro Mortensen