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


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

Esto está relacionado con el intercambio de variables entre los archivos fuente, pero ¿cómo funciona eso exactamente? Donde uso extern?


810
2017-09-16 14:08


origen


Respuestas:


Utilizando extern solo es relevante cuando el programa que estás construyendo consiste en múltiples archivos fuente vinculados entre sí, donde algunos de los variables definidas, por ejemplo, en el archivo fuente file1.c necesitan ser referenciado en otros archivos fuente, como file2.c.

Es importante entender la diferencia entre definiendo un variable y declarando un variable:

  • Una variable es declarado cuando se le informa al compilador que variable existe (y este es su tipo); no asigna el almacenamiento para la variable en ese punto.
  • Una variable es definido 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 variables las declaraciones son definiciones.

La mejor manera 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 de origen 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 el variable. Del mismo modo, un archivo de encabezado (y solo un archivo de encabezado) debe declarar el variable. El archivo de encabezado es crucial; permite la verificación cruzada entre TU independientes (unidades de traducción - piense en archivos fuente) y asegura consistencia.

Aunque hay otras maneras de hacerlo, este método es simple y de confianza. Está demostrado por file3.h, file1.c y 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++; }

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 tienen se arrastró. Tanto C99 como C11 requieren que las funciones se declaren o definan antes de que se usan (mientras que C90 no, por buenas razones). Yo uso la palabra clave extern delante de las declaraciones de funciones en los encabezados para coherencia - para que coincida con el extern frente a variable declaraciones en encabezados. Muchas personas prefieren no usar extern frente a la función declaraciones; al compilador no le importa, y en última instancia, tampoco lo hago siempre y cuando seas consistente, 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 un archivo MAKE para prog1 solamente. Funcionará con la mayoría de las versiones de make producido desde aproximadamente el turno del 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}

Lineamientos

Las reglas deben ser rotas 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 lo declara (SPOT - Punto único de la verdad).
  • Un archivo fuente nunca contiene extern declaraciones de variables - los archivos fuente siempre incluyen el encabezado (único) que los declara.
  • Para cualquier variable dada, exactamente un archivo fuente define la variable, preferiblemente inicializándolo 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 un particular variable global en un programa).
  • El archivo fuente que define la variable también incluye el encabezado para asegurarse de 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; use funciones en su lugar.

El código fuente y el texto de esta respuesta están disponibles en mi        SOQ (Preguntas sobre desbordamiento de pila)       repositorio en GitHub en el        src / so-0143-3204       subdirectorio.

Si no eres un experimentado programador de C, podrías (y quizás        debería) dejar de leer aquí.

No es tan buena forma de definir variables globales

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

file10.c

#include "prog2.h"

int i;   /* Do not do this in portable code */

void inc(void) { i++; }

file11.c

#include "prog2.h"

int i;   /* Do not do this in portable code */

void dec(void) { i--; }

file12.c

#include "prog2.h"
#include <stdio.h>

int i = 9;   /* Do not do this in portable code */

void put(void) { printf("i = %d\n", i); }

Esta técnica no se ajusta a la letra del estándar C y al 'una regla de definición' - es un comportamiento oficialmente indefinido:

J.2 Comportamiento indefinido

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

§6.9 Definiciones externas ¶5

Un 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 una   objeto.   Si se utiliza un identificador declarado con un enlace externo en un   expresión (que no sea parte del operando de un sizeof o    _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 que   uno.161)

161) Por lo tanto, si un identificador declarado con un enlace externo   no se utiliza en una expresión, no debe haber una definición externa para   eso.

Sin embargo, el estándar C también lo enumera en el Anexo J informativo como uno de el 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   el comportamiento no está definido (6.9.2).

