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

Sé que las variables globales en C a veces tienen el extern palabra clave. Que es un extern ¿variable? ¿Cómo es la declaración? Cual es su alcance?

Esto está relacionado con compartir variables entre archivos de origen, pero ¿cómo funciona eso precisamente? Donde lo uso extern?

preguntado el 16 de septiembre de 09 a las 11:09

17 Respuestas

Usar extern solo es relevante cuando el programa que está creando consta de varios archivos fuente vinculados entre sí, donde algunas de las variables definidas, por ejemplo, en el archivo fuente file1.c necesitan ser referenciados en otros archivos de origen, como file2.c.

Es importante entender la diferencia entre definir una variable y declarando una variable:

  • Una variable es declaró 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 es 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 ámbito determinado. Una definición de variable también es una declaración, pero no todas las declaraciones de variable son definiciones.

La mejor forma de declarar y definir variables globales

La forma limpia y confiable de declarar y definir variables globales es usar un archivo de encabezado para contener un extern declaración de la variable.

El encabezado está incluido por el archivo fuente que define la variable y por todos los archivos fuente que hacen referencia a la variable. Para cada programa, un archivo fuente (y solo un archivo fuente) define la variable. De manera similar, 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 existen otras formas de hacerlo, este método es sencillo y fiable. Está demostrado por file3.h, file1.c e file2.c:

archivo3.h

extern int global_variable;  /* Declaration of the variable */

archivo1.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++; }

archivo2.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 forma 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 que se utilicen (mientras que C90 no lo hizo, por buenas razones). Yo uso la palabra clave extern delante de las declaraciones de función en los encabezados para mantener la coherencia, para que coincida con el extern delante de las declaraciones de variables en los encabezados. Mucha gente prefiere no usar extern delante de las declaraciones de funciones; al compilador no le importa y, en última instancia, a mí tampoco, siempre que seas 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 e prog1.h.

El archivo prog1.mk es un archivo MAKE para prog1 solo. Funcionará con la mayoría de versiones de make producido 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}


Líneas directrices

