public class TestClass{
public static void main(String args[] ){
int i = 0 ;
int[] iA = {10, 20} ;
iA[i] = i = 30 ;
System.out.println(""+ iA[ 0 ] + " " + iA[ 1 ] + " "+i) ;
} }
Он будет печатать 30 20 30
Утверждение iA [i] = i = 30; будет обрабатываться следующим образом:
iA [i] = i = 30; => iA [0] = i = 30; => i = 30; iA [0] = i; => iA [0] = 30;
Вот что говорит JLS об этом:
1 Оцените левую операнду Сначала 2 Оцените операнды до операции 3 Оценка Уважение скобок и приоритет 4 Списки аргументов оцениваются слева направо
Для массивов: во-первых, выражения измерения оцениваются слева направо. Если какая-либо из оценок выражений завершается внезапно, выражения, находящиеся справа от нее, не оцениваются.
Adding an extern
turns a variable definition into a variable declaration. See this thread as to what's the difference between a declaration and a definition.
extern сообщает компилятору доверять вам, что память для этой переменной объявлена в другом месте, поэтому он не пытается выделить / проверить память.
Следовательно, вы можете скомпилировать файл, имеющий ссылку на extern, но вы не можете связать, если эта память где-то не объявлена.
Полезно для глобальных переменных и библиотек, но опасно, потому что компоновщик не проверяет тип.
Мне нравится думать о переменной extern как об обещании, которое вы даете компилятору.
При обнаружении extern компилятор может узнать только его тип, а не то, где он «живет» ", поэтому он не может разрешить ссылку.
Вы говорите ему:« Поверьте мне. Во время ссылки эта ссылка будет разрешена ».
Extern - это ключевое слово, которое вы используете, чтобы объявить, что сама переменная находится в другой единице перевода.
Таким образом, вы можете решить использовать переменную в единицу перевода, а затем получить к ней доступ из другой единицы,
Переменная 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
Using extern
is only of relevance when the program you're building
consists of multiple source files linked together, where some of the
variables defined, for example, in source file file1.c
need to be
упоминается в других исходных файлах, таких как file2.c
.
Важно понимать разницу между определением a переменная и объявление a переменная :
Вы можете объявить переменную несколько раз (хотя одного раза достаточно); вы можете определить его только один раз в пределах заданной области. Определение переменной также является объявлением, но не все переменные объявления - это определения.
Чистый и надежный способ объявления и определения глобальных переменных - это использовать
файл заголовка, содержащий объявление extern
переменной.
Заголовок включен в один исходный файл, который определяет переменную и всеми исходными файлами, которые ссылаются на переменную. Для каждой программы один исходный файл (и только один исходный файл) определяет переменная. Точно так же один файл заголовка (и только один файл заголовка) должен объявлять переменная. Заголовочный файл имеет решающее значение; это позволяет перекрестную проверку между независимые ЕП (единицы перевода - думаю, исходные файлы) и обеспечивает последовательность.
Хотя есть и другие способы добиться этого, этот метод прост и
надежный.
Это демонстрируется file3.h
, file1.c
и file2.c
:
extern int global_variable; /* Declaration of the variable */
#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++; }
#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
перед функцией.
декларации; компилятору все равно - и, в конечном счете, я тоже
пока вы последовательны, по крайней мере, в исходном файле.
extern void use_it(void);
extern int increment(void);
#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.
# 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
] объявления переменных - никогда
статические
или неквалифицированные определения переменных. extern
объявлений переменных -
исходные файлы всегда включают (единственный) заголовок, который их объявляет. extern
. Исходный код и текст этого ответа доступны в моем SOQ (Stack Overflow Questions) repository on GitHub in the src/so-0143-3204 sub-directory.
If you're not an experienced C programmer, you could (and perhaps should) stop reading here.
With some (indeed, many) C compilers, you can get away with what's called a 'common' definition of a variable too. 'Common', here, refers to a technique used in Fortran for sharing variables between source files, using a (possibly named) COMMON block. What happens here is that each of a number of files provides a tentative definition of the variable. As long as no more than one file provides an initialized definition, then the various files end up sharing a common single definition of the переменная:
#include "prog2.h"
int i; /* Do not do this in portable code */
void inc(void) { i++; }
#include "prog2.h"
int i; /* Do not do this in portable code */
void dec(void) { i--; }
#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).
Поскольку этот метод не всегда поддерживается, лучше избегать
using it, especially if your code needs to be portable.
Using this technique, you can also end up with unintentional type
punning.
If one of the files declared i
as a double
instead of as an int
,
C's type-unsafe linkers probably would not spot the mismatch.
If you're on a machine with 64-bit int
and double
, you'd not even
get a warning; on a machine with 32-bit int
and 64-bit double
, you'd
probably get a warning about the different sizes — the linker would
use the largest size, exactly as a Fortran program would take the
наибольший размер среди общих блоков.
Следующие два файла завершают исходный код для prog2
:
extern void dec(void);
extern void put(void);
extern void inc(void);
#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 или без него - не является строго соответствующая программа и не гарантированно работает везде. Эквивалентно: он содержит ошибку, которая может проявляться, а может и не проявляться.
Конечно, есть много способов, которыми эти правила могут быть нарушены. Иногда может быть веская причина нарушить правила, но такие случаи крайне необычны.
int some_var; /* Do not do this in a header!!! */
Примечание 1: если заголовок определяет переменную без ключевого слова extern
,
затем каждый файл, который включает заголовок, создает предварительное определение
переменной.
Как отмечалось ранее, это часто работает, но стандарт C не работает.
гарантировать, что он будет работать.
int some_var = 13; /* Only one source file in a program can use this */
Примечание 2: если заголовок определяет и инициализирует переменную, то только один исходный файл в данной программе может использовать заголовок. Поскольку заголовки предназначены в первую очередь для обмена информацией, это немного глупо. чтобы создать тот, который можно использовать только один раз.
static int hidden_global = 3; /* Each source file gets its own copy */
Примечание 3: если заголовок определяет статическую переменную (с или без инициализация), то каждый исходный файл получает свой собственный версия "глобальной" переменной.
Если переменная на самом деле представляет собой сложный массив, например, это может привести к к крайнему дублированию кода. Иногда это может быть разумный способ добиться некоторого эффекта, но это очень необычно.
Используйте метод заголовка, который я показал первым.
Работает надежно и везде.
Обратите внимание, в частности, что заголовок, объявляющий global_variable
, является
включены в каждый файл, который его использует, включая тот, который его определяет.
Это гарантирует, что все самосогласованно.
Аналогичные проблемы возникают при объявлении и определении функций - применяются аналогичные правила. Но вопрос был конкретно о переменных, поэтому я оставил ответ только на переменные.
Если вы не опытный программист на C, вам, вероятно, следует прекратить читать здесь.
Позднее основное добавление
Одна проблема, которая иногда (и законно) поднимается о описан механизм 'объявления в заголовках, определения в источнике' здесь есть два файла, которые нужно синхронизировать - заголовок и источник. Обычно за этим следует наблюдение, что макрос может использоваться, чтобы заголовок выполнял двойную функцию - обычно объявление переменных, но когда конкретный макрос установлен перед заголовок включен, вместо этого он определяет переменные.
Другая проблема может заключаться в том, что переменные должны быть определены в каждом из ряд «основных программ». Обычно это ложное беспокойство; ты можно просто ввести исходный файл C для определения переменных и ссылки объектный файл, созданный каждой из программ.
Типичная схема работает следующим образом, с использованием исходной глобальной переменной
illustrated in file3.h
:
#ifdef DEFINE_VARIABLES
#define EXTERN /* nothing */
#else
#define EXTERN extern
#endif /* DEFINE_VARIABLES */
EXTERN int global_variable;
#define DEFINE_VARIABLES
#include "file3a.h" /* Variable defined - but not initialized */
#include "prog3.h"
int increment(void) { return global_variable++; }
#include "file3a.h"
#include "prog3.h"
#include <stdio.h>
void use_it(void)
{
printf("Global variable: %d\n", global_variable++);
}
The next two files complete the source for prog3
:
extern void use_it(void);
extern int increment(void);
#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
uses prog3.c
, file1a.c
, file2a.c
, file3a.h
, prog3.h
.The problem with this scheme as shown is that it does not provide for initialization of the global variable. With C99 or C11 and variable argument lists for macros, you could define a macro to support initialization too. (With C89 and no support for variable argument lists in macros, there is no easy way to handle arbitrarily long initializers.)
#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 contents of #if
and #else
blocks, fixing bug identified by
Denis Kniazhev
#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; }
#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;
}
Clearly, the code for the oddball structure is not what you'd normally
write, but it illustrates the point. The first argument to the second
invocation of INITIALIZER
is { 41
and the remaining argument
(singular in this example) is 43 }
. Without C99 or similar support
for variable argument lists for macros, initializers that need to
contain commas are very problematic.
Correct header file3b.h
included (instead of fileba.h
) per
Денис Княжев
Следующие два файла дополняют исходный код для prog4
:
extern int increment(void);
extern int oddball_value(void);
extern void use_them(void);
#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
, тогда
you have some more tricky issues to resolve. Clearly, you might revise
the header list to include just file4b.h
. However, you might not be
aware of the internal dependencies — and the code should, ideally,
continue to work.
Further, it starts to get tricky because you might include file4b.h
before including file3b.h
to generate the definitions, but the normal
header guards on file3b.h
would prevent the header being reincluded.
So, you need to include the body of file3b.h
at most once for
declarations, and at most once for definitions, but you might need both
in a single translation unit (TU — a combination of a source file and
the headers it uses).
However, it can be done subject to a not too unreasonable constraint. Let's introduce a new set of file names:
external.h
for the EXTERN macro definitions, etc.file1c.h
to define types (notably, struct oddball
, the type of oddball_struct
).file2c.h
to define or declare the global variables.file3c.c
which defines the global variables.file4c.c
which simply uses the global variables.file5c.c
which shows that you can declare and then define the global variables.file6c.c
which shows that you can define and then (attempt to) declare the global variables.In these examples, file5c.c
and file6c.c
directly include the header
file2c.h
several times, but that is the simplest way to show that the
механизм работает. Это означает, что если заголовок был включен косвенно
дважды, это также будет безопасно.
Ограничения для этого:
file4c.c
, file1c.h
, file2c.h
, external.h
. Эта схема позволяет избежать большинства проблем. Вы столкнетесь с проблемой, только если
заголовок, определяющий переменные (например, file2c.h
), включается
другой заголовок (скажем, file7c.h
), который определяет переменные. Нет
простой способ обойти это, кроме «не делай этого».
Вы можете частично обойти проблему, изменив file2c.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
), тогда вы должны быть чисты. Это просто неприятность
have to remember to write the the extra line. An alternative might be:
#define HEADER_DEFINING_VARIABLES "file2c.h"
#include "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 */
This is getting a tad convoluted, but seems to be secure (using the
file2d.h
, with no #undef DEFINE_VARIABLES
in the file2d.h
).
/* 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; }
/* 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 */
/* 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; }
The next two files complete the source for prog8
and prog9
:
#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;
}
#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
uses prog8.c
, file7c.c
, file9c.c
.prog9
uses prog8.c
, file8c.c
, file9c.c
.However, the problems are relatively unlikely to occur in practice, especially if you take the standard advice to
Does this exposition miss anything?
Confession: The 'avoiding duplicated code' scheme outlined here was
developed because the issue affects some code I work on (but don't own),
and is a niggling concern with the scheme outlined in the first part of
the answer. However, the original scheme leaves you with just two
places to modify to keep variable definitions and declarations
synchronized, which is a big step forward over having exernal variable
declarations scattered throughout the code base (which really matters
when there are thousands of files in total). However, the code in the
files with the names fileNc.[ch]
(plus external.h
and externdef.h
)
shows that it can be made to work. Clearly, it would not be hard to
create a header generator script to give you the standardized template
for a variable defining and declaring header file.
NB These are toy programs with just barely enough code to make them
marginally interesting. There is repetition within the examples that
could be removed, but isn't to simplify the pedagogical explanation.
(For example: the difference between prog5.c
and prog8.c
is the name
of one of the headers that are included. It would be possible to
reorganize the code so that the main()
function was not repeated, but
it would conceal more than it revealed.)