Conceito da palavra-chave estática da perspectiva do C incorporado


9
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 PORTBser 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.hIsso tem algo a ver com a minha pergunta?


2
nenhum problema com a pergunta, mas seção SE errado Eu estou com medo
Gommer

static normalmente é usado dentro de uma função para especificar que a variável é criada uma vez e mantém seu valor de uma execução para outra. uma variável global é aquela criada fora de qualquer função, para que fique visível em qualquer lugar. global estático realmente não faz sentido.
Finbarr

8
@Finbarr Wrong. staticglobals 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 os privatemembros 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 ser static. Isso também reduz a "acumulação" do espaço para nome global do programa.
21719 JimmyB

2
Re "O operador @ é usado para armazenar o valor PORTB dentro do endereço 0x06" . Mesmo? Não é mais como "O operador @ é usado para armazenar a variável" PORTB "no endereço de memória absoluto 0x06" ?
Peter Mortensen

Respostas:


20

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

char não assinado volátil estático PORTB @ 0x06;

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:

static int a;
static int a = 7;
extern int b = 5;
extern int f() { return 10; }

A seguir, não são definições, mas são apenas declarações:

extern int b;
extern int f();

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 .


3
Ótima resposta, +1! Apenas um ponto menor: eles poderiam ter usado extern, e seria a maneira C mais apropriada de fazê-lo: declarar a variável externem 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 uso staticaqui é uma espécie de atalho que eles usaram para evitar a necessidade de outro arquivo .c além do arquivo de cabeçalho.
JimmyB 9/07/19

Eu também observaria que uma variável estática declarada em uma função não é alterada nas chamadas de função, o que pode ser útil para funções que precisam reter algum tipo de informação de estado (eu a usei especificamente para esse fim no passado).
Peter Smith

@ Peter Acho que disse isso. Mas talvez não tão bem quanto você gostaria?
9139

@ JimmyB Não, eles não poderiam ter usado 'extern', em vez disso, para declarações de variáveis ​​de função que se comportam como 'estático'. 'extern' já é uma opção para declarações de variáveis ​​(não definições) dentro dos corpos das funções e serve a um propósito diferente - fornecer acesso em tempo de link a variáveis ​​definidas fora de qualquer função. Mas é possível que eu também esteja entendendo mal o seu ponto de vista.
jonk

1
@JimmyB Ligação externa seria definitivamente possível, embora eu não saiba se é "mais adequado". Uma consideração é que o compilador poderá emitir um código mais otimizado se as informações forem encontradas na unidade de tradução. Para cenários incorporados, salvar ciclos em todas as instruções de E / S pode ser um grande problema.
Cort Ammon

9

statics 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.


Obrigado pela sua resposta. "Unidade de compilação", desculpe, eu não entendo, você pode explicar esse termo. Deixe-me fazer mais uma pergunta, mesmo que desejemos usar variáveis ​​e funções escritas dentro de outro arquivo, devemos primeiro INCLUIR esse arquivo no nosso ARQUIVO DE ORIGEM principal. Então, por que a palavra-chave "estática volátil" nesse arquivo de cabeçalho.
Electro Voyager


1
Consideravelmente na discussão aprofundada sobre stackoverflow.com/questions/572547/what-does-static-mean-in-c
Peter Smith

3
@ElectroVoyager; se você incluir o mesmo cabeçalho que contém uma declaração estática em vários arquivos de origem c, cada um desses arquivos terá uma variável estática com o mesmo nome, mas eles não são a mesma variável .
Peter Smith

2
No link @JimmyB: 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.
gustavovelascoh

5

Vou tentar resumir os comentários e a resposta de @ JimmyB com um exemplo explicativo:

Suponha que este conjunto de arquivos:

static_test.c:

#include <stdio.h>
#if USE_STATIC == 1
    #include "static.h"
#else
    #include "no_static.h"
#endif

void var_add_one();

void main(){

    say_hello();
    printf("var is %d\n", var);
    var_add_one();
    printf("now var is %d\n", var);
}

static.h:

static int var=64;
static void say_hello(){
    printf("Hello!!!\n");
};

no_static.h:

int var=64;
void say_hello(){
    printf("Hello!!!\n");
};

static_src.c:

#include <stdio.h>

#if USE_STATIC == 1
    #include "static.h"
#else
    #include "no_static.h"
#endif

void var_add_one(){
    var = var + 1;
    printf("Added 1 to var: %d\n", var);
    say_hello();
}

Você pode compilar e executar o código usando gcc -o static_test static_src.c static_test.c -DUSE_STATIC=1; ./static_testo cabeçalho estático ou o cabeçalho gcc -o static_test static_src.c static_test.c -DUSE_STATIC=0; ./static_testnã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ão vare say_helloestará disponível para cada unidade de compilação, ou seja, ambas as unidades podem usá-las, mas verifique se mesmo que a var_add_one()função aumente sua var variável, quando a função principal imprime sua var variável , ainda é 64:

$ gcc -o static_test static_src.c static_test.c -DUSE_STATIC=1; ./static_test                                                                                                                       14:33:12
Hello!!!
var is 64
Added 1 to var: 65
Hello!!!
now var is 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:

$ gcc -o static_test static_src.c static_test.c -DUSE_STATIC=0; ./static_test                                                                                                                       14:35:30
/tmp/ccLBy1s7.o:(.data+0x0): multiple definition of `var'
/tmp/ccV6izKJ.o:(.data+0x0): first defined here
/tmp/ccLBy1s7.o: In function `say_hello':
static_test.c:(.text+0x0): multiple definition of `say_hello'
/tmp/ccV6izKJ.o:static_src.c:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status
zsh: no such file or directory: ./static_test

Espero que isso possa ajudá-lo a esclarecer esse assunto.


4

#include pic.haproximadamente significa "copiar o conteúdo de pic.h no arquivo atual". Como resultado, todo arquivo que inclui pic.hrecebe sua própria definição local de PORTB.

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 usar PORTBem vários arquivos no seu projeto, precisará de pic.huma declaração de PORTBe pic.ccom sua definição . Permitir que cada arquivo C defina sua própria cópia PORTBfacilita 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 nome PORTBpara 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.


"você ficaria surpreso com a facilidade de reutilizar acidentalmente um nome usado por uma das MCUs existentes" - ouso esperar que todas as bibliotecas de matemática usem apenas nomes em minúsculas e que todos os ambientes da MCU usem apenas maiúsculas para registro nomes.
Vsz 09/07/19

O @vsz LAPACK está cheio de nomes históricos de maiúsculas.
Dmitry Grigoryev

3

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 staticpalavra - chave aqui é confuso, porque você nunca usaria staticdessa maneira no código normal. Para uma variável global, você usaria externno cabeçalho, não static. 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 PORTB staticajuda a induzir o compilador a fazer a coisa certa.

Quando usado no escopo do arquivo, staticlimita 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 usar staticem 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, staticsempre 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 declaradas static:

+------------------------+-------------------+--------------------+
| Variable type/location |    Allocation     |       Scope        |
+------------------------+-------------------+--------------------+
| Normal in file         | static            | global             |
| Normal in function     | automatic (stack) | limited (function) |
| Static in file         | static            | limited (file)     |
| Static in function     | static            | limited (function) |
+------------------------+-------------------+--------------------+

O que torna confuso é que o comportamento padrão das variáveis ​​depende de onde elas são definidas.


2

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.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.