Как использовать extern для обмена переменными между исходными файлами? - PullRequest
907 голосов
/ 16 сентября 2009

Я знаю, что глобальные переменные в C иногда имеют ключевое слово extern. Что такое переменная extern? Как выглядит декларация? Каков его объем?

Это связано с совместным использованием переменных в исходных файлах, но как это работает точно? Где я могу использовать extern?

Ответы [ 17 ]

1625 голосов
/ 16 сентября 2009

Использование extern имеет значение только при создании программы состоит из нескольких исходных файлов, связанных вместе, где некоторые из переменные, определенные, например, в исходном файле file1.c, должны быть упоминается в других исходных файлах, таких как file2.c.

Важно понять разницу между определением a переменная и декларирующая a переменная :

  • Переменная объявляется , когда компилятору сообщают, что переменная существует (и это ее тип); он не выделяет хранилище для переменной в этой точке.
  • Переменная определяется , когда компилятор выделяет хранилище для переменная.

Вы можете объявлять переменную несколько раз (хотя достаточно одного); вы можете определить его только один раз в рамках данной области. Определение переменной также является объявлением, но не всей переменной декларации являются определениями.

Лучший способ объявить и определить глобальные переменные

Чистый, надежный способ объявления и определения глобальных переменных - это использование заголовочный файл, содержащий extern объявление переменной.

Заголовок включен в один исходный файл, который определяет переменную и всеми исходными файлами, которые ссылаются на переменную. Для каждой программы один исходный файл (и только один исходный файл) определяет переменная. Аналогично, один заголовочный файл (и только один заголовочный файл) должен объявить переменная. Заголовочный файл имеет решающее значение; это позволяет перекрестную проверку между независимые TU (единицы перевода - думаю, исходные файлы) и обеспечивает консистенция.

Хотя есть и другие способы сделать это, этот метод прост и надежный. Это демонстрируют file3.h, file1.c и 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++);
}

Это лучший способ объявить и определить глобальные переменные.


Следующие два файла завершают источник для prog1:

Показанные полные программы используют функции, поэтому объявления функций имеют подкрался. И C99, и C11 требуют, чтобы функции были объявлены или определены до того, как они используются (в то время как C90 нет, по уважительным причинам). Я использую ключевое слово extern перед объявлениями функций в заголовках для согласованности - чтобы соответствовать extern перед переменной объявления в заголовках. Многие люди предпочитают не использовать extern перед функцией декларации; компилятору все равно - и, в конечном счете, я тоже если вы последовательны, по крайней мере, в исходном файле.

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 использует prog1.c, file1.c, file2.c, file3.h и prog1.h.

Файл prog1.mk является make-файлом только для prog1. Он будет работать с большинством версий make, выпущенных с начала тысячелетия. Он не привязан специально к 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}

Руководство

Правила должны нарушаться только экспертами и только по уважительной причине:

  • Заголовочный файл содержит только extern объявления переменных - никогда static или определения неполных переменных.
  • Для любой данной переменной только один заголовочный файл объявляет ее (SPOT - Единая точка истины).
  • Исходный файл никогда не содержит extern объявлений переменных - исходные файлы всегда включают (единственный) заголовок, который их объявляет.
  • Для любой данной переменной точно один исходный файл определяет переменную, желательно инициализировать это тоже. (Хотя в этом нет необходимости инициализировать явно до нуля, это не вредит и может принести пользу, потому что может быть только одно инициализированное определение конкретного глобальная переменная в программе).
  • Исходный файл, который определяет переменную, также включает заголовок убедитесь, что определение и декларация согласованы.
  • Функция никогда не должна объявлять переменную, используя extern.
  • Избегайте гЛобальные переменные, когда это возможно - используйте вместо них функции.

Исходный код и текст этого ответа доступны в моем SOQ (Вопросы о переполнении стека) репозиторий на GitHub в ЦСИ / так 0143-3204 подкаталог.

Если вы не опытный программист на C, вы можете (и, возможно, следует) перестаньте читать здесь.

Не очень хороший способ определения глобальных переменных