Reglas que deben ser infringidas solo por expertos, y solo con una buena razón:

  • 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 la declara (SPOT - Punto único de verdad).

  • Un archivo fuente nunca contiene extern declaraciones de variables: los archivos de origen siempre incluyen el encabezado (único) que los declara.

  • Para cualquier variable dada, exactamente un archivo fuente define la variable, preferiblemente inicializándola también. (Aunque no es necesario 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 de origen que define la variable también incluye el encabezado para garantizar que la definición y la declaración sean coherentes.

  • 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 SOQ (Preguntas de desbordamiento de pila) repositorio en GitHub en el src / so-0143-3204 subdirectorio.

Si no es un programador de C experimentado, podría (y tal vez debería) dejar de leer aquí.

No es una buena forma de definir variables globales.

Con algunos (de hecho, muchos) compiladores de C, también puede salirse con la suya con lo que se llama una definición "común" de una variable. 'Común', aquí, se refiere a una técnica usada en Fortran para compartir variables entre archivos fuente, usando un bloque COMÚN (posiblemente nombrado). Lo que sucede aquí es que cada uno de varios archivos proporciona una definición tentativa de la variable. Siempre que no más de un archivo proporcione una definición inicializada, los distintos archivos terminan compartiendo una única definición común de la variable:

archivo10.c

#include "prog2.h"

long l;   /* Do not do this in portable code */

void inc(void) { l++; }

archivo11.c

#include "prog2.h"

long l;   /* Do not do this in portable code */

void dec(void) { l--; }

archivo12.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 a la 'regla de una definición'; es un comportamiento oficialmente indefinido:

J.2 Comportamiento indefinido

Se usa un identificador con enlace externo, pero en el programa no existe exactamente una definición externa para el identificador, o el identificador no se usa y existen múltiples definiciones externas para el identificador (6.9).

§6.9 Definiciones externas ¶5

An 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 (que no sea como parte del operando de un sizeof or _Alignof operador cuyo resultado es una constante entera), en algún lugar 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 usa en una expresión, no es necesario que haya una definición externa para él.

Sin embargo, la norma C también lo enumera en el anexo informativo J como uno de los 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 concuerdan, o se inicializa más de una, el comportamiento es indefinido (6.9.2).

Debido a que esta técnica no siempre es compatible, es mejor evitar su uso, especialmente si su código necesita ser portátil. Con esta técnica, también puede terminar con juegos de palabras de tipo involuntario.

Si uno de los archivos anteriores declaró l como herramienta de edición del double en lugar de como long, Los enlazadores de tipo inseguro de C probablemente no detectarían la falta de coincidencia. Si está en una máquina con 64 bits long e double, ni siquiera recibirías una advertencia; en una máquina con 32 bits long y 64 bits double, probablemente recibiría una advertencia sobre los diferentes tamaños: el enlazador usaría el tamaño más grande, exactamente como un programa de Fortran tomaría el tamaño más grande de cualquier bloque común.

Tenga en cuenta que GCC 10.1.0, que se publicó el 2020 de mayo de 05, cambia las opciones de compilación predeterminadas para usar -fno-common, lo que significa que, de forma predeterminada, el código anterior ya no se vincula a menos que anule el predeterminado con -fcommon (o use atributos, etc - vea 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 indicó en mi respuesta a una similar pregunta, 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 pueden suceder es que el programa se comporte como espera; y J.5.11 dice, aproximadamente, "puede que tengas suerte más a menudo de lo que te mereces". Pero un programa que se basa en múltiples definiciones de una variable extern, con o sin la palabra clave explícita 'extern', no es un programa estrictamente conforme y no se garantiza que funcione en todas partes. Equivalente: contiene un error que puede aparecer o no.

Violar las pautas

Por supuesto, hay muchas formas en las que se pueden romper estas pautas. Ocasionalmente, puede haber una buena razón para romper las pautas, pero tales ocasiones son extremadamente inusuales.

defectuoso_header.h

int some_var;    /* Do not do this in a header!!! */

Nota 1: si el encabezado define la variable sin el extern palabra clave, luego 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

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

raramente_correcto.h

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), entonces 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. Muy ocasionalmente, puede ser una forma sensata de lograr algún efecto, pero eso es muy inusual.


Resumen

Utilice la técnica de encabezado que mostré primero. Funciona de forma fiable y en todas partes. Tenga en cuenta, en particular, que el encabezado que declara el global_variable se incluye en todos los archivos que lo utilizan, incluido el que lo define. Esto asegura que todo sea autoconsistente.

Surgen preocupaciones similares al declarar y definir funciones; se aplican reglas análogas. Pero la pregunta se refería específicamente a las variables, por lo que he guardado la respuesta solo para las variables.

Fin de la respuesta original

Si no es un programador de C experimentado, probablemente debería dejar de leer aquí.


Adición importante tardía

Evitar la duplicación de código

Una preocupación que a veces (y legítimamente) surge sobre el mecanismo de 'declaraciones en encabezados, definiciones en fuente' que se describe aquí es que hay dos archivos que deben mantenerse sincronizados: el encabezado y la fuente. A esto generalmente le sigue una observación de que se puede usar una macro para que el encabezado tenga una doble función: normalmente declara las variables, pero cuando se establece una macro específica antes de que se incluya el encabezado, define las variables en su lugar.

Otra preocupación puede ser que las variables deban definirse en cada uno de una serie de "programas principales". Esta es normalmente una preocupación falsa; simplemente puede introducir un archivo fuente en C para definir las variables y vincular el archivo objeto producido con cada uno de los programas.

Un esquema típico funciona así, usando la variable global original ilustrada en file3.h:

archivo3a.h

#ifdef DEFINE_VARIABLES
#define EXTERN /* nothing */
#else
#define EXTERN extern
#endif /* DEFINE_VARIABLES */

EXTERN int global_variable;

archivo1a.c

#define DEFINE_VARIABLES
#include "file3a.h"  /* Variable defined - but not initialized */
#include "prog3.h"

int increment(void) { return global_variable++; }

archivo2a.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 prevé la inicialización de la variable global. Con C99 o C11 y listas de argumentos variables para macros, también puede 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).

