Respostas:
O uso extern
é relevante apenas quando o programa que você está construindo consiste em vários arquivos de origem vinculados, onde algumas das variáveis definidas, por exemplo, no arquivo de origem file1.c
precisam ser referenciadas em outros arquivos de origem, como file2.c
.
É importante entender a diferença entre definir uma variável e declarar uma variável :
Uma variável é declarada quando o compilador é informado de que existe uma variável (e esse é o seu tipo); ele não aloca o armazenamento para a variável nesse ponto.
Uma variável é definida quando o compilador aloca o armazenamento para a variável.
Você pode declarar uma variável várias vezes (embora uma vez seja suficiente); você pode defini-lo apenas uma vez dentro de um determinado escopo. Uma definição de variável também é uma declaração, mas nem todas as declarações de variáveis são definições.
A maneira limpa e confiável de declarar e definir variáveis globais é usar um arquivo de cabeçalho para conter uma extern
declaração da variável.
O cabeçalho é incluído no arquivo de origem que define a variável e em todos os arquivos de origem que fazem referência à variável. Para cada programa, um arquivo de origem (e apenas um arquivo de origem) define a variável. Da mesma forma, um arquivo de cabeçalho (e apenas um arquivo de cabeçalho) deve declarar a variável. O arquivo de cabeçalho é crucial; ele permite a verificação cruzada entre TUs independentes (unidades de tradução - pense em arquivos de origem) e garante consistência.
Embora existam outras maneiras de fazer isso, esse método é simples e confiável. É demonstrado por file3.h
, file1.c
e 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++);
}
Essa é a melhor maneira de declarar e definir variáveis globais.
Os próximos dois arquivos completam a fonte para prog1
:
Os programas completos mostrados usam funções; portanto, as declarações de função foram introduzidas. Tanto o C99 quanto o C11 exigem que as funções sejam declaradas ou definidas antes de serem usadas (enquanto o C90 não o fez, por boas razões). Eu uso a palavra-chave extern
na frente das declarações de função nos cabeçalhos para obter consistência - para corresponder à extern
frente das declarações de variável nos cabeçalhos. Muitas pessoas preferem não usar extern
na frente das declarações de função; o compilador não se importa - e, por fim, nem eu, desde que você seja consistente, pelo menos em um arquivo de origem.
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
usos prog1.c
, file1.c
, file2.c
, file3.h
e prog1.h
.O arquivo prog1.mk
é um makefile prog1
apenas. Ele funcionará com a maioria das versões make
produzidas desde a virada do milênio. Não está ligado especificamente ao 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}
Regras a serem quebradas apenas por especialistas e apenas por um bom motivo:
Um arquivo de cabeçalho contém apenas extern
declarações de variáveis - static
definições de variáveis nunca
ou não qualificadas.
Para qualquer variável, apenas um arquivo de cabeçalho a declara (SPOT - Single Point of Truth).
Um arquivo de origem nunca contém extern
declarações de variáveis - os arquivos de origem sempre incluem o cabeçalho (único) que os declara.
Para qualquer variável, exatamente um arquivo de origem define a variável, de preferência inicializando-a também. (Embora não seja necessário inicializar explicitamente para zero, isso não faz mal e pode fazer algum bem, porque pode haver apenas uma definição inicializada de uma variável global específica em um programa).
O arquivo de origem que define a variável também inclui o cabeçalho para garantir que a definição e a declaração sejam consistentes.
Uma função nunca deve precisar declarar uma variável usando extern
.
Evite variáveis globais sempre que possível - use funções.
O código fonte e o texto desta resposta estão disponíveis no meu repositório SOQ (Stack Overflow Questions) no GitHub no subdiretório src / so-0143-3204 .
Se você não é um programador C experiente, pode (e talvez deva) parar de ler aqui.
Com alguns (de fato, muitos) compiladores C, você também pode se dar bem com o que é chamado de definição "comum" de uma variável. 'Comum', aqui, refere-se a uma técnica usada no Fortran para compartilhar variáveis entre arquivos de origem, usando um bloco COMMON (possivelmente nomeado). O que acontece aqui é que cada um de vários arquivos fornece uma definição provisória da variável. Desde que não mais de um arquivo forneça uma definição inicializada, os vários arquivos acabam compartilhando uma definição única comum da variável:
#include "prog2.h"
long l; /* Do not do this in portable code */
void inc(void) { l++; }
#include "prog2.h"
long l; /* Do not do this in portable code */
void dec(void) { l--; }
#include "prog2.h"
#include <stdio.h>
long l = 9; /* Do not do this in portable code */
void put(void) { printf("l = %ld\n", l); }
Essa técnica não está em conformidade com a letra do padrão C e com a 'regra de uma definição' - é oficialmente um comportamento indefinido:
Um identificador com ligação externa é usado, mas no programa não existe exatamente uma definição externa para o identificador, ou o identificador não é usado e existem várias definições externas para o identificador (6.9).
Uma definição externa é uma declaração externa que também é uma definição de uma função (que não seja uma definição embutida) ou de um objeto. Se um identificador declarado com ligação externa for usado em uma expressão (que não seja parte do operando de a
sizeof
ou_Alignof
operador cujo resultado é uma constante inteira), em algum lugar de todo o programa deve haver exatamente uma definição externa para o identificador; caso contrário, não haverá mais que um. 161)161) Assim, se um identificador declarado com ligação externa não for usado em uma expressão, não será necessário definir uma definição externa.
No entanto, a norma C também a lista no Anexo J informativo como uma das extensões comuns .
J.5.11 Múltiplas definições externas
Pode haver mais de uma definição externa para o identificador de um objeto, com ou sem o uso explícito da palavra-chave extern; se as definições discordarem ou mais de uma for inicializada, o comportamento será indefinido (6.9.2).
Como essa técnica nem sempre é suportada, é melhor evitar usá-la, principalmente se o seu código precisar ser portátil . Usando essa técnica, você também pode acabar com punições não intencionais.
Se um dos arquivos acima declarado l
como em double
vez de como long
, os vinculadores inseguros do tipo C provavelmente não detectariam a incompatibilidade. Se você estiver em uma máquina com 64 bits long
e double
, nem receberá um aviso; em uma máquina com 32 long
e 64 bits double
, você provavelmente receberá um aviso sobre os diferentes tamanhos - o vinculador usaria o maior tamanho, exatamente como um programa Fortran teria o maior tamanho de qualquer bloco comum.
Observe que o GCC 10.1.0, lançado em 07/05 2020, altera as opções de compilação padrão a serem usadas -fno-common
, o que significa que, por padrão, o código acima não é mais vinculado, a menos que você substitua o padrão por -fcommon
(ou use atributos, etc. - veja o link).
Os próximos dois arquivos completam a fonte para 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
usos prog2.c
, file10.c
, file11.c
, file12.c
, prog2.h
.Conforme observado nos comentários aqui, e conforme declarado na minha resposta a uma pergunta semelhante , o uso de várias definições para uma variável global leva a um comportamento indefinido (J.2; §6.9), que é a maneira do padrão de dizer "qualquer coisa pode acontecer". Uma das coisas que pode acontecer é que o programa se comporte conforme o esperado; e J.5.11 diz, aproximadamente, "você pode ter mais sorte do que merece". Mas um programa que se baseia em várias definições de uma variável externa - com ou sem a palavra-chave explícita 'extern' - não é um programa estritamente conforme e não é garantido que funcione em qualquer lugar. Equivalentemente: contém um bug que pode ou não aparecer.
É claro que existem muitas maneiras pelas quais essas diretrizes podem ser quebradas. Ocasionalmente, pode haver um bom motivo para quebrar as diretrizes, mas essas ocasiões são extremamente incomuns.
c int some_var; /* Do not do this in a header!!! */
Nota 1: se o cabeçalho define a variável sem a extern
palavra - chave, cada arquivo que inclui o cabeçalho cria uma definição provisória da variável. Como observado anteriormente, isso geralmente funciona, mas o padrão C não garante que funcione.
c int some_var = 13; /* Only one source file in a program can use this */
Nota 2: se o cabeçalho define e inicializa a variável, apenas um arquivo de origem em um determinado programa pode usar o cabeçalho. Como os cabeçalhos são principalmente para o compartilhamento de informações, é um pouco tolo criar um que possa ser usado apenas uma vez.
c static int hidden_global = 3; /* Each source file gets its own copy */
Nota 3: se o cabeçalho definir uma variável estática (com ou sem inicialização), cada arquivo de origem terminará com sua própria versão privada da variável 'global'.
Se a variável é realmente uma matriz complexa, por exemplo, isso pode levar à duplicação extrema de código. Ocasionalmente, pode ser uma maneira sensata de obter algum efeito, mas isso é muito incomum.
Use a técnica do cabeçalho que mostrei primeiro. Funciona de forma confiável e em qualquer lugar. Observe, em particular, que o cabeçalho que declara o global_variable
está incluído em todos os arquivos que o utilizam - incluindo o que o define. Isso garante que tudo seja auto-consistente.
Preocupações semelhantes surgem com a declaração e a definição de funções - regras análogas se aplicam. Mas a pergunta era sobre variáveis especificamente, então eu mantive a resposta apenas para variáveis.
Se você não é um programador C experiente, provavelmente deve parar de ler aqui.
Adição Principal Tardia
Uma preocupação que algumas vezes é (e legitimamente) levantada sobre o mecanismo de 'declarações em cabeçalhos, definições na fonte' descrito aqui é que existem dois arquivos a serem mantidos sincronizados - o cabeçalho e a fonte. Isso geralmente é seguido com uma observação de que uma macro pode ser usada para que o cabeçalho cumpra uma tarefa dupla - normalmente declarando as variáveis, mas quando uma macro específica é definida antes da inclusão do cabeçalho, ela define as variáveis.
Outra preocupação pode ser que as variáveis precisam ser definidas em cada um dos vários 'programas principais'. Isso normalmente é uma preocupação espúria; você pode simplesmente introduzir um arquivo de origem C para definir as variáveis e vincular o arquivo de objeto produzido a cada um dos programas.
Um esquema típico funciona assim, usando a variável global original ilustrada em 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++);
}
Os próximos dois arquivos completam a fonte para 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
usos prog3.c
, file1a.c
, file2a.c
, file3a.h
, prog3.h
.O problema com esse esquema, como mostrado, é que ele não fornece a inicialização da variável global. Com C99 ou C11 e listas de argumentos variáveis para macros, você também pode definir uma macro para suportar a inicialização. (Com C89 e sem suporte para listas de argumentos variáveis em macros, não há uma maneira fácil de lidar com inicializadores arbitrariamente longos.)
#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 });
Conteúdo reverso de #if
e #else
blocos, corrigindo bug identificado por
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;
}
Claramente, o código para a estrutura ímpar não é o que você normalmente escreveria, mas ilustra o ponto. O primeiro argumento para a segunda invocação de INITIALIZER
é { 41
e o argumento restante (singular neste exemplo) é 43 }
. Sem C99 ou suporte semelhante para listas de argumentos variáveis para macros, os inicializadores que precisam conter vírgulas são muito problemáticos.
Cabeçalho correto file3b.h
incluído (em vez de fileba.h
) por
Denis Kniazhev
Os próximos dois arquivos completam a fonte para 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
usos prog4.c
, file1b.c
, file2b.c
, prog4.h
, file3b.h
.Qualquer cabeçalho deve ser protegido contra reinclusão, para que as definições de tipo (tipos de enum, struct ou union ou typedefs geralmente) não causem problemas. A técnica padrão é envolver o corpo do cabeçalho em um protetor de cabeçalho, como:
#ifndef FILE3B_H_INCLUDED
#define FILE3B_H_INCLUDED
...contents of header...
#endif /* FILE3B_H_INCLUDED */
O cabeçalho pode ser incluído duas vezes indiretamente. Por exemplo, se file4b.h
inclui file3b.h
para uma definição de tipo que não é mostrada e file1b.c
precisa usar o cabeçalho file4b.h
e file3b.h
, então você tem alguns problemas mais difíceis de resolver. Claramente, você pode revisar a lista de cabeçalhos para incluir apenas file4b.h
. No entanto, você pode não estar ciente das dependências internas - e o código deve, idealmente, continuar funcionando.
Além disso, começa a ficar complicado porque você pode incluir file4b.h
antes de incluir file3b.h
para gerar as definições, mas as proteções normais do cabeçalho file3b.h
impediriam que o cabeçalho fosse reincluído.
Portanto, você precisa incluir o corpo de, file3b.h
no máximo, uma vez para declarações e, no máximo, de definições, mas pode ser necessário em uma única unidade de tradução (TU - uma combinação de um arquivo de origem e os cabeçalhos que ele usa).
No entanto, isso pode ser feito sujeito a uma restrição não muito razoável. Vamos apresentar um novo conjunto de nomes de arquivos:
external.h
para as definições de macro EXTERN, etc.
file1c.h
para definir tipos (principalmente struct oddball
, o tipo de oddball_struct
).
file2c.h
para definir ou declarar as variáveis globais.
file3c.c
que define as variáveis globais.
file4c.c
que simplesmente usa as variáveis globais.
file5c.c
que mostra que você pode declarar e depois definir as variáveis globais.
file6c.c
que mostra que você pode definir e depois (tentar) declarar as variáveis globais.
Nestes exemplos, file5c.c
e file6c.c
incluem diretamente o cabeçalho file2c.h
várias vezes, mas esta é a maneira mais simples de mostrar que as obras do mecanismo. Isso significa que se o cabeçalho fosse indiretamente incluído duas vezes, também seria seguro.
As restrições para que isso funcione são:
O cabeçalho que define ou declara as variáveis globais pode não definir nenhum tipo.
Imediatamente antes de incluir um cabeçalho que deve definir variáveis, você define a macro DEFINE_VARIABLES.
O cabeçalho que define ou declara as variáveis possui conteúdo estilizado.
#ifdef DEFINE_VARIABLES
#define EXTERN /* nothing */
#define INITIALIZE(...) = __VA_ARGS__
#else
#define EXTERN extern
#define INITIALIZE(...) /* nothing */
#endif /* DEFINE_VARIABLES */
#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 */
/* 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 */
#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; }
#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;
}
#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; }
#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; }
O arquivo fonte próxima completa a fonte (fornece um programa primária) para o prog5
, prog6
e prog7
:
#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 a maioria dos problemas. Você só encontra um problema se um cabeçalho que define variáveis (como file2c.h
) for incluído por outro cabeçalho (por exemplo file7c.h
) que define variáveis. Não há uma maneira fácil de contornar isso além de "não faça".
Você pode solucionar parcialmente o problema revisando file2c.h
para 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 */
O problema passa a ser "o cabeçalho deve incluir #undef DEFINE_VARIABLES
?" Se você omitir isso do cabeçalho e agrupar qualquer chamada de definição com #define
e #undef
:
#define DEFINE_VARIABLES
#include "file2c.h"
#undef DEFINE_VARIABLES
no código fonte (para que os cabeçalhos nunca alterem o valor de DEFINE_VARIABLES
), você deve estar limpo. É apenas um incômodo ter que lembrar de escrever a linha extra. Uma alternativa pode ser:
#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 */
Isso está ficando um pouco complicado, mas parece ser seguro (usando o file2d.h
, sem #undef DEFINE_VARIABLES
no 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; }
Os próximos dois arquivos completam a fonte prog8
e 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
usos prog8.c
, file7c.c
, file9c.c
.
prog9
usos prog8.c
, file8c.c
, file9c.c
.
No entanto, é pouco provável que os problemas ocorram na prática, especialmente se você seguir o conselho padrão para
Esta exposição perde alguma coisa?
Confissão : O esquema 'evitando código duplicado' descrito aqui foi desenvolvido porque o problema afeta algum código no qual trabalho (mas não possuo) e é uma preocupação constante com o esquema descrito na primeira parte da resposta. No entanto, o esquema original deixa você com apenas dois lugares para modificar para manter as definições e declarações de variáveis sincronizadas, o que é um grande passo em frente ao ter declarações de variáveis externas espalhadas por toda a base de código (o que realmente importa quando existem milhares de arquivos no total) . No entanto, o código nos arquivos com os nomes fileNc.[ch]
(mais external.h
e externdef.h
) mostra que ele pode ser feito para funcionar. Claramente, não seria difícil criar um script gerador de cabeçalho para fornecer o modelo padronizado para um arquivo de cabeçalho de definição e declaração de variável.
NB Estes são programas de brinquedo com código apenas insuficiente para torná-los marginalmente interessantes. Há repetição nos exemplos que poderiam ser removidos, mas não é para simplificar a explicação pedagógica. (Por exemplo: a diferença entre prog5.c
e prog8.c
é o nome de um dos cabeçalhos incluídos. Seria possível reorganizar o código para que a main()
função não fosse repetida, mas ocultaria mais do que revelou.)
foo.h
): #define FOO_INITIALIZER { 1, 2, 3, 4, 5 }
para definir o inicializador da matriz, enum { FOO_SIZE = sizeof((int [])FOO_INITIALIZER) / sizeof(((int [])FOO_INITIALIZER)[0]) };
obter o tamanho da matriz e extern int foo[];
declarar a matriz . Claramente, a definição deve ser justa int foo[FOO_SIZE] = FOO_INITIALIZER;
, embora o tamanho realmente não precise ser incluído na definição. Isso gera uma constante inteira FOO_SIZE
,.
Uma extern
variável é uma declaração (graças a sbi pela correção) de uma variável que é definida em outra unidade de tradução. Isso significa que o armazenamento da variável está alocado em outro arquivo.
Digamos que você tenha dois .c
arquivos test1.c
e test2.c
. Se você definir uma variável global int test1_var;
em test1.c
e quiser acessar essa variável, test2.c
precisará usá extern int test1_var;
-lo test2.c
.
Amostra 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
extern int test1_var;
para int test1_var;
, o vinculador (gcc 5.4.0) ainda passa. Então, é extern
realmente necessário neste caso?
extern
é uma extensão comum que geralmente funciona - e funciona especificamente com o GCC (mas o GCC está longe de ser o único compilador que o suporta; é predominante nos sistemas Unix). Você pode procurar por "J.5.11" ou a seção "Não é tão bom" na minha resposta (eu sei - é longa) e o texto ao lado que explica isso (ou tenta fazê-lo).
Extern é a palavra-chave usada para declarar que a própria variável reside em outra unidade de tradução.
Assim, você pode optar por usar uma variável em uma unidade de tradução e acessá-la a partir de outra. Em seguida, na segunda, declara-a como externa e o símbolo será resolvido pelo vinculador.
Se você não declarar como externo, receberá 2 variáveis nomeadas da mesma forma, mas não relacionadas, e um erro de várias definições da variável.
Eu gosto de pensar em uma variável externa como uma promessa que você faz ao compilador.
Ao encontrar um externo, o compilador só pode descobrir seu tipo, não onde "vive", portanto, não pode resolver a referência.
Você está dizendo: "Confie em mim. No momento do link, essa referência será resolvida".
extern diz ao compilador para confiar em você que a memória dessa variável está declarada em outro lugar, portanto, ele não tenta alocar / verificar a memória.
Portanto, você pode compilar um arquivo que tenha referência a um externo, mas não poderá vincular se essa memória não for declarada em algum lugar.
Útil para variáveis e bibliotecas globais, mas perigoso porque o vinculador não digita verificação.
Adicionar um extern
transforma uma definição de variável em uma declaração de variável . Veja este tópico sobre qual é a diferença entre uma declaração e uma definição.
declare | define | initialize |
----------------------------------
extern int a; yes no no
-------------
int a = 2019; yes yes yes
-------------
int a; yes yes no
-------------
A declaração não alocará memória (a variável deve ser definida para alocação de memória), mas a definição o fará. Esta é apenas outra visão simples da palavra-chave extern, pois as outras respostas são realmente ótimas.
A interpretação correta de extern é que você diz algo ao compilador. Você diz ao compilador que, apesar de não estar presente no momento, a variável declarada será encontrada de alguma forma pelo vinculador (normalmente em outro objeto (arquivo)). O vinculador será o sortudo a encontrar tudo e montar tudo, independentemente de você ter algumas declarações externas ou não.
Em C, uma variável dentro de um arquivo, por exemplo, example.c recebe escopo local. O compilador espera que a variável tenha sua definição dentro do mesmo arquivo exemplo.c e, quando não encontrar o mesmo, gerará um erro. Uma função, por outro lado, tem como escopo global por padrão. Portanto, você não precisa mencionar explicitamente ao compilador "look dude ... você pode encontrar a definição dessa função aqui". Para uma função incluindo o arquivo que contém sua declaração é suficiente (o arquivo que você chama de arquivo de cabeçalho). Por exemplo, considere os 2 arquivos a seguir:
example.c
#include<stdio.h>
extern int a;
main(){
printf("The value of a is <%d>\n",a);
}
example1.c
int a = 5;
Agora, quando você compila os dois arquivos juntos, usando os seguintes comandos:
passo 1) cc -o ex exemplo.c exemplo1.c passo 2) ./ex
Você obtém a seguinte saída: O valor de a é <5>
Implementação do GCC ELF Linux
Outras respostas abordaram o lado do uso do idioma, agora vamos ver como ele é implementado nesta implementação.
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 e descompilar:
gcc -c main.c
readelf -s main.o
A saída contém:
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
O capítulo "Tabela de símbolos" da especificação ELF de atualização ABI do System V explica:
SHN_UNDEF Este índice da tabela de seções significa que o símbolo está indefinido. Quando o editor de links combina esse arquivo de objeto com outro que define o símbolo indicado, as referências desse arquivo ao símbolo serão vinculadas à definição real.
que é basicamente o comportamento que o padrão C dá às extern
variáveis.
A partir de agora, é tarefa do vinculador fazer o programa final, mas as extern
informações já foram extraídas do código-fonte para o arquivo de objeto.
Testado no GCC 4.8.
Variáveis em linha do C ++ 17
No C ++ 17, convém usar variáveis embutidas em vez de externas, pois são simples de usar (podem ser definidas apenas uma vez no cabeçalho) e mais poderosas (suporte constexpr). Veja: O que significa 'const static' em C e C ++?
readelf
ou nm
possa ser útil, você não explicou os fundamentos de como fazer uso extern
nem concluiu o primeiro programa com a definição real. Seu código nem usa notExtern
. Também existe um problema de nomenclatura: embora notExtern
seja definido aqui e não declarado extern
, é uma variável externa que pode ser acessada por outros arquivos de origem se essas unidades de tradução contiverem uma declaração adequada (o que seria necessário extern int notExtern;
!).
notExtern
era feio, consertou. Sobre a nomenclatura, deixe-me saber se você tem um nome melhor. É claro que esse não seria um bom nome para um programa real, mas acho que se encaixa bem no papel didático aqui.
global_def
da variável definida aqui e extern_ref
da variável definida em algum outro módulo? Eles teriam simetria clara? Você ainda acaba com int extern_ref = 57;
algo assim no arquivo em que está definido, portanto o nome não é ideal, mas dentro do contexto do arquivo de origem único, é uma escolha razoável. Ter extern int global_def;
um cabeçalho não é tanto um problema, parece-me. Totalmente com você, é claro.
extern
permite que um módulo do seu programa acesse uma variável ou função global declarada em outro módulo do seu programa. Você geralmente tem variáveis externas declaradas nos arquivos de cabeçalho.
Se você não deseja que um programa acesse suas variáveis ou funções, use o static
que informa ao compilador que essa variável ou função não pode ser usada fora deste módulo.
Primeiro, a extern
palavra-chave não é usada para definir uma variável; ao contrário, é usado para declarar uma variável. Posso dizer que extern
é uma classe de armazenamento, não um tipo de dados.
extern
é usado para permitir que outros arquivos C ou componentes externos saibam que essa variável já está definida em algum lugar. Exemplo: se você estiver construindo uma biblioteca, não é necessário definir a variável global obrigatoriamente em algum lugar da própria biblioteca. A biblioteca será compilada diretamente, mas ao vincular o arquivo, ela verifica a definição.
extern
é usado para que um first.c
arquivo possa ter acesso total a um parâmetro global em outro second.c
arquivo.
O extern
pode ser declarado no first.c
arquivo ou em qualquer um dos arquivos de cabeçalho first.c
incluídos.
extern
declaração deve estar em um cabeçalho, não em first.c
, portanto, se o tipo for alterado, a declaração também será alterada. Além disso, o cabeçalho que declara a variável deve ser incluído second.c
para garantir que a definição seja consistente com a declaração. A declaração no cabeçalho é a cola que mantém tudo junto; Ele permite que os arquivos sejam compilados separadamente, mas garante que eles tenham uma visão consistente do tipo da variável global.
Com o xc8, você deve ter cuidado ao declarar uma variável do mesmo tipo em cada arquivo, pois você pode, erroneamente, declarar algo como um int
em um arquivo e char
dizer outro. Isso pode levar à corrupção de variáveis.
Esse problema foi resolvido de maneira elegante em um fórum de microchip há 15 anos / * Consulte "http: www.htsoft.com" / / "forum / all / showflat.php / Cat / 0 / Number / 18766 / an / 0 / page / 0 # 18766 "
Mas esse link parece não funcionar mais ...
Então, eu tentarei explicar rapidamente; crie um arquivo chamado global.h.
Declare o seguinte
#ifdef MAIN_C
#define GLOBAL
/* #warning COMPILING MAIN.C */
#else
#define GLOBAL extern
#endif
GLOBAL unsigned char testing_mode; // example var used in several C files
Agora no arquivo main.c
#define MAIN_C 1
#include "global.h"
#undef MAIN_C
Isso significa que em main.c a variável será declarada como um unsigned char
.
Agora, em outros arquivos, incluindo simplesmente global.h, ele será declarado como externo para esse arquivo .
extern unsigned char testing_mode;
Mas será declarado corretamente como um unsigned char
.
O post antigo do fórum provavelmente explicou isso um pouco mais claramente. Mas esse é um potencial real gotcha
ao usar um compilador que permite declarar uma variável em um arquivo e depois a declarar externamente como um tipo diferente em outro. Os problemas associados a isso são: se você declarar declarando testing_mode como um int em outro arquivo, ele pensaria que era uma var de 16 bits e substituiu alguma outra parte do ram, potencialmente corrompendo outra variável. Difícil de depurar!
Uma solução muito curta que utilizo para permitir que um arquivo de cabeçalho contenha a referência externa ou a implementação real de um objeto. O arquivo que realmente contém o objeto apenas contém #define GLOBAL_FOO_IMPLEMENTATION
. Então, quando adiciono um novo objeto a esse arquivo, ele também aparece nesse arquivo sem que eu precise copiar e colar a definição.
Eu uso esse padrão em vários arquivos. Portanto, para manter as coisas o mais independentes possível, apenas reutilizo a única macro GLOBAL em cada cabeçalho. Meu cabeçalho fica assim:
//file foo_globals.h
#pragma once
#include "foo.h" //contains definition of foo
#ifdef GLOBAL
#undef GLOBAL
#endif
#ifdef GLOBAL_FOO_IMPLEMENTATION
#define GLOBAL
#else
#define GLOBAL extern
#endif
GLOBAL Foo foo1;
GLOBAL Foo foo2;
//file main.cpp
#define GLOBAL_FOO_IMPLEMENTATION
#include "foo_globals.h"
//file uses_extern_foo.cpp
#include "foo_globals.h