С некоторыми (на самом деле, многими) компиляторами C вы можете избежать неприятностей с тем, что называется «общим» определением переменной тоже. «Общий» здесь относится к технике, используемой в Фортране для обмена переменные между исходными файлами, используя (возможно именованный) блок COMMON. Здесь происходит то, что каждый из нескольких файлов определение переменной. Пока не более одного файла обеспечивает инициализированное определение, затем различные файлы в конечном итоге разделяют общее единственное определение переменная:

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

Этот метод не соответствует букве стандарта С и «Правило одного определения» - это официально неопределенное поведение:

J.2 Неопределенное поведение

Используется идентификатор с внешней связью, но в программе есть не существует ровно одного внешнего определения для идентификатора, или идентификатор не используется и существует несколько внешних определения для идентификатора (6.9).

§6.9 Внешние определения ¶5

внешнее определение является внешней декларацией, которая также является определение функции (кроме встроенного определения) или объект. Если идентификатор, объявленный с внешней связью, используется в выражение (кроме как часть операнда sizeof или _Alignof оператор, результатом которого является целочисленная константа), где-то в во всей программе должно быть ровно одно внешнее определение идентификатор; в противном случае должно быть не более один. 161)

161) Таким образом, если идентификатор объявлен с внешней связью не используется в выражении, не должно быть внешнего определения для это.

Тем не менее, стандарт C также перечисляет его в информативном Приложении J в качестве одного из Общие расширения .

J.5.11 Несколько внешних определений

Может быть более одного внешнего определения для идентификатора объект, с или без явного использования ключевого слова extern; если определения не согласны или более одного инициализированы, поведение не определено (6.9.2).

Поскольку эта техника не всегда поддерживается, лучше избегать особенно если ваш код должен быть переносимым . Используя эту технику, вы также можете получить непреднамеренный тип каламбурная. Если один из файлов объявлен i как double вместо int, Небезопасные линкеры C, вероятно, не обнаружат несоответствия. Если вы работаете на машине с 64-битными int и double, вы даже не получить предупреждение; на машине с 32-битной int и 64-битной double, вы бы вероятно, получите предупреждение о разных размерах - компоновщик будет использовать самый большой размер, точно так же, как программа на Фортране Наибольший размер среди всех распространенных блоков.


Следующие два файла дополняют исходный код 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 использует prog2.c, file10.c, file11.c, file12.c, prog2.h.

Внимание

Как отмечено здесь в комментариях и как указано в моем ответе на аналогичный вопрос , используя несколько определения глобальной переменной приводят к неопределенному поведению (J.2; §6.9), который является стандартом для выражения «все может произойти». Один изТо, что может произойти, это то, что программа ведет себя так, как вы ожидать; и в J.5.11 примерно сказано: «вам чаще всего везет чем вы заслуживаете ". Но программа, которая опирается на несколько определений внешней переменной - с явным ключевым словом extern или без него - не является строго соответствует программе и не гарантированно работать везде. Эквивалентно: он содержит ошибку, которая может показывать или не показывать себя.

Нарушение руководящих принципов

Есть, конечно, много способов, которыми эти принципы могут быть нарушены. Иногда может быть веская причина нарушать руководящие принципы, но такие случаи крайне необычны.

faulty_header.h

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

Примечание 1: если заголовок определяет переменную без ключевого слова extern, тогда каждый файл, который включает в себя заголовок, создает предварительное определение переменной. Как отмечалось ранее, это часто будет работать, но стандарт C не гарантировать, что это будет работать.

broken_header.h

int some_var = 13;    /* Only one source file in a program can use this */

Примечание 2: если заголовок определяет и инициализирует переменную, то только один исходный файл в данной программе может использовать заголовок. Поскольку заголовки предназначены главным образом для обмена информацией, это немного глупо создать тот, который может быть использован только один раз.

seldom_correct.h

static int hidden_global = 3;   /* Each source file gets its own copy  */

Примечание 3: если заголовок определяет статическую переменную (с или без инициализации), то каждый исходный файл заканчивается своим собственным версия «глобальной» переменной.

Если переменная на самом деле представляет собой сложный массив, например, это может привести до крайнего дублирования кода. Иногда это может быть разумный способ добиться какого-то эффекта, но это очень необычно.


