¿Cuál es la diferencia entre lo estático y lo externo en C?

¿Cuál es la diferencia entre lo static y lo extern en C?

De http://wiki.answers.com/Q/What_is_the_difference_between_static_and_extern :

La clase de almacenamiento estático se usa para declarar un identificador que es una variable local ya sea para una función o un archivo y que existe y retiene su valor después de que el control pase desde donde se declaró. Esta clase de almacenamiento tiene una duración que es permanente. Una variable declarada de esta clase conserva su valor de una llamada de la función a la siguiente. El scope es local. Una variable es conocida solo por la función que está declarada dentro o si se declara globalmente en un archivo, es conocida o vista solo por las funciones dentro de ese archivo. Esta clase de almacenamiento garantiza que la statement de la variable también inicializa la variable a cero o a todos los bits desactivados.

La clase de almacenamiento externo se usa para declarar una variable global que será conocida por las funciones en un archivo y será conocida por todas las funciones en un progtwig. Esta clase de almacenamiento tiene una duración que es permanente. Cualquier variable de esta clase conserva su valor hasta que sea cambiada por otra asignación. El scope es global. Una variable puede ser conocida o vista por todas las funciones dentro de un progtwig.

static significa que una variable se conocerá globalmente solo en este archivo. extern significa que una variable global definida en otro archivo también se conocerá en este archivo y también se utiliza para acceder a las funciones definidas en otros archivos.

Una variable local definida en una función también se puede declarar como static . Esto provoca el mismo comportamiento como si se definiera como una variable global, pero solo es visible dentro de la función. Esto significa que obtiene una variable local cuyo almacenamiento es permanente y, por lo tanto, conserva su valor entre las llamadas a esa función.

No soy experto en C, por lo que podría estar equivocado al respecto, pero así es como entiendo static y extern . Esperemos que alguien con más conocimientos sea capaz de proporcionarle una mejor respuesta.

EDITAR: respuesta corregida según el comentario proporcionado por JeremyP.

Puede aplicar static tanto a variables como a funciones. Hay dos respuestas que discuten el comportamiento de static y extern con respecto a las variables, pero ninguna de las dos cubre realmente las funciones. Este es un bash de rectificar esa deficiencia.

TL; DR

  • Utilice las funciones estáticas siempre que sea posible.
  • Solo declarar funciones externas en los encabezados.
  • Use los encabezados donde se definen las funciones y donde se usan las funciones.
  • No declarar funciones dentro de otras funciones.
  • No explote la extensión GCC con definiciones de funciones anidadas dentro de otras funciones.

Funciones externas

De forma predeterminada, las funciones en C son visibles fuera de la unidad de traducción (TU, básicamente el archivo fuente C y los encabezados incluidos) en el que están definidas. Dichas funciones pueden llamarse por nombre desde cualquier código que notifique al comstackdor que la función existe, generalmente mediante una statement en un encabezado.

Por ejemplo, el encabezado hace visibles declaraciones de funciones tales como printf() , fprintf() , scanf() , fscanf() , fopen() , fclose() , etc. Si un archivo fuente incluye el encabezado, puede llamar a las funciones. Cuando el progtwig está vinculado, se debe especificar la biblioteca correcta para satisfacer la definición de la función. Afortunadamente, el comstackdor de C proporciona automáticamente la biblioteca que proporciona (la mayoría de) las funciones en la biblioteca de C estándar (y generalmente proporciona muchas más funciones que solo esas). La advertencia ‘la mayor parte’ se aplica porque en muchos sistemas (Linux, por ejemplo, pero no en macOS), si usa las funciones declaradas en el , debe vincularse con la biblioteca maths (biblioteca ‘math’ si eres estadounidense), que generalmente se indica con la opción -lm en la línea de comandos del vinculador.

Tenga en cuenta que las funciones externas deben ser declaradas en los encabezados. Cada función externa debe declararse en un encabezado, pero un encabezado puede declarar muchas funciones. El encabezado debe usarse tanto en la TU donde se define cada función como en cada TU que usa la función. Nunca debe tener que escribir una statement para una función global en un archivo de origen (a diferencia de un archivo de encabezado): debe haber un encabezado para declarar la función y debe usar ese encabezado para declararlo.

Funciones estáticas