Debido a que esta técnica no siempre es compatible, lo mejor es evitar usándolo, especialmente si tu código necesita ser portátil. Usando esta técnica, también puedes terminar con un tipo involuntario juego de palabras Si uno de los archivos declarado i como un double en lugar de como un int, Los enlazadores inseguros de C probablemente no detectarían el desajuste. Si estás en una máquina con 64 bits int y double, ni siquiera lo harías recibe una advertencia; en una máquina con 32 bits int y 64 bits double, tu probablemente reciba una advertencia sobre los diferentes tamaños: el enlazador use 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.


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 un pregunta, usando múltiples las definiciones de una variable global conducen 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 comporte como usted esperar; y J.5.11 dice, aproximadamente, "podrías ser afortunado más seguido de lo que te mereces ". Pero un programa que se basa en múltiples definiciones de una variable externa - con o sin la palabra clave explícita 'extern' - no es estrictamente programa conforme y no garantizado para trabajar en todas partes. Equivalentemente: contiene un error que puede o no mostrarse.

Violando las pautas

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

faulty_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, entonces 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 lo hace garantizar que funcionará

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 para crear uno que solo se pueda usar una vez

seldom_correct.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), luego cada archivo fuente termina con su propio archivo privado versión de la variable 'global'.

Si la variable es realmente una matriz compleja, por ejemplo, esto puede conducir a la duplicación extrema de código. Puede, muy ocasionalmente, ser un forma sensata de lograr algún efecto, pero eso es muy inusual.


Resumen

Usa 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 es incluido en cada archivo que lo usa, incluido el que lo define. Esto asegura que todo sea auto consistente.

Preocupaciones similares surgen con la declaración y definición de funciones reglas análogas se aplican. Pero la pregunta era sobre las variables específicamente, así que he mantenido el responder solo a las variables

Fin de la respuesta original

Si no eres un programador experimentado de C, probablemente deberías dejar de leer aquí.


Última adición importante

Evitar la duplicación de código

Una preocupación que a veces (y legítimamente) se plantea sobre la el mecanismo 'declaraciones en cabeceras, definiciones en origen' descrito aquí hay dos archivos para mantener sincronizados: el encabezado y la fuente. Esto generalmente se sigue con la observación de que la macro se puede usar para que el encabezado sirva doble tarea, normalmente declarando las variables, pero cuando se establece una macro específica antes del encabezado está incluido, 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 es normalmente una preocupación espuria; tú simplemente puede introducir un archivo fuente C para definir las variables y el enlace el archivo objeto producido con cada uno de los programas.

Un esquema típico funciona así, usando la variable global original ilustrado 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 de variable

El problema con este esquema como se muestra es que no proporciona inicialización de la variable global. Con C99 o C11 y argumento variable listas para macros, podría definir una macro para admitir la inicialización también. (Con C89 y no admite listas de argumentos variables en macros, no hay 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 });

Reverse los contenidos de #if y #else bloques, solucionando errores identificados 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 oddball no es lo que normalmente escribe, pero ilustra el punto. El primer argumento para el segundo 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, inicializadores que necesitan contener comas es muy problemático.

Corregir encabezado 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 encabezado

Cualquier encabezado debe estar protegido contra la reincorporación, por lo que ese tipo definiciones (enum, struct o union types, o typedefs generalmente) no causar 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 se puede incluir dos veces indirectamente. Por ejemplo, si file4b.h incluye file3b.h para una definición de tipo que no se muestra, y file1b.cnecesita usar ambos encabezados file4b.h y file3b.h, entonces tienes algunos problemas más difíciles de resolver. Claramente, podrías revisar la lista de encabezado para incluir solo file4b.h. Sin embargo, es posible que no seas consciente de las dependencias internas, y el código debería, idealmente, sigue trabajando

Además, comienza a ser complicado porque podrías incluir file4b.h antes de incluir file3b.h para generar las definiciones, pero la normal guardias de encabezado en file3b.h evitaría que el encabezado se vuelva a incluir.

Entonces, necesitas incluir el cuerpo de file3b.h a lo sumo una vez por 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 usa).

Inclusión múltiple con definiciones de variables

