Ответ 1
Использование extern
имеет extern
только в том случае, когда создаваемая вами программа состоит из нескольких исходных файлов, связанных друг с другом, где некоторые из переменных, определенных, например, в исходном файле file1.c
должны быть указаны в других исходных файлах, таких как file2.c
.
Важно понимать разницу между определением переменной и объявлением переменной:
- Переменная объявляется, когда компилятору сообщают, что существует переменная (и это ее тип); он не выделяет хранилище для переменной в этой точке.
- Переменная определяется, когда компилятор выделяет хранилище для переменной.
Вы можете объявить переменную несколько раз (хотя один раз достаточно); вы можете определить его только один раз в заданной области. Определение переменной также является объявлением, но не все объявления переменных являются определениями.
Лучший способ объявить и определить глобальные переменные
Чистый, надежный способ объявить и определить глобальные переменные - использовать заголовочный файл, чтобы содержать объявление 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
только для 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 [email protected] ${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 - Single Point of Truth).
- Исходный файл никогда не содержит
extern
деклараций переменных - исходные файлы всегда включают заголовок (единственный), который их объявляет. - Для любой заданной переменной ровно один исходный файл определяет переменную, предпочтительно инициализируя ее. (Хотя нет необходимости явно инициализировать нуль, это не наносит вреда и может принести пользу, потому что в программе может быть только одно инициализированное определение конкретной глобальной переменной).
- Исходный файл, который определяет переменную, также включает заголовок, чтобы гарантировать, что определение и декларация согласованы.
- Функция никогда не должна объявлять переменную с использованием
extern
. - Избегайте глобальных переменных, когда это возможно, вместо этого используйте функции.
Исходный код и текст этого ответа доступны в моем репозитории SOQ (Qaru Questions) в GitHub в подкаталоге src/so-0143-3204.
Если вы не опытный программист на C, вы можете (и, возможно, должны) перестать читать здесь.
Не так хороший способ определить глобальные переменные
С некоторыми (действительно, многими) компиляторами C вы можете избавиться от так называемого "общего" определения переменной. "Common" здесь относится к методу, используемому в Fortran для обмена переменными между исходными файлами, используя (возможно, называемый) 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); }
Этот метод не соответствует букве стандарта C и "одному правилу определения" - это официально неопределенное поведение:
Идентификатор с внешней связью используется, но в программе не существует только одного внешнего определения для идентификатора, или идентификатор не используется, и существует множество внешних определений для идентификатора (6.9).
Внешнее определение - это внешнее объявление, которое также является определением функции (отличной от встроенного определения) или объекта. Если идентификатор, объявленный с внешней связью, используется в выражении (кроме как в части операнда оператора
sizeof
или_Alignof
, результат которого является целочисленной константой), где-то во всей программе должно быть ровно одно внешнее определение для идентификатора; в противном случае должно быть не более одного. 161)161) Таким образом, если идентификатор, объявленный с внешней связью, не используется в выражении, для него не должно быть внешнего определения.
Однако стандарт C также перечисляет его в информационном приложении J как одно из общих расширений.
J.5.11 Множество внешних определений
Для идентификатора объекта может быть несколько внешних определений с явным использованием ключевого слова extern; если определения не согласуются или более одного инициализируется, поведение не определено (6.9.2).
Поскольку этот метод не всегда поддерживается, лучше избегать его использования, особенно если ваш код должен быть портативным. Используя эту технику, вы также можете столкнуться с непреднамеренным тиражом. Если один из файлов объявил i
как double
, а не как int
, C типа небезопасных линкеры, вероятно, не заметят несоответствие. Если вы находитесь на машине с 64-битным int
и double
, вы даже не получите предупреждение; на машине с 32-битным int
и 64-битным double
, вы, вероятно, получите предупреждение о разных размерах - компоновщик будет использовать самый большой размер, точно так же, как программа Fortran займет наибольший размер любых общих блоков.
Следующие два файла завершают источник для 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;
}
Очевидно, что код для структуры oddball - это не то, что вы обычно пишете, но это иллюстрирует суть. Первым аргументом второго вызова 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
.
Заголовки
Любой заголовок должен быть защищен от повторного включения, так что определения типов (перечисление, структура или типы соединений или типизация вообще) не создают проблем. Стандартный метод заключается в том, чтобы обернуть тело заголовка в защиту заголовка, например:
#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
будут препятствовать повторному включению заголовка.
Таким образом, вам нужно включить тело 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
несколько раз, но это самый простой способ показать, что механизм работает. Это означает, что если заголовок косвенно включался дважды, это также было бы безопасно.
Ограничения для этого:
- Заголовок, определяющий или объявляющий глобальные переменные, не может сам определять какие-либо типы.
- Непосредственно перед включением заголовка, который должен определять переменные, вы определяете макрос DEFINE_VARIABLES.
- Заголовок, определяющий или объявляющий переменные, имеет стилизованное содержимое.
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
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
.
Тем не менее, проблемы относительно маловероятны на практике, особенно если вы примете стандартный совет для
Избегайте глобальных переменных
Означает ли это изложение что-нибудь?
Исповедь: схема, "исключающая дублированный код", описанная здесь, была разработана, потому что проблема затрагивает некоторый код, над которым я работаю (но не владею), и вызывает беспокойство по поводу схемы, изложенной в первой части ответа. Однако исходная схема оставляет вам только два места для изменения, чтобы синхронизировать определения переменных и декларации, что является большим шагом вперед по сравнению с объявлениями exernal переменных, разбросанными по всей базе кода (что действительно имеет значение, когда в нем всего тысячи файлов), Однако код в файлах с именами fileNc.[ch]
(плюс external.h
и externdef.h
) показывает, что его можно externdef.h
работать. Очевидно, что создать сценарий генератора заголовков не составит труда, чтобы дать вам стандартизованный шаблон для переменной, определяющей и объявляющий заголовочный файл.
NB Это игрушечные программы с едва достаточным количеством кода, чтобы сделать их немного интересными. В примерах, которые можно было бы удалить, есть повторение, но это не должно упрощать педагогическое объяснение. (Например: разница между prog5.c
и prog8.c
- это имя одного из включенных заголовков. Можно было бы реорганизовать код так, чтобы функция main()
не повторялась, но она скрывала бы больше чем это показало.)