Como alternativa a las funciones generalmente visibles, puede hacer que sus propias funciones sean static . Esto significa que no se puede llamar a la función por su nombre desde fuera de la TU en la que está definida. Es una función oculta.

La principal ventaja de las funciones estáticas es que oculta detalles que el mundo exterior no necesita conocer. Es una técnica básica pero poderosa para ocultar información. También sabe que si una función es estática, no necesita buscar usos de la función fuera de la TU actual, lo que puede simplificar enormemente la búsqueda. Sin embargo, si las funciones son static , puede haber múltiples TU, cada una de las cuales contiene una definición de una función con el mismo nombre; cada TU tiene su propia función, que puede o no hacer lo mismo que una función con el mismo nombre en un TU diferente.

En mi código, califico todas las funciones excepto main() con la palabra clave static de forma predeterminada, a menos que haya un encabezado que declare la función. Si posteriormente necesito usar la función desde otro lugar, se puede agregar al encabezado apropiado y la palabra clave static eliminará de su definición.

Declarar funciones dentro de otras funciones

Es posible, pero muy desaconsejable, declarar una función dentro del scope de otra función. Dichas declaraciones van en contra de las máximas de desarrollo ágil como SPOT (punto único de la verdad) y DRY (no se repita). También son una responsabilidad de mantenimiento.

Sin embargo, si lo desea, puede escribir código como:

 extern int processor(int x); int processor(int x) { extern int subprocess(int); int sum = 0; for (int i = 0; i < x; i++) sum += subprocess((x + 3) % 7); return sum; } extern int subprocess(int y); int subprocess(int y) { return (y * 13) % 37; } 

La statement en processor() suficiente para que use subprocess() , pero por lo demás no es satisfactoria. La statement extern antes de la definición es necesaria si usa las opciones del comstackdor GCC como:

 $ gcc -O3 -g -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes -Wstrict-prototypes \ > -c process.c process.c:12:5: error: no previous prototype for 'subprocess' [-Werror=missing-prototypes] int subprocess(int y) ^~~~~~~~~~ cc1: all warnings being treated as errors $ 

Esto es, me parece, una buena disciplina, similar a lo que C ++ impone. Es otra razón por la que hago que la mayoría de las funciones sean estáticas y defino las funciones antes de que se usen. La alternativa es declarar funciones estáticas en la parte superior del archivo y luego definirlas en el orden que parezca apropiado. Hay algunas ventajas para ambas técnicas; Prefiero evitar la necesidad de declarar y definir la misma función en el archivo definiendo antes de usar.

Tenga en cuenta que no puede declarar una función static dentro de otra función, y si intenta definir una función como subprocess() como una función estática, el comstackdor genera un error:

 process.c:12:16: error: static declaration of 'subprocess' follows non-static declaration static int subprocess(int y) ^~~~~~~~~~ process.c:5:20: note: previous declaration of 'subprocess' was here extern int subprocess(int); ^~~~~~~~~~ 

Dado que las funciones que son visibles externamente deben declararse en un encabezado, no hay necesidad de declararlas dentro de una función, por lo que nunca debe encontrarse con esto como un problema.

De nuevo, el extern no es necesario en la statement de función dentro de la función; si se omite, se asume. Esto puede provocar un comportamiento inesperado en los progtwigs para principiantes aquí en SO: a veces se encuentra una statement de función donde se pretendía realizar una llamada.

Con GCC, la opción -Wnested-externs identifica las declaraciones extern anidadas.

Llamado por nombre vs llamado por puntero

Si tienes una disposición nerviosa, deja de leer ahora. Esto se pone peludo!

El comentario 'llamado por nombre' significa que si tiene una statement como:

 extern int function(void); 

Puedes escribir en tu código:

 int i = function(); 

y el comstackdor y el enlazador ordenarán las cosas para que se llame a la función y se use el resultado. El extern en la statement de la función es opcional pero explícito. Normalmente lo uso en un archivo de encabezado para coincidir con la statement de esas raras variables globales, donde el extern no es opcional sino obligatorio. Muchas personas no están de acuerdo conmigo en esto; haz lo que quieras (o debes).

Ahora, ¿qué pasa con las funciones estáticas? Supongamos que TU reveal.c define una función static void hidden_function(int) { … } . Luego, en otra openness.c TU.c, no puedes escribir:

 hidden_function(i); 