archivo3b.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 });

Contenidos inversos de #if e #else bloques, corrección de error identificado por Denis Kniazhev

archivo1b.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; }

archivo2b.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 de bichos raros no es el que normalmente escribirías, pero ilustra el punto. El primer argumento de la segunda invocación de INITIALIZER is { 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) Para 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 enum, struct o union, o typedefs 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 de forma indirecta. Por ejemplo, si file4b.h incluye file3b.h para una definición de tipo que no se muestra, y file1b.c necesita usar ambos encabezados file4b.h e file3b.h, entonces tienes algunos problemas más complicados que resolver. Claramente, puede revisar la lista de encabezados para incluir solo file4b.h. Sin embargo, es posible que no conozca las dependencias internas y, idealmente, el código debería seguir funcionando.

Además, comienza a ser complicado porque puede incluir file4b.h antes de incluir file3b.h para generar las definiciones, pero el encabezado normal guarda en file3b.h evitaría que se volviera a incluir el encabezado.

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 ambos en una sola unidad de traducción (TU - una combinación de un archivo fuente y los encabezados que utiliza).

Inclusión múltiple con definiciones de variables

Sin embargo, se puede hacer sujeto a una restricción no demasiado irrazonable. Introduzcamos un nuevo conjunto de nombres de archivos:

  • external.h para las definiciones de macros 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 incluir directamente el encabezado file2c.h varias veces, pero esa es la forma más sencilla de demostrar que el mecanismo funciona. Significa que si el encabezado se incluye 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 no puede definir por sí mismo ningún tipo.

  2. Inmediatamente antes de incluir un encabezado que debe definir variables, define la macro DEFINE_VARIABLES.

  3. El encabezado que define o declara las variables tiene contenidos estilizados.

externo.h

/*
** This header must not contain header guards (like <assert.h> must not).
** Each time it is invoked, it redefines the macros EXTERN, INITIALIZE
** based on whether macro DEFINE_VARIABLES is currently defined.
*/
#undef EXTERN
#undef INITIALIZE

#ifdef DEFINE_VARIABLES
#define EXTERN              /* nothing */
#define INITIALIZE(...)     = __VA_ARGS__
#else
#define EXTERN              extern
#define INITIALIZE(...)     /* nothing */
#endif /* DEFINE_VARIABLES */

archivo1c.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 */

archivo2c.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 */

archivo3c.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; }

archivo4c.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;
}

archivo5c.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; }

archivo6c.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 siguiente archivo fuente completa la fuente (proporciona un programa principal) para prog5, prog6 e 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 un encabezado que define variables (como file2c.h) está incluido en otro encabezado (digamos file7c.h) que define variables. No hay una manera fácil de evitar eso que no sea "no lo hagas".

Puede solucionar parcialmente el problema revisando file2c.h dentro file2d.h:

archivo2d.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 'si el encabezado incluye #undef DEFINE_VARIABLES? ' Si omite eso del encabezado y envuelve cualquier invocación de definición con #define e #undef:

#define DEFINE_VARIABLES
#include "file2c.h"
#undef DEFINE_VARIABLES

en el código fuente (por lo que los encabezados nunca alteran el valor de DEFINE_VARIABLES), entonces debería estar limpio. Es solo una molestia tener que recordar escribir la línea adicional. Una alternativa podría ser:

#define HEADER_DEFINING_VARIABLES "file2c.h"
#include "externdef.h"

externodef.h

/*
** This header must not contain header guards (like <assert.h> must not).
** Each time it is included, the macro HEADER_DEFINING_VARIABLES should
** be defined with the name (in quotes - or possibly angle brackets) of
** the header to be included that defines variables when the macro
** DEFINE_VARIABLES is defined.  See also: external.h (which uses
** DEFINE_VARIABLES and defines macros EXTERN and INITIALIZE
** appropriately).
**
** #define HEADER_DEFINING_VARIABLES "file2c.h"
** #include "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, sin #undef DEFINE_VARIABLES en el capítulo respecto a la file2d.h).

archivo7c.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; }

archivo8c.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 */