Резюме

Используйте технику заголовка, которую я показал первым. Работает надежно и везде. Обратите внимание, в частности, что заголовок, объявляющий global_variable, включается в каждый файл, который его использует, включая тот, который его определяет. Это гарантирует, что все является самосогласованным.

Аналогичные проблемы возникают при объявлении и определении функций - применяются аналогичные правила. Но вопрос был конкретно о переменных, поэтому я сохранил ответ только на переменные.

Конец оригинального ответа

Если вы не опытный программист на C, вам, вероятно, следует прекратить читать здесь.


Позднее крупное добавление

Предотвращение дублирования кода

Одна проблема, которая иногда (и законно) поднимается о описан механизм «объявления в заголовках, определения в источнике» здесь есть два файла для синхронизации - заголовок и источник. Обычно это сопровождается наблюдением, что макрос можно использовать так, чтобы заголовок выполнял двойную функцию - обычно объявляя переменные, но когда конкретный макрос установлен перед заголовок включен, вместо него определены переменные.

Другая проблема может заключаться в том, что переменные должны быть определены в каждом из ряд «основных программ». Это обычно ложная проблема; вы можно просто ввести исходный файл C, чтобы определить переменные и ссылку объектный файл, создаваемый каждой из программ.

Типичная схема работает так, используя оригинальную глобальную переменную показано в 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++);
}

Следующие два файла дополняют исходный код 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 использует prog3.c, file1a.c, file2a.c, file3a.h, prog3.h.

Инициализация переменной

Проблема с этой схемой, как показано в том, что она не предусматривает инициализация глобальной переменной. С C99 или C11 и переменным аргументом списки для макросов, вы можете определить макрос для поддержки инициализации тоже. (С C89 и без поддержки списков переменных аргументов в макросах нет простой способобрабатывать произвольно длинные инициализаторы.)

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

Обратное содержимое блоков #if и #else, исправление ошибки, определенной Денис Княжев

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

Ясно, что код для странной структуры не тот, который вы обычно делаете пиши, но это иллюстрирует суть. Первый аргумент второму вызов INITIALIZER равен { 41, а оставшийся аргумент (единственное число в этом примере) - 43 }. Без C99 или аналогичной поддержки для переменных списков аргументов для макросов, инициализаторов, которые должны содержать запятые очень проблематично.

Правильный заголовок file3b.h включен (вместо fileba.h) в Денис Княжев


Следующие два файла дополняют источник для 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 использует prog4.c, file1b.c, file2b.c, prog4.h, file3b.h.

Заголовок стражи

Любой заголовок должен быть защищен от повторного включения, так что тип определения (типы enum, struct или union, или typedefs вообще) не вызвать проблемы. Стандартная техника заключается в том, чтобы обернуть тело Заголовок в заголовке, например:

#ifndef FILE3B_H_INCLUDED
#define FILE3B_H_INCLUDED

...contents of header...

#endif /* FILE3B_H_INCLUDED */

Заголовок может быть включен дважды косвенно. Например, если file4b.h включает file3b.h для определения типа, которое не отображается, и file1b.c необходимо использовать оба заголовка file4b.h и file3b.h, затем у вас есть еще несколько хитрых вопросов, которые нужно решить. Очевидно, вы могли бы пересмотреть список заголовков, чтобы включить только file4b.h. Тем не менее, вы не можете быть знать о внутренних зависимостях - и в идеале код должен продолжать работать.

Кроме того, все становится сложнее, потому что вы можете включить file4b.h перед включением file3b.h для генерации определений, но обычный защита заголовка на file3b.h предотвратит повторное включение заголовка.

Итак, вам нужно включить тело file3b.h самое большее один раз для декларации, и не более одного раза для определений, но вам могут понадобиться оба в одной единице перевода (TU - комбинация исходного файла и заголовки, которые он использует).

Множественное включение с определениями переменных

