¿Cómo uso extern para compartir variables entre archivos fuente?

988

Sé que las variables globales en C a veces tienen la externpalabra clave. ¿Qué es una externvariable? ¿Cómo es la declaración? ¿Cuál es su alcance?

Esto está relacionado con el intercambio de variables entre los archivos de origen, pero ¿cómo funciona eso con precisión? ¿Dónde puedo usar extern?

Lundin
fuente

Respuestas:

1752

El uso externsolo 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.cdeben 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.cy 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 externdelante de las declaraciones de funciones en los encabezados para mantener la coherencia, para que coincida con las externdeclaraciones delante de las variables en los encabezados. Muchas personas prefieren no usar externfrente 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;
}
  • prog1usos prog1.c, file1.c, file2.c, file3.hy prog1.h.

El archivo prog1.mkes solo un archivo MAKE prog1. Funcionará con la mayoría de las versiones makeproducidas 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 externdeclaraciones de variables, nunca statico 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 externdeclaraciones 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 sizeofo _Alignofoperador 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 lcomo un en doublelugar 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 longy doubleni siquiera recibirá una advertencia; en una máquina con 32 longy 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();
}
  • prog2usos 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 externpalabra 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_variablese 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;
}
  • prog3usos 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 #ify los #elsebloques, 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 INITIALIZERes { 41y 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.hincluido (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;
}
  • prog4usos 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.hincluye file3b.huna definición de tipo que no se muestra y file1b.cnecesita usar tanto el encabezado file4b.hcomo 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.hantes de incluir file3b.hpara generar las definiciones, pero las file3b.hprotecciones de encabezado normales en evitarían que el encabezado se vuelva a incluir.

Por lo tanto, debe incluir el cuerpo de file3b.hcomo 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.hpara 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.ce file6c.cincluye directamente el encabezado file2c.hvarias 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:

  1. El encabezado que define o declara las variables globales puede no definir ningún tipo.

  2. Inmediatamente antes de incluir un encabezado que debería definir variables, defina la macro DEFINE_VARIABLES.

  3. 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, prog6y 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;
}
  • prog5usos prog5.c, file3c.c, file4c.c, file1c.h, file2c.h, external.h.

  • prog6usos prog5.c, file5c.c, file4c.c, file1c.h, file2c.h, external.h.

  • prog7usos 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.hotro 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.hen 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 #definey #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_VARIABLESen 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 prog8y 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;
}
  • prog8usos prog8.c, file7c.c, file9c.c.

  • prog9usos 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.hy 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.cy prog8.ces 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ó).

Jonathan Leffler
fuente
3
@litb: consulte el Anexo J.5.11 para la definición común: es una extensión común.
Jonathan Leffler
3
@litb: y estoy de acuerdo en que debe evitarse, es por eso que está en la sección 'No es una buena manera de definir variables globales'.
Jonathan Leffler
3
De hecho, es una extensión común, pero es un comportamiento indefinido para que un programa confíe en él. Simplemente no estaba claro si estabas diciendo que esto está permitido por las propias reglas de C. Ahora veo que estás diciendo que es solo una extensión común y que debes evitarlo si necesitas que tu código sea portátil. Para que pueda votarte sin dudas. Realmente gran respuesta en mi humilde opinión :)
Johannes Schaub - litb
19
Si te detienes en la parte superior, las cosas simples son simples. A medida que lea más abajo, se trata de más matices, complicaciones y detalles. Acabo de agregar dos 'puntos de parada temprana' para programadores de C menos experimentados, o programadores de C que ya conocen el tema. No es necesario leerlo todo si ya conoce la respuesta (pero avíseme si encuentra una falla técnica).
Jonathan Leffler
44
@supercat: Se me ocurre que puede usar literales de matriz C99 para obtener un valor de enumeración para el tamaño de la matriz, ejemplificado por ( 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 y extern int foo[];declarar la matriz . Claramente, la definición debe ser justa int 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.
Jonathan Leffler
125

Una externvariable 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 .carchivos test1.cy test2.c. Si define una variable global int test1_var;en test1.cy desea acceder a esta variable entest2.c lo que tiene que utilizar extern int test1_var;en test2.c.

Muestra completa:

$ cat test1.c 
int test1_var = 5;
$ cat test2.c
#include <stdio.h>

extern int test1_var;

int main(void) {
    printf("test1_var = %d\n", test1_var);
    return 0;
}
$ gcc test1.c test2.c -o test
$ ./test
test1_var = 5
Johannes Weiss
fuente
21
No hay "pseudo-definiciones". Es una declaracion.
sbi
3
En el ejemplo anterior, si cambio el extern int test1_var;a int test1_var;, el enlazador (gcc 5.4.0) aún pasa. Entonces, ¿es externrealmente necesario en este caso?
radiohead
2
@radiohead: En mi respuesta , encontrará la información de que soltar externes 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).
Jonathan Leffler
Una declaración externa ciertamente no tiene que definirse en otra unidad de traducción (y comúnmente no lo es). De hecho, la declaración y la definición pueden ser una y la misma.
Recuerda a Mónica el
40

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.

Arkaitz Jiménez
fuente
55
En otras palabras, la unidad de traducción donde se usa extern conoce esta variable, su tipo, etc. y, por lo tanto, permite que el código fuente en la lógica subyacente la use, pero no asigna la variable, otra unidad de traducción lo hará. Si ambas unidades de traducción declararan la variable normalmente, habría efectivamente dos ubicaciones físicas para la variable, con las referencias "incorrectas" asociadas dentro del código compilado, y con la ambigüedad resultante para el enlazador.
mjv
26

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".