archivo8c.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 e 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;
}

archivo9c.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 relativamente poco probable que los problemas ocurran en la práctica, especialmente si sigue el consejo estándar de

Evite las variables globales


¿A esta exposición le falta algo?

_Confesión_: El esquema de 'evitar código duplicado' descrito aquí se desarrolló porque el problema afecta a algunos códigos en los que trabajo (pero que no son de mi propiedad), y es una preocupación molesta con el esquema descrito en la primera parte de la respuesta. Sin embargo, el esquema original lo deja con solo dos lugares para modificar para mantener sincronizadas las definiciones y declaraciones de variables, lo cual es un gran paso adelante con respecto a tener declaraciones de variables externas 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.

NB Estos son programas de juguete con apenas código suficiente para hacerlos marginalmente interesantes. Hay una repetición dentro de los ejemplos que podría eliminarse, pero no para simplificar la explicación pedagógica. (Por ejemplo: la diferencia entre prog5.c e prog8.c es el nombre de uno de los encabezados que se incluyen. Sería posible reorganizar el código para que el main() La función no se repetía, pero ocultaba más de lo que revelaba).

Respondido 28 Jul 20, 18:07

@litb: consulte el anexo J.5.11 para ver la definición común; es una extensión común. - jonathan leffler

@litb: y estoy de acuerdo en que debe evitarse, por eso está en la sección 'No es una buena manera de definir variables globales'. - jonathan leffler

De hecho, es una extensión común, pero es un comportamiento indefinido para que un programa confíe en ella. Simplemente no estaba claro si estabas diciendo que esto está permitido por las propias reglas de C. Ahora veo que está diciendo que es solo una extensión común y que debe evitarlo si necesita que su código sea portátil. Así que puedo darte un voto positivo sin dudas. Realmente una gran respuesta en mi humilde opinión :) - Johannes Schaub - litb

Si se detiene en la parte superior, las cosas simples se mantienen simples. A medida que lea más abajo, se ocupará de más matices, complicaciones y detalles. Acabo de agregar dos 'puntos de parada iniciales' para programadores en C con menos experiencia, o programadores en C que ya conocen el tema. No es necesario que lo lea todo si ya conoce la respuesta (pero avíseme si encuentra una falla técnica). - jonathan leffler

@supercat: Se me ocurre que puede usar los 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[]; para declarar la matriz. Claramente, la definición debería ser justa int foo[FOO_SIZE] = FOO_INITIALIZER;, aunque el tamaño no tiene por qué estar incluido en la definición. Esto le da una constante entera, FOO_SIZE. - jonathan leffler

An extern variable es una declaración (gracias a sbi por la corrección) de una variable que está definida en otra unidad de traducción. Eso significa que el almacenamiento de la variable se asigna en otro archivo.

Di que tienes dos .c-archivos test1.c e test2.c. Si define una variable global int test1_var; in test1.c y le gustaría acceder a esta variable en test2.c tienes que usar extern int test1_var; in 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

Respondido 18 Feb 17, 20:02

No hay "pseudodefiniciones". Es una declaración. - sbi

En el ejemplo anterior, si cambio el extern int test1_var; a int test1_var;, el enlazador (gcc 5.4.0) todavía pasa. Asi es extern realmente necesario en este caso? - Radiohead

@radiohead: En mi https://www.youtube.com/watch?v=xB-eutXNUMXJtA&feature=youtu.be, encontrará la información de que soltando el 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; prevalece en los sistemas Unix). Puede buscar "J.5.11" o la sección "No tan bien" en mi respuesta (lo sé, is de largo) y el texto cercano que lo explica (o intenta hacerlo). - jonathan leffler

