El uso extern
solo es relevante cuando el programa que está compilando consta de varios archivos fuente vinculados entre sí, donde algunas de las variables definidas, por ejemplo, en el archivo fuente file1.c
deben ser referenciadas en otros archivos fuente, como file2.c
.
Es importante comprender la diferencia entre definir una variable y declarar una variable :
Se declara una variable cuando se informa al compilador que existe una variable (y este es su tipo); no asigna el almacenamiento para la variable en ese punto.
Una variable se define cuando el compilador asigna el almacenamiento para la variable.
Puede declarar una variable varias veces (aunque una vez es suficiente); solo puede definirlo una vez dentro de un alcance determinado. Una definición de variable también es una declaración, pero no todas las declaraciones de variable son definiciones.
La mejor manera de declarar y definir variables globales
La manera limpia y confiable de declarar y definir variables globales es usar un archivo de encabezado para contener una extern
declaración de la variable.
El encabezado está incluido en el archivo fuente que define la variable y en todos los archivos fuente que hacen referencia a la variable. Para cada programa, un archivo fuente (y solo un archivo fuente) define la variable. Del mismo modo, un archivo de encabezado (y solo un archivo de encabezado) debe declarar la variable. El archivo de encabezado es crucial; permite la verificación cruzada entre TU independientes (unidades de traducción - piense en archivos fuente) y garantiza la coherencia.
Aunque hay otras formas de hacerlo, este método es simple y confiable. Se demuestra por file3.h
, file1.c
y file2.c
:
file3.h
extern int global_variable; /* Declaration of the variable */
file1.c
#include "file3.h" /* Declaration made available here */
#include "prog1.h" /* Function declarations */
/* Variable defined here */
int global_variable = 37; /* Definition checked against declaration */
int increment(void) { return global_variable++; }
file2.c
#include "file3.h"
#include "prog1.h"
#include <stdio.h>
void use_it(void)
{
printf("Global variable: %d\n", global_variable++);
}
Esa es la mejor manera de declarar y definir variables globales.
Los siguientes dos archivos completan la fuente de prog1
:
Los programas completos que se muestran usan funciones, por lo que las declaraciones de funciones se han infiltrado. Tanto C99 como C11 requieren que las funciones se declaren o definan antes de usarse (mientras que C90 no lo hizo, por buenas razones). Utilizo la palabra clave extern
delante de las declaraciones de funciones en los encabezados para mantener la coherencia, para que coincida con las extern
declaraciones delante de las variables en los encabezados. Muchas personas prefieren no usar extern
frente a las declaraciones de funciones; al compilador no le importa, y en última instancia, tampoco a mí, siempre y cuando sea coherente, al menos dentro de un archivo fuente.
prog1.h
extern void use_it(void);
extern int increment(void);
prog1.c
#include "file3.h"
#include "prog1.h"
#include <stdio.h>
int main(void)
{
use_it();
global_variable += 19;
use_it();
printf("Increment: %d\n", increment());
return 0;
}
prog1
usos prog1.c
, file1.c
, file2.c
, file3.h
y prog1.h
.
El archivo prog1.mk
es solo un archivo MAKE prog1
. Funcionará con la mayoría de las versiones make
producidas desde aproximadamente el cambio de milenio. No está vinculado específicamente a GNU Make.
prog1.mk
# Minimal makefile for prog1
PROGRAM = prog1
FILES.c = prog1.c file1.c file2.c
FILES.h = prog1.h file3.h
FILES.o = ${FILES.c:.c=.o}
CC = gcc
SFLAGS = -std=c11
GFLAGS = -g
OFLAGS = -O3
WFLAG1 = -Wall
WFLAG2 = -Wextra
WFLAG3 = -Werror
WFLAG4 = -Wstrict-prototypes
WFLAG5 = -Wmissing-prototypes
WFLAGS = ${WFLAG1} ${WFLAG2} ${WFLAG3} ${WFLAG4} ${WFLAG5}
UFLAGS = # Set on command line only
CFLAGS = ${SFLAGS} ${GFLAGS} ${OFLAGS} ${WFLAGS} ${UFLAGS}
LDFLAGS =
LDLIBS =
all: ${PROGRAM}
${PROGRAM}: ${FILES.o}
${CC} -o $@ ${CFLAGS} ${FILES.o} ${LDFLAGS} ${LDLIBS}
prog1.o: ${FILES.h}
file1.o: ${FILES.h}
file2.o: ${FILES.h}
# If it exists, prog1.dSYM is a directory on macOS DEBRIS = a.out core *~ *.dSYM RM_FR = rm -fr
clean:
${RM_FR} ${FILES.o} ${PROGRAM} ${DEBRIS}
Pautas
Reglas que deben romper los expertos solo, y solo con buenas razones:
Un archivo de encabezado solo contiene extern
declaraciones de variables, nunca
static
o definiciones de variables no calificadas.
Para cualquier variable dada, solo un archivo de encabezado lo declara (SPOT - Single Point of Truth).
Un archivo fuente nunca contiene extern
declaraciones de variables: los archivos fuente siempre incluyen el encabezado (único) que las declara.
Para cualquier variable dada, exactamente un archivo fuente define la variable, preferiblemente inicializándola también. (Aunque no hay necesidad de inicializar explícitamente a cero, no hace daño y puede hacer algo bueno, porque solo puede haber una definición inicializada de una variable global particular en un programa).
El archivo fuente que define la variable también incluye el encabezado para garantizar que la definición y la declaración sean consistentes.
Una función nunca debería necesitar declarar una variable usando extern
.
Evite las variables globales siempre que sea posible; utilice funciones en su lugar.
El código fuente y el texto de esta respuesta están disponibles en mi repositorio SOQ (Stack Overflow Questions) en GitHub en el subdirectorio src / so-0143-3204 .
Si no eres un programador de C experimentado, podrías (y quizás deberías) dejar de leer aquí.
No es una buena manera de definir variables globales
Con algunos (de hecho, muchos) compiladores de C, también puede salirse con lo que se llama una definición 'común' de una variable. 'Común', aquí, se refiere a una técnica utilizada en Fortran para compartir variables entre archivos fuente, usando un bloque COMÚN (posiblemente llamado). Lo que sucede aquí es que cada uno de varios archivos proporciona una definición tentativa de la variable. Mientras no más de un archivo proporcione una definición inicializada, los diversos archivos terminan compartiendo una definición común común de la variable:
file10.c
#include "prog2.h"
long l; /* Do not do this in portable code */
void inc(void) { l++; }
file11.c
#include "prog2.h"
long l; /* Do not do this in portable code */
void dec(void) { l--; }
file12.c
#include "prog2.h"
#include <stdio.h>
long l = 9; /* Do not do this in portable code */
void put(void) { printf("l = %ld\n", l); }
Esta técnica no se ajusta a la letra del estándar C y la 'regla de una definición': es un comportamiento oficialmente indefinido:
J.2 Comportamiento indefinido
Se utiliza un identificador con enlace externo, pero en el programa no existe exactamente una definición externa para el identificador, o el identificador no se utiliza y existen múltiples definiciones externas para el identificador (6.9).
§6.9 Definiciones externas ¶5
Una definición externa es una declaración externa que también es una definición de una función (que no sea una definición en línea) o un objeto. Si un identificador declarado con enlace externo se utiliza en una expresión (distinta de como parte del operando de una sizeof
o _Alignof
operador cuyo resultado es una constante entera), en alguna parte de todo el programa habrá exactamente una definición externa para el identificador; de lo contrario, no habrá más de uno. 161)
161) Por lo tanto, si un identificador declarado con enlace externo no se utiliza en una expresión, no necesita haber una definición externa para ello.
Sin embargo, el estándar C también lo incluye en el Anexo J informativo como una de las extensiones comunes .
J.5.11 Múltiples definiciones externas
Puede haber más de una definición externa para el identificador de un objeto, con o sin el uso explícito de la palabra clave extern; si las definiciones no están de acuerdo, o se inicializa más de una, el comportamiento no está definido (6.9.2).
Debido a que esta técnica no siempre es compatible, es mejor evitar usarla, especialmente si su código debe ser portátil . Usando esta técnica, también puedes terminar con un tipo de punteo involuntario.
Si uno de los archivos anteriores se declara l
como un en double
lugar de como un long
, los enlazadores inseguros de tipo de C probablemente no detectarán la falta de coincidencia. Si está en una máquina con 64 bits long
y double
ni siquiera recibirá una advertencia; en una máquina con 32 long
y 64 bits double
, probablemente reciba una advertencia sobre los diferentes tamaños: el enlazador usaría el tamaño más grande, exactamente como un programa Fortran tomaría el tamaño más grande de cualquier bloque común.
Tenga en cuenta que GCC 10.1.0, que se lanzó el 2020-05-07, cambia las opciones de compilación predeterminadas para usar -fno-common
, lo que significa que, de manera predeterminada, el código anterior ya no enlaza a menos que anule el valor predeterminado con -fcommon
(o use atributos, etc.) ver el enlace).
Los siguientes dos archivos completan la fuente de prog2
:
prog2.h
extern void dec(void);
extern void put(void);
extern void inc(void);
prog2.c
#include "prog2.h"
#include <stdio.h>
int main(void)
{
inc();
put();
dec();
put();
dec();
put();
}
prog2
usos prog2.c
, file10.c
, file11.c
, file12.c
, prog2.h
.
Advertencia
Como se señaló en los comentarios aquí, y como se indica en mi respuesta a una pregunta similar , el uso de múltiples definiciones para una variable global conduce a un comportamiento indefinido (J.2; §6.9), que es la forma estándar de decir "cualquier cosa podría suceder". Una de las cosas que puede suceder es que el programa se comporta como espera; y J.5.11 dice, aproximadamente, "podrías tener suerte más de lo que mereces". Pero un programa que se basa en múltiples definiciones de una variable externa, con o sin la palabra clave explícita 'externa', no es un programa estrictamente conforme y no se garantiza que funcione en todas partes. Equivalente: contiene un error que puede mostrarse o no.
Violar las pautas
Por supuesto, hay muchas maneras en que estas pautas pueden romperse. Ocasionalmente, puede haber una buena razón para romper las pautas, pero tales ocasiones son extremadamente inusuales.
faulty_header.h
c int some_var; /* Do not do this in a header!!! */
Nota 1: si el encabezado define la variable sin la extern
palabra clave, cada archivo que incluye el encabezado crea una definición tentativa de la variable. Como se señaló anteriormente, esto a menudo funcionará, pero el estándar C no garantiza que funcione.
broken_header.h
c int some_var = 13; /* Only one source file in a program can use this */
Nota 2: si el encabezado define e inicializa la variable, solo un archivo fuente en un programa determinado puede usar el encabezado. Dado que los encabezados son principalmente para compartir información, es un poco tonto crear uno que solo se pueda usar una vez.
rara vez_corregido.h
c static int hidden_global = 3; /* Each source file gets its own copy */
Nota 3: si el encabezado define una variable estática (con o sin inicialización), cada archivo fuente termina con su propia versión privada de la variable 'global'.
Si la variable es en realidad una matriz compleja, por ejemplo, esto puede conducir a una duplicación extrema de código. Puede, muy ocasionalmente, ser una forma sensata de lograr algún efecto, pero eso es muy inusual.
Resumen
Use la técnica de encabezado que mostré primero. Funciona de manera confiable y en todas partes. Tenga en cuenta, en particular, que el encabezado que declara el global_variable
se incluye en cada archivo que lo usa, incluido el que lo define. Esto asegura que todo sea autoconsistente.
Surgen preocupaciones similares con la declaración y definición de funciones; se aplican reglas análogas. Pero la pregunta era sobre variables específicamente, por lo que he guardado la respuesta solo a las variables.
Fin de la respuesta original
Si no eres un programador de C experimentado, probablemente deberías dejar de leer aquí.
Adición mayor tardía
Evitar la duplicación de código
Una preocupación que a veces se plantea (y legítimamente) sobre el mecanismo de 'declaraciones en encabezados, definiciones en origen' descrito aquí es que hay dos archivos que se deben mantener sincronizados: el encabezado y el origen. Esto generalmente se sigue con una observación de que se puede usar una macro para que el encabezado cumpla una doble función, normalmente declarando las variables, pero cuando se establece una macro específica antes de incluir el encabezado, define las variables en su lugar.
Otra preocupación puede ser que las variables deben definirse en cada uno de una serie de 'programas principales'. Esto normalmente es una preocupación espuria; simplemente puede introducir un archivo fuente C para definir las variables y vincular el archivo objeto producido con cada uno de los programas.
Un esquema típico funciona así, utilizando la variable global original ilustrada en file3.h
:
file3a.h
#ifdef DEFINE_VARIABLES
#define EXTERN /* nothing */
#else
#define EXTERN extern
#endif /* DEFINE_VARIABLES */
EXTERN int global_variable;
file1a.c
#define DEFINE_VARIABLES
#include "file3a.h" /* Variable defined - but not initialized */
#include "prog3.h"
int increment(void) { return global_variable++; }
file2a.c
#include "file3a.h"
#include "prog3.h"
#include <stdio.h>
void use_it(void)
{
printf("Global variable: %d\n", global_variable++);
}
Los siguientes dos archivos completan la fuente de prog3
:
prog3.h
extern void use_it(void);
extern int increment(void);
prog3.c
#include "file3a.h"
#include "prog3.h"
#include <stdio.h>
int main(void)
{
use_it();
global_variable += 19;
use_it();
printf("Increment: %d\n", increment());
return 0;
}
prog3
usos prog3.c
, file1a.c
, file2a.c
, file3a.h
, prog3.h
.
Inicialización variable
El problema con este esquema como se muestra es que no proporciona la inicialización de la variable global. Con C99 o C11 y listas de argumentos variables para macros, también podría definir una macro para admitir la inicialización. (Con C89 y sin soporte para listas de argumentos variables en macros, no hay una manera fácil de manejar inicializadores arbitrariamente largos).
file3b.h
#ifdef DEFINE_VARIABLES
#define EXTERN /* nothing */
#define INITIALIZER(...) = __VA_ARGS__
#else
#define EXTERN extern
#define INITIALIZER(...) /* nothing */
#endif /* DEFINE_VARIABLES */
EXTERN int global_variable INITIALIZER(37);
EXTERN struct { int a; int b; } oddball_struct INITIALIZER({ 41, 43 });
Invierta el contenido #if
y los #else
bloques, reparando el error identificado por
Denis Kniazhev
file1b.c
#define DEFINE_VARIABLES
#include "file3b.h" /* Variables now defined and initialized */
#include "prog4.h"
int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }
file2b.c
#include "file3b.h"
#include "prog4.h"
#include <stdio.h>
void use_them(void)
{
printf("Global variable: %d\n", global_variable++);
oddball_struct.a += global_variable;
oddball_struct.b -= global_variable / 2;
}
Claramente, el código para la estructura extraña no es lo que normalmente escribirías, pero ilustra el punto. El primer argumento para la segunda invocación de INITIALIZER
es { 41
y el argumento restante (singular en este ejemplo) es 43 }
. Sin C99 o soporte similar para listas de argumentos variables para macros, los inicializadores que necesitan contener comas son muy problemáticos.
Encabezado correcto file3b.h
incluido (en lugar de fileba.h
) por
Denis Kniazhev
Los siguientes dos archivos completan la fuente de prog4
:
prog4.h
extern int increment(void);
extern int oddball_value(void);
extern void use_them(void);
prog4.c
#include "file3b.h"
#include "prog4.h"
#include <stdio.h>
int main(void)
{
use_them();
global_variable += 19;
use_them();
printf("Increment: %d\n", increment());
printf("Oddball: %d\n", oddball_value());
return 0;
}
prog4
usos prog4.c
, file1b.c
, file2b.c
, prog4.h
, file3b.h
.
Guardias de cabecera
Cualquier encabezado debe protegerse contra la reinclusión, de modo que las definiciones de tipo (tipos de enumeración, estructura o unión, o tipos de definición en general) no causen problemas. La técnica estándar es envolver el cuerpo del encabezado en un protector de encabezado como:
#ifndef FILE3B_H_INCLUDED
#define FILE3B_H_INCLUDED
...contents of header...
#endif /* FILE3B_H_INCLUDED */
El encabezado puede incluirse dos veces indirectamente. Por ejemplo, si file4b.h
incluye file3b.h
una definición de tipo que no se muestra y file1b.c
necesita usar tanto el encabezado file4b.h
como file3b.h
, entonces tiene algunos problemas más difíciles de resolver. Claramente, puede revisar la lista de encabezados para incluir solo file4b.h
. Sin embargo, es posible que no conozca las dependencias internas, y el código debería, idealmente, continuar funcionando.
Además, comienza a ser complicado porque puede incluir file4b.h
antes de incluir file3b.h
para generar las definiciones, pero las file3b.h
protecciones de encabezado normales en evitarían que el encabezado se vuelva a incluir.
Por lo tanto, debe incluir el cuerpo de file3b.h
como máximo una vez para las declaraciones, y como máximo una vez para las definiciones, pero es posible que necesite ambas en una sola unidad de traducción (TU: una combinación de un archivo fuente y los encabezados que usa).
Inclusión múltiple con definiciones variables
Sin embargo, se puede hacer sujeto a una restricción no demasiado irrazonable. Vamos a presentar un nuevo conjunto de nombres de archivo:
external.h
para las definiciones de macro EXTERNAS, etc.
file1c.h
para definir tipos (en particular struct oddball
, el tipo de oddball_struct
).
file2c.h
para definir o declarar las variables globales.
file3c.c
que define las variables globales.
file4c.c
que simplemente usa las variables globales.
file5c.c
que muestra que puede declarar y luego definir las variables globales.
file6c.c
que muestra que puede definir y luego (intentar) declarar las variables globales.
En estos ejemplos, file5c.c
e file6c.c
incluye directamente el encabezado file2c.h
varias veces, pero esa es la forma más sencilla de mostrar que el mecanismo funciona. Significa que si el encabezado se incluyera indirectamente dos veces, también sería seguro.
Las restricciones para que esto funcione son:
El encabezado que define o declara las variables globales puede no definir ningún tipo.
Inmediatamente antes de incluir un encabezado que debería definir variables, defina la macro DEFINE_VARIABLES.
El encabezado que define o declara las variables tiene contenidos estilizados.
externo.h
#ifdef DEFINE_VARIABLES
#define EXTERN /* nothing */
#define INITIALIZE(...) = __VA_ARGS__
#else
#define EXTERN extern
#define INITIALIZE(...) /* nothing */
#endif /* DEFINE_VARIABLES */
file1c.h
#ifndef FILE1C_H_INCLUDED
#define FILE1C_H_INCLUDED
struct oddball
{
int a;
int b;
};
extern void use_them(void);
extern int increment(void);
extern int oddball_value(void);
#endif /* FILE1C_H_INCLUDED */
file2c.h
/* Standard prologue */
#if defined(DEFINE_VARIABLES) && !defined(FILE2C_H_DEFINITIONS)
#undef FILE2C_H_INCLUDED
#endif
#ifndef FILE2C_H_INCLUDED
#define FILE2C_H_INCLUDED
#include "external.h" /* Support macros EXTERN, INITIALIZE */
#include "file1c.h" /* Type definition for struct oddball */
#if !defined(DEFINE_VARIABLES) || !defined(FILE2C_H_DEFINITIONS)
/* Global variable declarations / definitions */
EXTERN int global_variable INITIALIZE(37);
EXTERN struct oddball oddball_struct INITIALIZE({ 41, 43 });
#endif /* !DEFINE_VARIABLES || !FILE2C_H_DEFINITIONS */
/* Standard epilogue */
#ifdef DEFINE_VARIABLES
#define FILE2C_H_DEFINITIONS
#endif /* DEFINE_VARIABLES */
#endif /* FILE2C_H_INCLUDED */
file3c.c
#define DEFINE_VARIABLES
#include "file2c.h" /* Variables now defined and initialized */
int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }
file4c.c
#include "file2c.h"
#include <stdio.h>
void use_them(void)
{
printf("Global variable: %d\n", global_variable++);
oddball_struct.a += global_variable;
oddball_struct.b -= global_variable / 2;
}
file5c.c
#include "file2c.h" /* Declare variables */
#define DEFINE_VARIABLES
#include "file2c.h" /* Variables now defined and initialized */
int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }
file6c.c
#define DEFINE_VARIABLES
#include "file2c.h" /* Variables now defined and initialized */
#include "file2c.h" /* Declare variables */
int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }
El archivo de origen próximo completa la fuente (proporciona un programa principal) para prog5
, prog6
y prog7
:
prog5.c
#include "file2c.h"
#include <stdio.h>
int main(void)
{
use_them();
global_variable += 19;
use_them();
printf("Increment: %d\n", increment());
printf("Oddball: %d\n", oddball_value());
return 0;
}
prog5
usos prog5.c
, file3c.c
, file4c.c
, file1c.h
, file2c.h
, external.h
.
prog6
usos prog5.c
, file5c.c
, file4c.c
, file1c.h
, file2c.h
, external.h
.
prog7
usos prog5.c
, file6c.c
, file4c.c
, file1c.h
, file2c.h
, external.h
.
Este esquema evita la mayoría de los problemas. Solo se encuentra con un problema si file2c.h
otro encabezado (por ejemplo file7c.h
) que define variables incluye un encabezado que define variables (como ) . No hay una manera fácil de evitar eso que no sea "no lo hagas".
Puede solucionar el problema parcialmente revisando file2c.h
en file2d.h
:
file2d.h
/* Standard prologue */
#if defined(DEFINE_VARIABLES) && !defined(FILE2D_H_DEFINITIONS)
#undef FILE2D_H_INCLUDED
#endif
#ifndef FILE2D_H_INCLUDED
#define FILE2D_H_INCLUDED
#include "external.h" /* Support macros EXTERN, INITIALIZE */
#include "file1c.h" /* Type definition for struct oddball */
#if !defined(DEFINE_VARIABLES) || !defined(FILE2D_H_DEFINITIONS)
/* Global variable declarations / definitions */
EXTERN int global_variable INITIALIZE(37);
EXTERN struct oddball oddball_struct INITIALIZE({ 41, 43 });
#endif /* !DEFINE_VARIABLES || !FILE2D_H_DEFINITIONS */
/* Standard epilogue */
#ifdef DEFINE_VARIABLES
#define FILE2D_H_DEFINITIONS
#undef DEFINE_VARIABLES
#endif /* DEFINE_VARIABLES */
#endif /* FILE2D_H_INCLUDED */
El problema se convierte en '¿debería incluir el encabezado #undef DEFINE_VARIABLES
?' Si omite eso del encabezado y ajusta cualquier invocación de definición con #define
y #undef
:
#define DEFINE_VARIABLES
#include "file2c.h"
#undef DEFINE_VARIABLES
en el código fuente (para que los encabezados nunca alteren el valor de DEFINE_VARIABLES
), entonces debes estar limpio. Es solo una molestia tener que recordar escribir la línea extra. Una alternativa podría ser:
#define HEADER_DEFINING_VARIABLES "file2c.h"
#include "externdef.h"
externdef.h
#if defined(HEADER_DEFINING_VARIABLES)
#define DEFINE_VARIABLES
#include HEADER_DEFINING_VARIABLES
#undef DEFINE_VARIABLES
#undef HEADER_DEFINING_VARIABLES
#endif /* HEADER_DEFINING_VARIABLES */
Esto se está volviendo un poco complicado, pero parece ser seguro (usando el file2d.h
, con no #undef DEFINE_VARIABLES
en el file2d.h
).
file7c.c
/* Declare variables */
#include "file2d.h"
/* Define variables */
#define HEADER_DEFINING_VARIABLES "file2d.h"
#include "externdef.h"
/* Declare variables - again */
#include "file2d.h"
/* Define variables - again */
#define HEADER_DEFINING_VARIABLES "file2d.h"
#include "externdef.h"
int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }
file8c.h
/* Standard prologue */
#if defined(DEFINE_VARIABLES) && !defined(FILE8C_H_DEFINITIONS)
#undef FILE8C_H_INCLUDED
#endif
#ifndef FILE8C_H_INCLUDED
#define FILE8C_H_INCLUDED
#include "external.h" /* Support macros EXTERN, INITIALIZE */
#include "file2d.h" /* struct oddball */
#if !defined(DEFINE_VARIABLES) || !defined(FILE8C_H_DEFINITIONS)
/* Global variable declarations / definitions */
EXTERN struct oddball another INITIALIZE({ 14, 34 });
#endif /* !DEFINE_VARIABLES || !FILE8C_H_DEFINITIONS */
/* Standard epilogue */
#ifdef DEFINE_VARIABLES
#define FILE8C_H_DEFINITIONS
#endif /* DEFINE_VARIABLES */
#endif /* FILE8C_H_INCLUDED */
file8c.c
/* Define variables */
#define HEADER_DEFINING_VARIABLES "file2d.h"
#include "externdef.h"
/* Define variables */
#define HEADER_DEFINING_VARIABLES "file8c.h"
#include "externdef.h"
int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }
Los siguientes dos archivos completan la fuente de prog8
y prog9
:
prog8.c
#include "file2d.h"
#include <stdio.h>
int main(void)
{
use_them();
global_variable += 19;
use_them();
printf("Increment: %d\n", increment());
printf("Oddball: %d\n", oddball_value());
return 0;
}
file9c.c
#include "file2d.h"
#include <stdio.h>
void use_them(void)
{
printf("Global variable: %d\n", global_variable++);
oddball_struct.a += global_variable;
oddball_struct.b -= global_variable / 2;
}
prog8
usos prog8.c
, file7c.c
, file9c.c
.
prog9
usos prog8.c
, file8c.c
, file9c.c
.
Sin embargo, es poco probable que los problemas ocurran en la práctica, especialmente si toma el consejo estándar para
Evitar variables globales
¿Esta exposición pierde algo?
Confesión : El esquema de 'evitar el código duplicado' descrito aquí se desarrolló porque el problema afecta a algún código en el que trabajo (pero que no poseo), y es una preocupación insignificante con el esquema descrito en la primera parte de la respuesta. Sin embargo, el esquema original te deja con solo dos lugares para modificar para mantener sincronizadas las definiciones y declaraciones de variables, lo cual es un gran paso adelante para tener declaraciones de variables de ejercicio dispersas por toda la base del código (lo que realmente importa cuando hay miles de archivos en total) . Sin embargo, el código en los archivos con los nombres fileNc.[ch]
(más external.h
y externdef.h
) muestra que se puede hacer que funcione. Claramente, no sería difícil crear un script generador de encabezado para darle la plantilla estandarizada para una variable que define y declara el archivo de encabezado.
Nota: estos son programas de juguetes con apenas el código suficiente para que sean marginalmente interesantes. Hay una repetición dentro de los ejemplos que podría eliminarse, pero no es para simplificar la explicación pedagógica. (Por ejemplo: la diferencia entre prog5.c
y prog8.c
es el nombre de uno de los encabezados que se incluyen. Sería posible reorganizar el código para que la main()
función no se repita, pero ocultaría más de lo que reveló).
foo.h
):#define FOO_INITIALIZER { 1, 2, 3, 4, 5 }
para definir el inicializador de la matriz,enum { FOO_SIZE = sizeof((int [])FOO_INITIALIZER) / sizeof(((int [])FOO_INITIALIZER)[0]) };
para obtener el tamaño de la matriz yextern int foo[];
declarar la matriz . Claramente, la definición debe ser justaint foo[FOO_SIZE] = FOO_INITIALIZER;
, aunque el tamaño realmente no tiene que incluirse en la definición. Esto se consigue un constante entera,FOO_SIZE
.Una
extern
variable es una declaración (gracias a sbi por la corrección) de una variable que se define en otra unidad de traducción. Eso significa que el almacenamiento de la variable se asigna en otro archivo.Digamos que tiene dos
.c
archivostest1.c
ytest2.c
. Si define una variable globalint test1_var;
entest1.c
y desea acceder a esta variable entest2.c
lo que tiene que utilizarextern int test1_var;
entest2.c
.Muestra completa:
fuente
extern int test1_var;
aint test1_var;
, el enlazador (gcc 5.4.0) aún pasa. Entonces, ¿esextern
realmente necesario en este caso?extern
es una extensión común que a menudo funciona, y específicamente funciona con GCC (pero GCC está lejos de ser el único compilador que lo admite; es frecuente en los sistemas Unix). Puede buscar "J.5.11" o la sección "No es una buena manera" en mi respuesta (lo sé, es larga) y el texto cercano que lo explica (o intenta hacerlo).Extern es la palabra clave que utiliza para declarar que la variable en sí misma reside en otra unidad de traducción.
Por lo tanto, puede decidir utilizar una variable en una unidad de traducción y luego acceder a ella desde otra, luego, en la segunda, la declara como externa y el enlazador resolverá el símbolo.
Si no lo declara como externo, obtendrá 2 variables con el mismo nombre pero no relacionadas en absoluto, y un error de múltiples definiciones de la variable.
fuente
Me gusta pensar en una variable externa como una promesa que le haces al compilador.
Cuando se encuentra con un externo, el compilador solo puede averiguar su tipo, no dónde "vive", por lo que no puede resolver la referencia.
Lo estás diciendo: "Confía en mí. En el momento del enlace, esta referencia será resoluble".
fuente
extern le dice al compilador que confíe en usted que la memoria para esta variable se declara en otro lugar, por lo que no intenta asignar / verificar la memoria.
Por lo tanto, puede compilar un archivo que tenga referencia a un externo, pero no puede vincular si esa memoria no se declara en alguna parte.
Útil para variables globales y bibliotecas, pero peligroso porque el enlazador no escribe check.
fuente
Agregar un
extern
convierte una definición de variable en una declaración de variable . Vea este hilo sobre cuál es la diferencia entre una declaración y una definición.fuente
int foo
yextern int foo
(alcance del archivo)? Ambos son declaración, ¿no?La declaración no asignará memoria (la variable debe definirse para la asignación de memoria) pero la definición sí lo hará. Esta es solo otra vista simple de la palabra clave externa ya que las otras respuestas son realmente geniales.
fuente
La interpretación correcta de extern es que le dices algo al compilador. Le dice al compilador que, a pesar de no estar presente en este momento, la variable declarada de alguna manera será encontrada por el vinculador (generalmente en otro objeto (archivo)). El enlazador será el tipo afortunado en encontrar todo y armarlo, ya sea que haya tenido declaraciones externas o no.
fuente
En C una variable dentro de un archivo, por ejemplo, ejemplo.c tiene un alcance local. El compilador espera que la variable tenga su definición dentro del mismo archivo example.c y cuando no encuentra la misma, arrojará un error. Por otro lado, una función tiene por defecto un alcance global. Por lo tanto, no tiene que mencionar explícitamente al compilador "look dude ... puede encontrar la definición de esta función aquí". Para una función que incluye el archivo que contiene su declaración es suficiente (el archivo que realmente llama un archivo de encabezado). Por ejemplo, considere los siguientes 2 archivos:
ejemplo.c
ejemplo1.c
Ahora, cuando compila los dos archivos juntos, use los siguientes comandos:
paso 1) cc -o ex ejemplo.c ejemplo1.c paso 2) ./ ex
Obtiene el siguiente resultado: el valor de a es <5>
fuente
Implementación de GCC ELF Linux
Otras respuestas han cubierto el lado de la vista del uso del lenguaje, así que ahora echemos un vistazo a cómo se implementa en esta implementación.
C Principal
Compilar y descompilar:
La salida contiene:
El capítulo "Tabla de símbolos" de la especificación ELF de actualización de ABI de System V explica:
que es básicamente el comportamiento que le da el estándar C a
extern
variables.De ahora en adelante, es el trabajo del enlazador hacer el programa final, pero la
extern
información ya ha sido extraída del código fuente en el archivo objeto.Probado en GCC 4.8.
C ++ 17 variables en línea
En C ++ 17, es posible que desee utilizar variables en línea en lugar de variables externas, ya que son fáciles de usar (se pueden definir una sola vez en el encabezado) y más potentes (admiten constexpr). Ver: ¿Qué significa 'const static' en C y C ++?
fuente
readelf
onm
puede ser útil, no ha explicado los fundamentos de cómo usarloextern
, ni ha completado el primer programa con la definición real. Tu código ni siquiera usanotExtern
. También hay un problema de nomenclatura: aunquenotExtern
se define aquí en lugar de declararse conextern
él, es una variable externa a la que podrían acceder otros archivos fuente si esas unidades de traducción contuvieran una declaración adecuada (¡lo que sería necesarioextern int notExtern;
!).notExtern
era feo, lo arregló. Acerca de la nomenclatura, avíseme si tiene un nombre mejor. Por supuesto, ese no sería un buen nombre para un programa real, pero creo que encaja bien con el papel didáctico aquí.global_def
con la variable definida aquí yextern_ref
con la variable definida en algún otro módulo? ¿Tendrían una simetría adecuadamente clara? Todavía terminasint extern_ref = 57;
o algo así en el archivo donde está definido, por lo que el nombre no es ideal, pero dentro del contexto del archivo de origen único, es una opción razonable. Tenerextern int global_def;
un encabezado no es un gran problema, me parece a mí. Completamente de usted, por supuesto.La palabra clave extern se usa con la variable para su identificación como variable global.
fuente
extern
permite que un módulo de su programa acceda a una variable o función global declarada en otro módulo de su programa. Por lo general, tiene variables externas declaradas en archivos de encabezado.Si no desea que un programa acceda a sus variables o funciones, utilice lo
static
que le dice al compilador que esta variable o función no se puede usar fuera de este módulo.fuente
extern
simplemente significa que una variable se define en otra parte (por ejemplo, en otro archivo).fuente
En primer lugar, la
extern
palabra clave no se usa para definir una variable; más bien se usa para declarar una variable. Puedo decir queextern
es una clase de almacenamiento, no un tipo de datos.extern
se utiliza para permitir que otros archivos C o componentes externos sepan que esta variable ya está definida en alguna parte. Ejemplo: si está creando una biblioteca, no es necesario definir la variable global obligatoriamente en algún lugar de la biblioteca. La biblioteca se compilará directamente, pero al vincular el archivo, verifica la definición.fuente
extern
se utiliza para que unfirst.c
archivo pueda tener acceso completo a un parámetro global en otrosecond.c
archivo.Se
extern
puede declarar en elfirst.c
archivo o en cualquiera de los archivos de encabezadofirst.c
incluidos.fuente
extern
declaración debe estar en un encabezado, no dentrofirst.c
, de modo que si el tipo cambia, la declaración también cambiará. Además, el encabezado que declara la variable debe incluirsesecond.c
para garantizar que la definición sea coherente con la declaración. La declaración en el encabezado es el pegamento que lo mantiene todo junto; permite que los archivos se compilen por separado, pero garantiza que tengan una vista coherente del tipo de la variable global.Con xc8 debe tener cuidado al declarar una variable como el mismo tipo en cada archivo, ya que podría, erróneamente, declarar algo an
int
en un archivo y unchar
decir en otro. Esto podría conducir a la corrupción de las variables.Este problema se resolvió elegantemente en un foro de microchips hace unos 15 años / * Ver "http: www.htsoft.com" / / "forum / all / showflat.php / Cat / 0 / Number / 18766 / an / 0 / page / 0 # 18766 "
Pero este enlace parece que ya no funciona ...
Así que trataré de explicarlo rápidamente; crea un archivo llamado global.h.
En él declaramos lo siguiente
Ahora en el archivo main.c
Esto significa que en main.c la variable se declarará como
unsigned char
.Ahora en otros archivos simplemente incluyendo global.h lo tendrá declarado como externo para ese archivo .
Pero se declarará correctamente como un
unsigned char
.La antigua publicación del foro probablemente explicó esto un poco más claramente. Pero este es un potencial real
gotcha
cuando se utiliza un compilador que le permite declarar una variable en un archivo y luego declararla externa como un tipo diferente en otro. Los problemas asociados con eso son si usted dice que testing_mode como int en otro archivo pensaría que es una variable de 16 bits y sobrescribe otra parte de ram, corrompiendo potencialmente otra variable. Difícil de depurar!fuente
Una solución muy corta que uso para permitir que un archivo de encabezado contenga la referencia externa o la implementación real de un objeto. El archivo que realmente contiene el objeto simplemente lo hace
#define GLOBAL_FOO_IMPLEMENTATION
. Luego, cuando agrego un nuevo objeto a este archivo, aparece en ese archivo también sin que tenga que copiar y pegar la definición.Yo uso este patrón en varios archivos. Entonces, para mantener las cosas lo más autónomas posible, solo reutilizo la macro GLOBAL individual en cada encabezado. Mi encabezado se ve así:
fuente