Solo la TU que define la función oculta puede usarla directamente. Sin embargo, si hay una función en reveal.c que devuelve un puntero a la función hidden_function() , entonces el código openness.c puede llamar a esa otra función (por su nombre) para obtener un puntero a la función oculta.

reveal1.h

 extern void (*(revealer(void)))(int); 

Obviamente, esa es una función que no toma argumentos y devuelve un puntero a una función que toma un argumento int y no devuelve ningún valor. No; no es bonito Una de las veces que tiene sentido usar typedef en punteros es con punteros a funciones ( reveal2.h ):

 typedef void (*HiddenFunctionType)(int); extern HiddenFunctionType revealer(void); 

Allí: mucho más fácil de entender.

Consulte ¿Es una buena idea escribir los punteros para una discusión general sobre el tema de typedef y los punteros? El breve resumen es "no es una buena idea, excepto quizás con punteros de función".

reveal1.c

 #include  #include "reveal1.h" static void hidden_function(int x) { printf("%s:%s(): %d\n", __FILE__, __func__, x); } extern void (*(revealer(void)))(int) { return hidden_function; } 

Sí, es legítimo (pero muy inusual) definir la función con un extern explícito; muy rara vez lo hago, pero aquí enfatiza el papel del extern y lo contrasta con la static . El hidden_function() puede ser devuelto por revealer() , y puede ser llamado por el código dentro de reveal.c . Puede eliminar el extern sin cambiar el significado del progtwig.

openness1.c

 #include  #include "reveal1.h" int main(void) { void (*revelation)(int) = revealer(); printf("%s:%s: %d\n", __FILE__, __func__, __LINE__); (*revelation)(37); return 0; } 

Este archivo no puede contener una llamada directa por nombre a hidden_function() porque está oculto en la otra TU. Sin embargo, la función de revealer() declarada en reveal.h puede llamarse por su nombre y devuelve un puntero a la función oculta, que luego se puede usar.

reveal2.c

 #include  #include "reveal2.h" static void hidden_function(int x) { printf("%s:%s(): %d\n", __FILE__, __func__, x); } extern HiddenFunctionType revealer(void) { return hidden_function; } 

openness2.c

 #include  #include "reveal2.h" int main(void) { HiddenFunctionType revelation = revealer(); printf("%s:%s: %d\n", __FILE__, __func__, __LINE__); (*revelation)(37); return 0; } 

Salidas de muestra

¡No es la salida más emocionante del mundo!

 $ openness1 openness1.c:main: 7 reveal1.c:hidden_function(): 37 $ openness2 openness2.c:main: 7 reveal2.c:hidden_function(): 37 $ 

Ambos modificadores tienen algo que ver con la asignación de memoria y la vinculación de su código. El estándar C [3] se refiere a ellos como especificadores de clase de almacenamiento. El uso de estos le permite especificar cuándo asignar memoria para su objeto y / o cómo vincularlo con el rest del código. Veamos qué es exactamente lo que hay que especificar primero.

Enlace en C

Hay tres tipos de enlace: externo, interno y ninguno. Cada objeto declarado en su progtwig (es decir, variable o función) tiene algún tipo de enlace, generalmente especificado por las circunstancias de la statement. La vinculación de un objeto dice cómo se propaga el objeto a través de todo el progtwig. La vinculación puede ser modificada por palabras clave externas y estáticas.

Enlace externo

Los objetos con enlace externo se pueden ver (y acceder) a través de todo el progtwig a través de los módulos. Todo lo que declare en el ámbito de archivo (o global) tiene un enlace externo de forma predeterminada. Todas las variables globales y todas las funciones tienen enlaces externos de forma predeterminada.

Enlace interno

Las variables y funciones con enlace interno son accesibles solo desde una unidad de comstackción, la unidad en la que se definieron. Los objetos con enlace interno son privados para un solo módulo.

Ninguna vinculación

Ninguna vinculación hace que los objetos sean completamente privados al scope en el que se definieron. Como su nombre indica, no se realiza vinculación. Esto se aplica a todas las variables locales y parámetros de función, a los que solo se puede acceder desde dentro del cuerpo de la función, en ningún otro lugar.

Duración del almacenamiento

Otra área afectada por estas palabras clave es la duración del almacenamiento, es decir, la vida útil del objeto a través del tiempo de ejecución del progtwig. Hay dos tipos de duración de almacenamiento en C: estática y automática.