Una declaración externa ciertamente no tiene que estar definida en otra unidad de traducción (y comúnmente no lo es). De hecho, declaración y definición pueden ser lo mismo. - Recuerda Monica

Extern es la palabra clave que usa para declarar que la variable en sí reside en otra unidad de traducción.

Entonces puedes decidir usar una variable en una unidad de traducción y luego acceder a ella desde otra, luego en la segunda la declaras como extern y el símbolo será resuelto por el enlazador.

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.

Respondido el 16 de Septiembre de 09 a las 15:09

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 lo use, pero no asignar la variable, otra unidad de traducción hará eso. 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

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 encontrar 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 se podrá resolver".

Respondido el 16 de Septiembre de 09 a las 15:09

Más generalmente, un declaración es una promesa de que el nombre se resolverá exactamente en una definición en el momento del enlace. Un extern declara una variable sin definir. - Mentira ryan

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 vincularlo si esa memoria no está declarada en algún lugar.

Útil para bibliotecas y variables globales, pero peligroso porque el enlazador no escribe check.

Respondido el 16 de Septiembre de 09 a las 15:09

La memoria no se declara. Vea las respuestas a esta pregunta: stackoverflow.com/questions/1410563 para más detalles. - sbi

                 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 lo hará. Esta es solo otra vista simple de la palabra clave extern, ya que las otras respuestas son realmente geniales.

Respondido el 09 de enero de 19 a las 20:01

Añadiendo un extern convierte una variable definición en una variable declaración. Vea este hilo en cuanto a cuál es la diferencia entre una declaración y una definición.

contestado el 23 de mayo de 17 a las 13:05

Que diferencia entre int foo e extern int foo (alcance del archivo)? Ambos son declaraciones, ¿no? - usuario1150105

@ user14284: Ambos son declaraciones solo en el sentido de que cada definición también es una declaración. Pero lo vinculé a una explicación de esto. ("Vea este hilo para saber cuál es la diferencia entre una declaración y una definición"). ¿Por qué no simplemente sigue el enlace y lee? - sbi

La interpretación correcta de extern es que le dice algo al compilador. Le dice al compilador que, a pesar de no estar presente en este momento, el vinculador encontrará de alguna manera la variable declarada (generalmente en otro objeto (archivo)). El enlazador será entonces el afortunado que encuentre todo y lo junte, ya sea que tenga algunas declaraciones externas o no.

Respondido el 21 de junio de 12 a las 00:06

La palabra clave extern se utiliza 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 esté declarada / definida en otro archivo.

Respondido 20 ago 12, 11:08

En C, una variable dentro de un archivo, digamos example.c, tiene un alcance local. El compilador espera que la variable tenga su definición dentro del mismo archivo example.c y cuando no encuentre el mismo arrojaría un error. Una función en cambio tiene por defecto alcance global. Por lo tanto, no tiene que mencionar explícitamente al compilador "mira amigo ... puede encontrar la definición de esta función aquí". Para una función que incluya el archivo que contiene su declaración es suficiente (el archivo al 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 compile los dos archivos juntos, use los siguientes comandos:

paso 1) cc -o ex example.c example1.c paso 2) ./ ex

Obtiene el siguiente resultado: el valor de a es <5>

Respondido 20 ago 12, 11:08

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

LA Sistema V ABI Actualizar especificación ELF El capítulo "Tabla de símbolos" 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 da el estándar C a extern variables.

De ahora en adelante, es el trabajo del enlazador hacer el programa final, pero el extern la información ya se ha extraído del código fuente en el archivo objeto.

Probado en GCC 4.8.

Variables en línea de C ++ 17

En C ++ 17, es posible que desee usar variables en línea en lugar de externas, ya que son fáciles de usar (se pueden definir solo una vez en el encabezado) y más potentes (admiten constexpr). Ver: ¿Qué significa 'const static' en C y C ++?

respondido 28 mar '20, 09:03