Однако это может быть сделано при условии не слишком необоснованного ограничения. Давайте введем новый набор имен файлов:

  • external.h для макроопределений EXTERN и т. Д.
  • file1c.h для определения типов (в частности, struct oddball, тип oddball_struct).
  • file2c.h для определения или объявления глобальных переменных.
  • file3c.c, который определяет глобальные переменные.
  • file4c.c, который просто использует глобальные переменные.
  • file5c.c, который показывает, что вы можете объявить и затем определить глобальные переменные.
  • file6c.c, который показывает, что вы можете определить, а затем (попытаться) объявить глобальные переменные.

В этих примерах file5c.c и file6c.c напрямую включают заголовок file2c.h несколько раз, но это самый простой способ показать, что механизм работает. Это означает, что если заголовок был косвенно включен дважды, это также будет безопасно.

Ограничения для этой работы:

  1. Заголовок, определяющий или объявляющий глобальные переменные, не может сам по себе определить любые типы.
  2. Непосредственно перед включением заголовка, который должен определять переменные, Вы определяете макрос DEFINE_VARIABLES.
  3. Заголовок, определяющий или объявляющий переменные, имеет стилизованное содержимое.

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

Следующий исходный файл завершает источник (предоставляет основную программу) для prog5, prog6 и 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 использует prog5.c, file3c.c, file4c.c, file1c.h, file2c.h, external.h.
  • prog6 использует prog5.c, file5c.c, file4c.c, file1c.h, file2c.h, external.h.
  • prog7 использует prog5.c, file6c.c, file4c.c, file1c.h, file2c.h, external.h.

Эта схема позволяет избежать большинства проблем. Вы только столкнетесь с проблемой, если Заголовок, который определяет переменные (такие как file2c.h), включается другой заголовок (скажем, file7c.h), который определяет переменные. Там нет простой способ обойти это, кроме "не делай этого".

Вы можете частично обойти проблему, изменив file2c.h на 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 */

Вопрос становится «должен ли заголовок включать #undef DEFINE_VARIABLES?» Если вы пропустите это из заголовка и закроете любой определяющий вызов #define и #undef:

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

в исходном коде (поэтому заголовки никогда не изменяют значение DEFINE_VARIABLES), тогда вы должны быть чистыми. Это просто неприятность должны помнить, чтобы написать дополнительную строку. Альтернативой может быть:

#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 */