Los objetos con duración de almacenamiento estático se inicializan en el inicio del progtwig y permanecen disponibles durante todo el tiempo de ejecución. Todos los objetos con enlaces externos e internos también tienen una duración de almacenamiento estático. La duración del almacenamiento automático es predeterminada para los objetos sin vinculación. Estos objetos se asignan al ingresar al bloque en el que se definieron y eliminaron cuando finaliza la ejecución del bloque. La duración del almacenamiento puede ser modificada por la palabra clave static.

Estático

Hay dos usos diferentes de esta palabra clave en el lenguaje C. En el primer caso, static modifica el enlace de una variable o función. La norma ANSI establece:

Si la statement de un identificador para un objeto o una función tiene scope de archivo y contiene el estático especificador de la clase de almacenamiento, el identificador tiene un enlace interno.

Esto significa que si usa la palabra clave estática en un nivel de archivo (es decir, no en una función), cambiará el enlace del objeto a interno, haciéndolo privado solo para el archivo o, más precisamente, unidad de comstackción.

 /* This is file scope */ int one; /* External linkage. */ static int two; /* Internal linkage. */ /* External linkage. */ int f_one() { return one; } /* Internal linkage. */ static void f_two() { two = 2; } int main(void) { int three = 0; /* No linkage. */ one = 1; f_two(); three = f_one() + two; return 0; } 

La variable y la función () tendrán un enlace interno y no serán visibles desde ningún otro módulo.

El otro uso de la palabra clave estática en C es para especificar la duración del almacenamiento. La palabra clave se puede utilizar para cambiar la duración del almacenamiento automático a estático. Una variable estática dentro de una función se asigna solo una vez (al inicio del progtwig) y, por lo tanto, mantiene su valor entre invocaciones

 #include  void foo() { int a = 10; static int sa = 10; a += 5; sa += 5; printf("a = %d, sa = %d\n", a, sa); } int main() { int i; for (i = 0; i < 10; ++i) foo(); } 

La salida se verá así:

 a = 15, sa = 15 a = 15, sa = 20 a = 15, sa = 25 a = 15, sa = 30 a = 15, sa = 35 a = 15, sa = 40 a = 15, sa = 45 a = 15, sa = 50 a = 15, sa = 55 a = 15, sa = 60 

Externo

La palabra clave extern denota que "este identificador se declara aquí, pero se define en otra parte". En otras palabras, le dice al comstackdor que alguna variable estará disponible, pero su memoria está asignada en otro lugar. La cosa es, ¿dónde? Veamos primero la diferencia entre la statement y la definición de algún objeto. Al declarar una variable, usted dice qué tipo de variable es y qué nombre se usa más adelante en su progtwig. Por ejemplo puedes hacer lo siguiente:

 extern int i; /* Declaration. */ extern int i; /* Another declaration. */ 

La variable virtualmente no existe hasta que la definas (es decir, asigna memoria para ella). La definición de una variable se ve así:

 int i = 0; /* Definition. */ 

Puede poner tantas declaraciones como desee en su progtwig, pero solo una definición dentro de un ámbito. Aquí hay un ejemplo que viene del estándar C:

 /* definition, external linkage */ int i1 = 1; /* definition, internal linkage */ static int i2 = 2; /* tentative definition, external linkage */ int i3; /* valid tentative definition, refers to previous */ int i1; /* valid tenative definition, refers to previous */ static int i2; /* valid tentative definition, refers to previous */ int i3 = 3; /* refers to previous, whose linkage is external */ extern int i1; /* refers to previous, whose linkage is internal */ extern int i2; /* refers to previous, whose linkage is external */ extern int i4; int main(void) { return 0; } 

Esto se comstackrá sin errores.

Resumen

Recuerde que estático: el especificador de la clase de almacenamiento y la duración del almacenamiento estático son dos cosas diferentes. La duración del almacenamiento es un atributo de los objetos que, en algunos casos, puede modificarse mediante estática, pero la palabra clave tiene múltiples usos.

También la palabra clave externa y el enlace externo representan dos áreas diferentes de interés. La vinculación externa es un atributo de objeto que dice que se puede acceder desde cualquier parte del progtwig. La palabra clave, por otro lado, denota que el objeto declarado no está definido aquí, sino en otro lugar.