No es mi voto en contra, así que no lo sé. Sin embargo, daré una opinión. Aunque mirando la salida de readelf or nm puede ser útil, no ha explicado los fundamentos de cómo utilizar extern, ni completó el primer programa con la definición real. Tu código ni siquiera usa notExtern. También hay un problema de nomenclatura: aunque notExtern se define aquí en lugar de declararse con extern, es una variable externa a la que podrían acceder otros archivos fuente si esas unidades de traducción contuvieran una declaración adecuada (que necesitaría extern int notExtern;!). - jonathan leffler

@JonathanLeffler ¡gracias por los comentarios! El comportamiento estándar y las recomendaciones de uso ya se han realizado en otras respuestas, por lo que decidí mostrar un poco la implementación, ya que eso realmente me ayudó a comprender lo que está sucediendo. No usando notExtern era feo, lo arregló. Sobre 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 se ajusta bien al papel didáctico aquí. - Ciro Santilli TRUMP BAN ES MALO

En cuanto a los nombres, ¿qué pasa con global_def para la variable definida aquí, y extern_ref para la variable definida en algún otro módulo? ¿Tendrían una simetría adecuadamente clara? Todavía terminas con int extern_ref = 57; o algo así en el archivo donde está definido, por lo que el nombre no es del todo ideal, pero dentro del contexto del archivo de origen único, es una opción razonable. Teniendo extern int global_def; en un encabezado no es un gran problema, me parece. Depende completamente de ti, por supuesto. - jonathan leffler

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

Respondido el 02 de Septiembre de 15 a las 16:09

Primero, el extern la palabra clave no se utiliza para definir una variable; más bien se usa para declarar una variable. puedo decir extern es una clase de almacenamiento, no un tipo de datos.

extern se utiliza para que otros archivos C o componentes externos sepan que esta variable ya está definida en algún lugar. Ejemplo: si está creando una biblioteca, no es necesario definir la variable global obligatoriamente en algún lugar de la propia biblioteca. La biblioteca se compilará directamente, pero al vincular el archivo, verifica la definición.

Respondido el 02 de Septiembre de 15 a las 16:09

extern simplemente significa que una variable está definida en otro lugar (por ejemplo, en otro archivo).

contestado el 05 de mayo de 18 a las 07:05

extern se usa así que uno first.c archivo puede tener acceso completo a un parámetro global en otro second.c archivo.

LA extern puede ser declarado en el first.c archivo o en cualquiera de los archivos de encabezado first.c incluye.

Respondido el 02 de Septiembre de 15 a las 16:09

Tenga en cuenta que extern La declaración debe estar en un encabezado, no en first.c, de modo que si el tipo cambia, la declaración también cambiará. Además, el encabezado que declara la variable debe ser incluido por second.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 variable global. - jonathan leffler

Con xc8, debe tener cuidado al declarar una variable del mismo tipo en cada archivo, ya que podría, erróneamente, declarar algo int en un archivo y un char decir en otro. Esto podría conducir a la corrupción de variables.

Este problema se resolvió elegantemente en un foro de microchip 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 intentaré explicarlo rápidamente; crea un archivo llamado global.h.

En él declara 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 un unsigned char.

Ahora, en otros archivos, simplemente incluyendo global.h lo tendrá declarado como un extern para ese archivo.

extern unsigned char testing_mode;

Pero se declarará correctamente como un unsigned char.

La publicación anterior del foro probablemente explicó esto con un poco más de claridad. Pero este es un potencial real gotcha cuando se usa un compilador que le permite declarar una variable en un archivo y luego declararlo extern como un tipo diferente en otro. Los problemas asociados con eso son que si dice que declaró testing_mode como un int en otro archivo, pensaría que es una var de 16 bits y sobrescribirá alguna otra parte de la RAM, corrompiendo potencialmente otra variable. ¡Difícil de depurar!

Respondido 09 Oct 18, 13:10

Utilizo una solución muy corta 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.

Utilizo este patrón en varios archivos. Entonces, para mantener las cosas lo más autónomas posible, simplemente reutilizo la macro GLOBAL única 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

Respondido el 24 de junio de 19 a las 03:06

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.