Sin embargo, puede hacerse sujeto a una restricción no demasiado irracional. Vamos a presentar un nuevo conjunto de nombres de archivos:

  • external.h para las definiciones de macro EXTERN, etc.
  • file1c.h para definir tipos (notablemente 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 y file6c.c incluir 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 incluyó 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 cualquier tipo.
  2. Inmediatamente antes de incluir un encabezado que debe definir variables, usted define la macro DEFINE_VARIABLES.
  3. El encabezado que define o declara las variables tiene contenidos estilizados.

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

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 siguiente archivo fuente 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 encabezado que define variables (como file2c.h) está incluido por otro encabezado (por ejemplo file7c.h) que define las variables. No hay un manera fácil de evitar eso que no sea "no lo hagas".

Puede solucionar parcialmente el problema revisando file2c.h dentro 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 deberías estar limpio. Es solo una molestia para tiene que recordar escribir la línea adicional. Una alternativa podría ser:

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

externdef.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 está un poco complicado, pero parece ser seguro (usando el file2d.h, con ningún #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 tomas el consejo estándar para

Evitar variables globales


¿Esta exposición se pierde algo?

Confesión: El esquema 'evitar el código duplicado' esbozado aquí fue desarrollado porque el problema afecta a algunos códigos en los que trabajo (pero que no son de mi propiedad), y es una preocupación preocupante con el esquema descrito en la primera parte de la respuesta. Sin embargo, el esquema original te deja solo dos lugares para modificar para mantener definiciones de variables y declaraciones sincronizado, que es un gran paso adelante para tener una variable de ejercicio declaraciones dispersas en la base de códigos (que realmente importa cuando hay miles de archivos en total). Sin embargo, el código en el 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 crea un script de generador de encabezado para darte la plantilla estandarizada para una variable que define y declara el archivo de encabezado.

nótese bien Estos son programas de juguete con apenas el código suficiente para hacerlos marginalmente interesante. Hay 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 están incluidos. Sería posible reorganizar el código para que el main() la función no se repitió, pero ocultaría más de lo que revela.)


1482
2017-09-16 14:37



Un extern variable es una declaración (gracias a sbi para 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 tienes dos .carchivos test1.c y test2.c. Si defines una variable global int test1_var; en test1.c y le gustaría acceder a esta variable en test2.c tienes que usar 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

108
2017-09-16 14:12



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

Entonces puede decidir usar una variable en una unidad de traducción y luego acceder a ella desde otra, luego en la segunda declararla como externa y el símbolo será resuelto por el vinculador.

Si no lo declaras como externo obtendrás 2 variables llamadas igual pero no relacionadas en absoluto, y un error de múltiples definiciones de la variable.


34
2017-09-16 14:11



Me gusta pensar en una variable externa como una promesa que haces al compilador.

Cuando se encuentra con un extern, el compilador solo puede encontrar su tipo, no donde "vive", por lo que no puede resolver la referencia.

Usted le está diciendo: "Confíe en mí. En el momento del enlace, esta referencia se podrá resolver".


23
2017-09-16 14:50



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 extern, pero no puede vincular si esa memoria no está declarada en alguna parte.

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


17
2017-09-16 14:18



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


15
2017-09-16 14:16



La interpretación correcta de extern es que le dices algo al compilador. Usted le dice al compilador que, a pesar de no estar presente en este momento, la variable declarada de alguna manera será encontrada por el enlazador (típicamente en otro objeto (archivo)). El enlazador será el tipo afortunado para encontrar todo y armarlo, ya sea que tengas algunas declaraciones externas o no.


11
2018-06-20 23:43



En C una variable dentro de un archivo dice ejemplo.c tiene alcance local. El compilador espera que la variable tenga su definición dentro del mismo archivo example.c y cuando no encuentre lo mismo, arrojaría un error. Por otro lado, una función tiene por defecto alcance global. Por lo tanto, no tiene que mencionar explícitamente al compilador "mira amigo ... puede encontrar aquí la definición de esta función". Para una función que incluye el archivo que contiene su declaración es suficiente. (El archivo que en realidad se 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 ejemplo.ejemplo1.c ejemplo1.c paso 2) ./ ex

Obtiene la siguiente salida: El valor de a es <5>


8
2017-07-02 09:11



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


7
2017-08-20 10:19



Implementación GCC ELF Linux

main.c:

#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

los System V ABI Update ELF spec 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 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 el estándar C le da a extern variables.

A partir de ahora, es el trabajo del vinculador para 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.


5
2018-05-29 07:34