static volatile unsigned char PORTB @ 0x06;
Esta é uma linha de código em um arquivo de cabeçalho do microcontrolador PIC. O @
operador é usado para armazenar o valor PORTB dentro do endereço 0x06
, que é um registro dentro do controlador PIC que representa PORTB. Até este ponto, tenho uma ideia clara.
Esta linha é declarada como uma variável global dentro de um arquivo de cabeçalho ( .h
). Portanto, pelo que soube da linguagem C, uma "variável global estática" não é visível para nenhum outro arquivo - ou, simplesmente, variáveis / funções estáticas globais não podem ser usadas fora do arquivo atual.
Então, como essa palavra-chave pode PORTB
ser visível no meu arquivo de origem principal e em muitos outros arquivos de cabeçalho que eu criei manualmente?
No meu arquivo de origem principal, adicionei apenas o arquivo de cabeçalho #include pic.h
Isso tem algo a ver com a minha pergunta?
static
globals são visíveis dentro de toda a unidade de compilação e não são exportados além disso. Eles são muito parecidos com osprivate
membros de uma classe no OOP. Ou seja, todas as variáveis que precisam ser compartilhadas entre diferentes funções dentro de uma unidade de compilação, mas não devem ser visíveis fora do que realmente deveria serstatic
. Isso também reduz a "acumulação" do espaço para nome global do programa.Respostas:
A palavra-chave 'estático' em C tem dois significados fundamentalmente diferentes.
Limite de escopo
Nesse contexto, 'estático' se emparelha com 'externo' para controlar o escopo de um nome de variável ou função. Static faz com que o nome da variável ou função esteja disponível apenas em uma única unidade de compilação e disponível apenas para o código que existe após a declaração / definição no texto da unidade de compilação.
Essa limitação em si realmente significa apenas algo se e somente se você tiver mais de uma unidade de compilação em seu projeto. Se você tiver apenas uma unidade de compilação, ela ainda funciona, mas esses efeitos são inúteis (a menos que você goste de pesquisar nos arquivos de objetos para ler o que o compilador gerou)
Como observado, essa palavra-chave nesse contexto é emparelhada com a palavra-chave 'extern', que faz o oposto - tornando o nome da variável ou função vinculável ao mesmo nome encontrado em outras unidades de compilação. Portanto, você pode considerar 'estático' exigindo que a variável ou o nome seja encontrado na unidade atual de compilação, enquanto 'externo' permite a ligação da unidade de compilação cruzada.
Vida estática
Vida útil estática significa que a variável existe durante toda a duração do programa (por mais tempo que seja.) Quando você usa 'static' para declarar / definir uma variável em uma função, significa que a variável é criada algum tempo antes de seu primeiro uso ( o que significa que, toda vez que a experimento, a variável é criada antes do início do main () e não é destruída posteriormente. Nem mesmo quando a execução da função é concluída e ela retorna ao seu chamador. E, assim como as variáveis de vida estática declaradas fora das funções, elas são inicializadas no mesmo momento - antes do início do main () - para um zero semântico (se nenhuma inicialização for fornecida) ou para um valor explícito especificado, se fornecido.
Isso é diferente das variáveis de função do tipo 'auto', que são criadas novas (ou como se fossem novas) cada vez que a função é inserida e depois são destruídas (ou como se fossem destruídas) quando a função é encerrada.
Ao contrário do impacto de aplicar 'estático' em uma definição de variável fora de uma função, o que afeta diretamente seu escopo, declarar uma variável de função (dentro de um corpo de função, obviamente) como 'estático' não afeta seu escopo. O escopo é determinado pelo fato de ter sido definido dentro de um corpo de função. As variáveis estáticas do tempo de vida definidas nas funções têm o mesmo escopo que outras variáveis 'auto' definidas nos corpos das funções - escopo das funções.
Sumário
Portanto, a palavra-chave 'estática' possui contextos diferentes, com o que significa "significados muito diferentes". O motivo pelo qual foi usado de duas maneiras, assim, foi evitar o uso de outra palavra-chave. (Houve uma longa discussão sobre isso.) Considerou-se que os programadores podiam tolerar o uso e o valor de evitar mais uma palavra-chave na linguagem, sendo mais importante (do que argumentos de outra maneira.)
(Todas as variáveis declaradas fora das funções têm vida útil estática e não precisam da palavra-chave 'static' para torná-la verdadeira. Portanto, esse tipo de liberação da palavra-chave a ser usada ali significa algo completamente diferente: 'visível apenas em uma única compilação unidade. 'É um tipo de hack.)
Nota específica
A palavra 'estático' aqui deve ser interpretada como significando que o vinculador não tentará corresponder várias ocorrências de PORTB que podem ser encontradas em mais de uma unidade de compilação (assumindo que seu código tenha mais de uma).
Ele usa uma sintaxe especial (não portátil) para especificar o "local" (ou o valor numérico do rótulo, que geralmente é um endereço) do PORTB. Portanto, o vinculador recebe o endereço e não precisa encontrar um para ele. Se você tivesse duas unidades de compilação usando essa linha, cada uma delas acabaria apontando para o mesmo local. Portanto, não há necessidade de rotulá-lo de 'externo', aqui.
Se eles usassem 'extern', isso poderia representar um problema. O vinculador seria capaz de ver (e tentaria corresponder) várias referências ao PORTB encontradas em várias compilações. Se todos eles especificarem um endereço como este, e os endereços NÃO forem os mesmos por algum motivo [erro?], O que ele deve fazer? Reclamar? Ou? (Tecnicamente, com 'extern' a regra geral seria que apenas UMA unidade de compilação especificaria o valor e as outras não.)
É mais fácil rotulá-lo como 'estático', evitando que o vinculador se preocupe com conflitos e simplesmente culpe os erros por endereços incompatíveis com quem alterou o endereço para algo que não deveria ser.
De qualquer maneira, a variável é tratada como tendo uma 'vida útil estática'. (E 'volátil'.)
Uma declaração não é uma definição , mas todas as definições são declarações
Em C, uma definição cria um objeto. Também o declara. Porém, uma declaração geralmente não (veja a nota abaixo) cria um objeto.
A seguir estão definições e declarações:
A seguir, não são definições, mas são apenas declarações:
Observe que as declarações não criam um objeto real. Eles apenas declaram os detalhes, que o compilador pode usar para ajudar a gerar o código correto e fornecer mensagens de aviso e erro, conforme apropriado.
Acima, eu digo "normalmente", aconselhando. Em alguns casos, uma declaração pode criar um objeto e, portanto, é promovida a uma definição pelo vinculador (nunca pelo compilador.) Portanto, mesmo nesse caso raro, o compilador C ainda pensa que a declaração é apenas uma declaração. É a fase do vinculador que faz as promoções necessárias de alguma declaração. Tenha isso em mente.
Nos exemplos acima, se houver apenas declarações para um "extern int b;" em todas as unidades de compilação vinculadas, o vinculador é responsável pela criação de uma definição. Esteja ciente de que este é um evento de tempo do link. O compilador é completamente inconsciente durante a compilação. Só pode ser determinado no momento do link, se uma declaração desse tipo for mais promovida.
O compilador está ciente de que "static int a;" não pode ser promovido pelo vinculador no momento do link, portanto, essa é realmente uma definição no momento da compilação .
fonte
extern
, e seria a maneira C mais apropriada de fazê-lo: declarar a variávelextern
em um arquivo de cabeçalho como incluída várias vezes no programa e defini- la em algum arquivo não-cabeçalho a ser compilado e vinculado exatamente uma vez. Afinal de contas,PORTB
é suposto ser exatamente uma instância da variável à qual diferentes do cu pode se referir. Portanto, o usostatic
aqui é uma espécie de atalho que eles usaram para evitar a necessidade de outro arquivo .c além do arquivo de cabeçalho.static
s não são visíveis fora da unidade de compilação atual ou "unidade de tradução". Não é o mesmo que o mesmo arquivo .Observe que você inclui o arquivo de cabeçalho em qualquer arquivo de origem em que possa precisar das variáveis declaradas no cabeçalho. Essa inclusão torna o arquivo de cabeçalho parte da unidade de tradução atual e (uma instância) da variável visível dentro dele.
fonte
Files included by using the #include preprocessor directive become part of the compilation unit.
Quando você incluir o arquivo de cabeçalho (.h) em um arquivo .c, pense nele como inserindo o conteúdo do cabeçalho no arquivo de origem e agora essa é sua unidade de compilação. Se você declarar essa variável estática ou função em um arquivo .c, poderá usá-los apenas no mesmo arquivo, que no final será outra unidade de compilação.Vou tentar resumir os comentários e a resposta de @ JimmyB com um exemplo explicativo:
Suponha que este conjunto de arquivos:
static_test.c:
static.h:
no_static.h:
static_src.c:
Você pode compilar e executar o código usando
gcc -o static_test static_src.c static_test.c -DUSE_STATIC=1; ./static_test
o cabeçalho estático ou o cabeçalhogcc -o static_test static_src.c static_test.c -DUSE_STATIC=0; ./static_test
não estático.Observe que duas unidades de compilação estão presentes aqui: static_src e static_test. Quando você usa a versão estática do cabeçalho (
-DUSE_STATIC=1
), uma versãovar
esay_hello
estará disponível para cada unidade de compilação, ou seja, ambas as unidades podem usá-las, mas verifique se mesmo que avar_add_one()
função aumente suavar
variável, quando a função principal imprime suavar
variável , ainda é 64:Agora, se você tentar compilar e executar o código, usando a versão não estática (
-DUSE_STATIC=0
), ele lançará um erro de vinculação devido à definição de variável duplicada:Espero que isso possa ajudá-lo a esclarecer esse assunto.
fonte
#include pic.h
aproximadamente significa "copiar o conteúdo de pic.h no arquivo atual". Como resultado, todo arquivo que incluipic.h
recebe sua própria definição local dePORTB
.Talvez você esteja se perguntando por que não existe uma definição global única de
PORTB
. O motivo é bastante simples: você só pode definir uma variável global em um arquivo C; portanto, se você quiser usarPORTB
em vários arquivos no seu projeto, precisará depic.h
uma declaração dePORTB
epic.c
com sua definição . Permitir que cada arquivo C defina sua própria cópiaPORTB
facilita a criação de código, pois você não precisa incluir nos arquivos do projeto que não escreveu.Um benefício adicional de variáveis estáticas versus globais é que você obtém menos conflitos de nomes. O arquivo AC que não usa nenhum recurso de hardware do MCU (e, portanto, não inclui
pic.h
) pode usar o nomePORTB
para seu próprio propósito. Não é uma boa idéia fazê-lo de propósito, mas quando você desenvolve, por exemplo, uma biblioteca de matemática independente de MCU, você se surpreenderá com a facilidade de reutilizar acidentalmente um nome usado por uma das MCUs existentes.fonte
Já existem boas respostas, mas acho que a causa da confusão precisa ser tratada de maneira simples e direta:
A declaração PORTB não é padrão C. É uma extensão da linguagem de programação C que funciona apenas com o compilador PIC. A extensão é necessária porque os PICs não foram projetados para oferecer suporte a C.
O uso da
static
palavra - chave aqui é confuso, porque você nunca usariastatic
dessa maneira no código normal. Para uma variável global, você usariaextern
no cabeçalho, nãostatic
. Mas PORTB não é uma variável normal . É um hack que diz ao compilador para usar instruções especiais de montagem para registrar E / S. Declarar PORTBstatic
ajuda a induzir o compilador a fazer a coisa certa.Quando usado no escopo do arquivo,
static
limita o escopo da variável ou função a esse arquivo. "Arquivo" significa o arquivo C e qualquer coisa copiada nele pelo pré-processador. Ao usar #include, você está copiando o código no seu arquivo C. É por isso que usarstatic
em um cabeçalho não faz sentido - em vez de uma variável global, cada arquivo que # inclui o cabeçalho obteria uma cópia separada da variável.Ao contrário da crença popular,
static
sempre significa a mesma coisa: alocação estática com escopo limitado. Aqui está o que acontece com as variáveis antes e depois de serem declaradasstatic
:O que torna confuso é que o comportamento padrão das variáveis depende de onde elas são definidas.
fonte
O motivo pelo qual o arquivo principal pode ver a definição de porta "estática" deve-se à diretiva #include. Essa diretiva é equivalente a inserir todo o arquivo de cabeçalho no seu código-fonte na mesma linha que a própria diretiva.
O compilador de microchip XC8 trata os arquivos .c e .h exatamente da mesma maneira, para que você possa colocar suas definições de variáveis em qualquer um deles.
Normalmente, um arquivo de cabeçalho contém referência "externa" a variáveis definidas em outro local (geralmente um arquivo .c).
As variáveis de porta precisam ser especificadas em endereços de memória específicos que correspondem ao hardware real. Portanto, uma definição real (não externa) precisava existir em algum lugar.
Só posso adivinhar por que a Microchip Corporation escolheu colocar as definições reais no arquivo .h. Um palpite provável é que eles queriam apenas um arquivo (.h) em vez de 2 (.he ec) (para facilitar as coisas para o usuário).
Mas se você colocar as definições de variáveis reais em um arquivo de cabeçalho e incluir esse cabeçalho em vários arquivos de origem, o vinculador reclamará que as variáveis são definidas várias vezes.
A solução é declarar as variáveis como estáticas, então cada definição é tratada como local para esse arquivo de objeto e o vinculador não reclama.
fonte