Buggieboy
fuente
En términos más generales, una declaración es una promesa de que el nombre se resolverá con una definición exacta en el momento del enlace. Un externo declara una variable sin definir.
Mentira Ryan el
18

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.

Ben B
fuente
La memoria no está declarada. Consulte las respuestas a esta pregunta: stackoverflow.com/questions/1410563 para obtener más detalles.
sbi
15

Agregar un externconvierte 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.

sbi
fuente
¿Qué diferencia entre int fooy extern int foo(alcance del archivo)? Ambos son declaración, ¿no?
@ user14284: Ambas son declaraciones solo en el sentido de que cada definición es también una declaración. Pero me vinculé a una explicación de esto. ("Vea este hilo en cuanto a cuál es la diferencia entre una declaración y una definición".) ¿Por qué no simplemente sigue el enlace y lee?
sbi
14
                 declare | define   | initialize |
                ----------------------------------

extern int a;    yes          no           no
-------------
int a = 2019;    yes          yes          yes
-------------
int a;           yes          yes          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.

Lucian Nut
fuente
11

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.

Alex Lockwood
fuente
8

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

#include<stdio.h>
extern int a;
main(){
       printf("The value of a is <%d>\n",a);
}

ejemplo1.c

int a = 5;

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>

Phoenix225
fuente
8

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

#include <stdio.h>

int not_extern_int = 1;
extern int extern_int;

void main() {
    printf("%d\n", not_extern_int);
    printf("%d\n", extern_int);
}

Compilar y descompilar:

gcc -c main.c
readelf -s main.o

La salida contiene:

Num:    Value          Size Type    Bind   Vis      Ndx Name
 9: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    3 not_extern_int
12: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND extern_int

El capítulo "Tabla de símbolos" de la especificación ELF de actualización de ABI de System V explica:

SHN_UNDEF Este índice de tabla de sección significa que el símbolo no está definido. Cuando el editor de enlaces combina este archivo de objeto con otro que define el símbolo indicado, las referencias de este archivo al símbolo se vincularán a la definición real.

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 externinformació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 ++?

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente
3
No es mi voto negativo, así que no lo sé. Sin embargo, voy a ofrecer una opinión. Aunque mirar el resultado de readelfo nmpuede ser útil, no ha explicado los fundamentos de cómo usarlo extern, ni ha completado el primer programa con la definición real. Tu código ni siquiera usa notExtern. También hay un problema de nomenclatura: aunque notExternse define aquí en lugar de declararse con externé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 necesario extern int notExtern;!).
Jonathan Leffler
1
@ JonathanLeffler gracias por los comentarios! El comportamiento estándar y las recomendaciones de uso ya se han hecho en otras respuestas, así que decidí mostrar un poco la implementación, ya que eso realmente me ayudó a comprender lo que está sucediendo. No usar notExternera 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í.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
En cuanto a los nombres, ¿qué pasa global_defcon la variable definida aquí y extern_refcon la variable definida en algún otro módulo? ¿Tendrían una simetría adecuadamente clara? Todavía terminas int 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. Tener extern int global_def;un encabezado no es un gran problema, me parece a mí. Completamente de usted, por supuesto.
Jonathan Leffler
7

La palabra clave extern se usa con la variable para su identificación como variable global.

También representa que puede usar la variable declarada usando la palabra clave extern en cualquier archivo aunque se declare / defina en otro archivo.

Anup
fuente
5

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 staticque le dice al compilador que esta variable o función no se puede usar fuera de este módulo.

loganaayahee
fuente
5

extern simplemente significa que una variable se define en otra parte (por ejemplo, en otro archivo).

Geremia
fuente
4

En primer lugar, la externpalabra clave no se usa para definir una variable; más bien se usa para declarar una variable. Puedo decir que externes una clase de almacenamiento, no un tipo de datos.

externse 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.

usuario1270846
fuente
3

externse utiliza para que un first.carchivo pueda tener acceso completo a un parámetro global en otro second.carchivo.

Se externpuede declarar en el first.carchivo o en cualquiera de los archivos de encabezado first.cincluidos.

shoham
fuente
3
Tenga en cuenta que la externdeclaración debe estar en un encabezado, no dentro first.c, de modo que si el tipo cambia, la declaración también cambiará. Además, el encabezado que declara la variable debe incluirse second.cpara 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.
Jonathan Leffler
2

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 inten 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

#ifdef MAIN_C
#define GLOBAL
 /* #warning COMPILING MAIN.C */
#else
#define GLOBAL extern
#endif
GLOBAL unsigned char testing_mode; // example var used in several C files

Ahora en el archivo main.c

#define MAIN_C 1
#include "global.h"
#undef 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 .

extern unsigned char testing_mode;

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 gotchacuando 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!

usuario50619
fuente
0

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í:

//file foo_globals.h
#pragma once  
#include "foo.h"  //contains definition of foo

#ifdef GLOBAL  
#undef GLOBAL  
#endif  

#ifdef GLOBAL_FOO_IMPLEMENTATION  
#define GLOBAL  
#else  
#define GLOBAL extern  
#endif  

GLOBAL Foo foo1;  
GLOBAL Foo foo2;


//file main.cpp
#define GLOBAL_FOO_IMPLEMENTATION
#include "foo_globals.h"

//file uses_extern_foo.cpp
#include "foo_globals.h
muusbolla
fuente