Это становится немного запутанным, но кажется безопасным (используя file2d.h, без #undef DEFINE_VARIABLES в 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; }

Следующие два файла завершают источник для prog8 и 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 использует prog8.c, file7c.c, file9c.c.
  • prog9 использует prog8.c, file8c.c, file9c.c.

Однако проблемы относительно маловероятны на практике, особенно если принять стандартный совет к

Избегайте глобальных переменных


Что-то пропускает эта экспозиция?

Исповедь : Схема «избежания дублирования кода», описанная здесь, была возникла потому, что проблема затрагивает некоторый код, над которым я работаю (но не владею), и вызывает озабоченность по поводу схемы, изложенной в первой части ответ. Тем не менее, оригинальная схема оставляет вам только два места для изменения, чтобы сохранить определения и объявления переменных синхронизированный, что является большим шагом вперед по сравнению с наличием внешней переменной объявления разбросаны по всей базе кода (что действительно имеет значение когда есть тысячи файлов в общей сложности). Тем не менее, код в файлы с именами fileNc.[ch] (плюс external.h и externdef.h) показывает, что его можно заставить работать. Понятно, что было бы не сложно создать скрипт генератора заголовков, чтобы дать вам стандартизированный шаблон для переменной, определяющей и объявляющей файл заголовка.

NB Это игрушечные программы с едва достаточным кодом, чтобы сделать их незначительно интересно. В примерах есть повторение может быть удалено, но не для упрощения педагогического объяснения. (Например: разница между prog5.c и prog8.c является именем одного из заголовков, которые включены. Было бы возможно реорганизовать код так, чтобы функция main() не повторялась, а это скрыло бы больше чем это показало.)

116 голосов
/ 16 сентября 2009

Переменная extern - это объявление (спасибо sbi за исправление) переменной, которая определена в другой единице перевода. Это означает, что хранилище для переменной выделено в другом файле.

Скажем, у вас есть два .c -файла test1.c и test2.c. Если вы определяете глобальную переменную int test1_var; в test1.c и хотите получить доступ к этой переменной в test2.c, вы должны использовать extern int test1_var; в test2.c.

Полный образец:

$ 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
37 голосов
/ 16 сентября 2009

Extern - это ключевое слово, которое вы используете, чтобы объявить, что сама переменная находится в другой единице перевода.

Таким образом, вы можете решить использовать переменную в блоке перевода, а затем получить к ней доступ из другой, затем во второй вы объявите ее как extern, и символ будет обработан компоновщиком.

Если вы не объявите его как extern, вы получите 2 переменные с одинаковыми именами, но никак не связанные между собой, и ошибку нескольких определений переменной.

25 голосов
/ 16 сентября 2009

Мне нравится воспринимать переменную extern как обещание, которое вы даете компилятору.

При обнаружении extern компилятор может определить только его тип, а не место, где он "живет", поэтому он не может разрешить ссылку.

Вы говорите: «Поверьте мне. Во время ссылки эта ссылка будет разрешена».

18 голосов
/ 16 сентября 2009

extern говорит компилятору доверять вам, что память для этой переменной объявлена ​​в другом месте, поэтому он не пытается выделить / проверить память.

Следовательно, вы можете скомпилировать файл, который имеет ссылку на extern, но вы не можете создать ссылку, если эта память где-то не объявлена.

Полезно для глобальных переменных и библиотек, но опасно, потому что компоновщик не проверяет тип.

15 голосов
/ 16 сентября 2009

Добавление extern превращает переменную определение в переменную объявление . См. эту ветку о разнице между объявлением и определением.

11 голосов
/ 21 июня 2012

Правильная интерпретация extern заключается в том, что вы что-то говорите компилятору. Вы сообщаете компилятору, что, несмотря на отсутствие в данный момент, объявленная переменная каким-то образом будет найдена компоновщиком (обычно в другом объекте (файле)). Тогда линкер станет счастливчиком, который все найдет и соберет, независимо от того, были ли у вас какие-то внешние объявления или нет.

8 голосов
/ 02 июля 2012

В C переменная внутри файла, скажем, example.c, имеет локальную область видимости. Компилятор ожидает, что переменная будет иметь свое определение в том же файле example.c, и если он не найдет его, он выдаст ошибку. С другой стороны, функция по умолчанию имеет глобальную область видимости. Таким образом, вам не нужно явно упоминать компилятору «смотрите, чувак ... вы можете найти определение этой функции здесь». Для функции, включающей файл, содержащий ее объявление, достаточно (файл, который вы фактически называете заголовочным файлом). Например, рассмотрим следующие 2 файла:
example.c

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

example1.c

int a = 5;

Теперь, когда вы скомпилируете два файла вместе, используйте следующие команды:

шаг 1) cc -o ex example.c example1.c шаг 2) ./ ex

Вы получите следующий вывод: значение a равно <5>

7 голосов
/ 20 августа 2012

ключевое слово extern используется с переменной для ее идентификации в качестве глобальной переменной.

Это также означает, что вы можете использовать переменную, объявленную с использованием extern. ключевое слово в любом файле, хотя оно объявлено / определено в другом файле.

6 голосов

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

Компилировать и декомпилировать:

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

Вывод содержит:

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

Спецификация ELF System V ABI Update В главе «Таблица символов» объясняется:

SHN_UNDEF Этот индекс таблицы разделов означает, что символ не определен. Когда редактор ссылок объединяет этот объектный файл с другим, который определяет указанный символ, ссылки этого файла на символ будут связаны с фактическим определением.

, что в основном соответствует стандарту C для extern переменных.

Теперь компоновщик должен создавать окончательную программу, но информация extern уже извлечена из исходного кода в объектный файл.

Протестировано на GCC 4.8.

C ++ 17 встроенных переменных

В C ++ 17 вы можете захотеть использовать встроенные переменные вместо внешних, поскольку они просты в использовании (могут быть определены только один раз в заголовке) и более мощны (поддерживают constexpr). См .: Что означает «const static» в C и C ++?

...