Declarações de variáveis ​​em arquivos de cabeçalho - estáticas ou não?

91

Ao refatorar alguns #defines, encontrei declarações semelhantes às seguintes em um arquivo de cabeçalho C ++:

static const unsigned int VAL = 42;
const unsigned int ANOTHER_VAL = 37;

A questão é: que diferença, se houver, a estática fará? Observe que a inclusão múltipla dos cabeçalhos não é possível devido ao #ifndef HEADER #define HEADER #endiftruque clássico (se isso for importante).

O estático significa que apenas uma cópia do VALé criada, caso o cabeçalho seja incluído por mais de um arquivo de origem?

Roubar
fonte
relacionado: stackoverflow.com/questions/177437/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Respostas:

107

Os staticmeios de que haverá uma cópia do VALcriado para cada arquivo-fonte está incluído. Mas isso também significa que várias inclusões não vai resultar em múltiplas definições de VALque irá colidir em tempo de ligação. Em C, sem o, staticvocê precisaria garantir que apenas um arquivo de origem foi definido VALenquanto os outros arquivos de origem o declararam extern. Normalmente, isso seria feito definindo-o (possivelmente com um inicializador) em um arquivo de origem e colocando oextern declaração em um arquivo de cabeçalho.

static variáveis ​​em nível global só são visíveis em seu próprio arquivo de origem, quer tenham chegado lá por meio de uma inclusão ou estivessem no arquivo principal.


Nota do editor: Em C ++, os constobjetos sem as palavras-chave staticnem externem suas declarações são implicitamente static.

Justsalt
fonte
Sou fã da última frase, incrivelmente útil. Não votei a resposta porque 42 é melhor. editar: gramática
RealDeal_EE'18 01 de
"O estático significa que haverá uma cópia do VAL criada para cada arquivo de origem no qual está incluído." Isso parece implicar que haveria duas cópias de VAL se dois arquivos de origem incluíssem o arquivo de cabeçalho. Espero que isso não seja verdade e que sempre haja uma única instância de VAL, independentemente de quantos arquivos incluam o cabeçalho.
Brent212
4
@ Brent212 O compilador não sabe se uma declaração / definição veio de um arquivo de cabeçalho ou arquivo principal. Então você espera em vão. Haverá duas cópias do VAL se alguém for bobo e colocar uma definição estática em um arquivo de cabeçalho e ela for incluída em duas fontes.
Justsalt
1
valores const têm ligação interna em C ++
adrianN
112

As tags statice externnas variáveis ​​com escopo de arquivo determinam se elas são acessíveis em outras unidades de tradução (isto é, outros .cou .cpparquivos).

  • staticdá a ligação interna variável, ocultando-a de outras unidades de tradução. No entanto, as variáveis ​​com ligação interna podem ser definidas em várias unidades de tradução.

  • externdá a ligação externa variável, tornando-a visível para outras unidades de tradução. Normalmente, isso significa que a variável só deve ser definida em uma unidade de tradução.

O padrão (quando você não especifica staticou extern) é uma daquelas áreas em que C e C ++ diferem.

  • Em C, as variáveis ​​com escopo de arquivo são extern(ligação externa) por padrão. Se você estiver usando C, VALé statice ANOTHER_VALé extern.

  • Em C ++, as variáveis ​​com escopo de arquivo são static(ligação interna) por padrão se forem const, e externpor padrão se não forem. Se você estiver usando C ++, ambos VALe ANOTHER_VALestão static.

De um esboço da especificação C :

6.2.2 Ligações de identificadores ... -5- Se a declaração de um identificador para uma função não tem especificador de classe de armazenamento, sua ligação é determinada exatamente como se fosse declarada com o especificador de classe de armazenamento extern. Se a declaração de um identificador para um objeto tem escopo de arquivo e nenhum especificador de classe de armazenamento, seu vínculo é externo.

De um rascunho da especificação C ++ :

7.1.1 - Especificadores de classe de armazenamento [dcl.stc] ... -6- Um nome declarado em um escopo de namespace sem um especificador de classe de armazenamento tem ligação externa, a menos que tenha ligação interna devido a uma declaração anterior e desde que não seja declarado const. Os objetos declarados const e não explicitamente declarados extern têm ligação interna.

bk1e
fonte
47

A estática significa que você terá uma cópia por arquivo, mas ao contrário de outros disseram, é perfeitamente legal fazer isso. Você pode testar isso facilmente com um pequeno exemplo de código:

test.h:

static int TEST = 0;
void test();

test1.cpp:

#include <iostream>
#include "test.h"

int main(void) {
    std::cout << &TEST << std::endl;
    test();
}

test2.cpp:

#include <iostream>
#include "test.h"

void test() {
    std::cout << &TEST << std::endl;
}

Executá-lo fornece esta saída:

0x446020
0x446040

limão cortado
fonte
5
Obrigado pelo exemplo!
Kyrol
Eu me pergunto se TESTfosse const, se o LTO seria capaz de otimizá-lo em um único local de memória. Mas -O3 -fltodo GCC 8.1 não.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
Seria ilegal fazer isso - mesmo que seja constante, a estática garante que cada instância seja local para a unidade de compilação. Ele provavelmente poderia embutir o próprio valor da constante se usado como uma constante, mas como pegamos seu endereço, ele deve retornar um ponteiro exclusivo.
cal cortada em
6

constvariáveis ​​em C ++ têm ligação interna. Portanto, usar staticnão tem efeito.

ah

const int i = 10;

one.cpp

#include "a.h"

func()
{
   cout << i;
}

two.cpp

#include "a.h"

func1()
{
   cout << i;
}

Se este fosse um programa C, você obteria o erro de 'definição múltipla' para i(devido à ligação externa).

Nitin
fonte
2
Bem, usar statictem o efeito de sinalizar nitidamente a intenção e a consciência do que se está codificando, o que nunca é uma coisa ruim. Para mim, isso é como incluir virtualao substituir: não precisamos, mas as coisas parecem muito mais intuitivas - e consistentes com outras declarações - quando fazemos.
underscore_d
Você pode obter erro de definição múltipla em C. É um comportamento indefinido, sem necessidade de diagnóstico
MM
5

A declaração estática neste nível de código significa que a variável só é visível na unidade de compilação atual. Isso significa que apenas o código dentro desse módulo verá essa variável.

se você tiver um arquivo de cabeçalho que declara uma variável estática e esse cabeçalho está incluído em vários arquivos C / CPP, essa variável será "local" para esses módulos. Haverá N cópias dessa variável para os N lugares em que o cabeçalho está incluído. Eles não estão relacionados entre si de forma alguma. Qualquer código em qualquer um desses arquivos de origem fará referência apenas à variável declarada nesse módulo.

Nesse caso específico, a palavra-chave 'estática' não parece oferecer nenhum benefício. Posso estar faltando alguma coisa, mas parece que não importa - eu nunca vi nada assim antes.

Quanto ao inline, neste caso a variável provavelmente está inline, mas isso é apenas porque é declarada const. O compilador pode estar mais propenso a incorporar variáveis ​​estáticas do módulo, mas isso depende da situação e do código que está sendo compilado. Não há garantia de que o compilador irá inline 'estática'.

Marca
fonte
O benefício de 'estático' aqui é que, caso contrário, você está declarando vários globais com o mesmo nome, um para cada módulo que inclui o cabeçalho. Se o vinculador não reclamar, é só porque está mordendo a língua e sendo educado.
Nesse caso, devido ao const, o staticé implícito e, portanto, opcional. O corolário é que não há suscetibilidade a erros de definição múltipla, como afirma Mike F.
underscore_d
2

Para responder à pergunta, "o estático significa que apenas uma cópia do VAL é criada, caso o cabeçalho seja incluído por mais de um arquivo de origem?" ...

NO . VAL sempre será definido separadamente em cada arquivo que inclui o cabeçalho.

Os padrões para C e C ++ causam uma diferença neste caso.

Em C, as variáveis ​​com escopo de arquivo são externas por padrão. Se você estiver usando C, VAL é estático e ANOTHER_VAL é externo.

Observe que os vinculadores modernos podem reclamar de ANOTHER_VAL se o cabeçalho for incluído em arquivos diferentes (mesmo nome global definido duas vezes), e definitivamente reclamariam se ANOTHER_VAL fosse inicializado com um valor diferente em outro arquivo

Em C ++, as variáveis ​​com escopo de arquivo são estáticas por padrão se forem constantes e externas por padrão se não forem. Se você estiver usando C ++, VAL e ANOTHER_VAL são estáticos.

Você também precisa levar em consideração o fato de que ambas as variáveis ​​são designadas como const. Idealmente, o compilador sempre escolheria incorporar essas variáveis ​​e não incluir nenhum armazenamento para elas. Há uma série de razões pelas quais o armazenamento pode ser alocado. Eu posso pensar em ...

  • opções de depuração
  • endereço obtido no arquivo
  • o compilador sempre aloca armazenamento (tipos const complexos não podem ser facilmente embutidos, então se torna um caso especial para tipos básicos)
itj
fonte
Nota: Na máquina abstrata, há uma cópia do VAL em cada unidade de tradução separada que inclui o cabeçalho. Na prática, o vinculador pode decidir combiná-los de qualquer maneira, e o compilador pode otimizar alguns ou todos eles primeiro.
MM de
1

Supondo que essas declarações estejam no escopo global (ou seja, não sejam variáveis ​​de membro), então:

estático significa 'ligação interna'. Neste caso, uma vez que é declarado const, pode ser otimizado / embutido pelo compilador. Se você omitir const , o compilador deve alocar armazenamento em cada unidade de compilação.

Ao omitir estático, a ligação é externa por padrão. Novamente, você foi salvo pela const ness - o compilador pode otimizar / usar em linha. Se você eliminar o const , obterá um erro de símbolos múltiplos definidos no momento do link.

Seb Rose
fonte
Eu acredito que o compilador deve alocar espaço para um const int em todos os casos, uma vez que outro módulo sempre poderia dizer "extern const int qualquer; algo (& qualquer);"
1

Você não pode declarar uma variável estática sem defini-la também (isso ocorre porque os modificadores da classe de armazenamento static e extern são mutuamente exclusivos). Uma variável estática pode ser definida em um arquivo de cabeçalho, mas isso faria com que cada arquivo de origem que incluiu o arquivo de cabeçalho tivesse sua própria cópia privada da variável, o que provavelmente não é o que se pretendia.

Gajendra Kumar
fonte
"... mas isso faria com que cada arquivo de origem que incluísse o arquivo de cabeçalho tivesse sua própria cópia privada da variável, o que provavelmente não é o que se pretendia." - Devido ao fiasco do pedido de inicialização estática , pode ser necessário ter uma cópia em cada unidade de tradução.
jww
1

Variáveis const são por padrão estáticas em C ++, mas externas C. Portanto, se você usar C ++, isso não fará sentido que construção usar.

(7.11.6 C ++ 2003 e Apexndix C tem amostras)

Exemplo em comparar fontes de compilação / link como programa C e C ++:

bruziuz:~/test$ cat a.c
const int b = 22;
int main(){return 0;}
bruziuz:~/test$ cat b.c
const int b=2;
bruziuz:~/test$ gcc -x c -std=c89 a.c b.c
/tmp/ccSKKIRZ.o:(.rodata+0x0): multiple definition of `b'
/tmp/ccDSd0V3.o:(.rodata+0x0): first defined here
collect2: error: ld returned 1 exit status
bruziuz:~/test$ gcc -x c++ -std=c++03 a.c b.c 
bruziuz:~/test$ 
bruziuz:~/test$ gcc --version | head -n1
gcc (Ubuntu 5.4.0-6ubuntu1~16.04.5) 5.4.0 20160609
Bruziuz
fonte
Ainda faz sentido incluir o static. Ele sinaliza a intenção / consciência do que o programador está fazendo e mantém a paridade com outros tipos de declaração (e, fwiw, C) que carecem do implícito static. É como incluir virtuale ultimamente overrideem declarações de funções de override - não é necessário, mas muito mais autodocumentado e, no caso deste último, propício à análise estática.
underscore_d
Estou absolutamente de acordo. Por exemplo, quanto a mim na vida real, sempre escrevo explicitamente.
bruziuz
"Então, se você usa C ++ não faz sentido que construção usar ..." - Hmm ... Acabei de compilar um projeto que usei constapenas em uma variável em um cabeçalho com g++ (GCC) 7.2.1 20170915 (Red Hat 7.2.1-2). Resultou em cerca de 150 símbolos múltiplos definidos (um para cada unidade de tradução em que o cabeçalho foi incluído). Acho que precisamos tanto static, inlineou um anônimo / namespace sem nome para evitar a ligação externa.
jww
Tentei baby-example com gcc-5.4 com declare const intdentro do escopo do namespace e no namespace global. E é compilado e segue a regra "Objetos declarados const e não explicitamente declarados extern têm ligação interna." ".... Talvez no projeto por algum motivo este cabeçalho incluído em fontes compiladas C, onde as regras são completamente diferentes.
bruziuz
@jww Eu carreguei exemplo com problema de ligação para C e sem problemas para C ++
bruziuz
0

Static evita que outra unidade de compilação externe essa variável, de forma que o compilador possa apenas "embutir" o valor da variável onde ela é usada e não criar armazenamento de memória para ela.

Em seu segundo exemplo, o compilador não pode presumir que algum outro arquivo de origem não o externará, portanto, ele deve realmente armazenar esse valor em algum lugar da memória.

Jim Buck
fonte
-2

Estático impede que o compilador adicione várias instâncias. Isso se torna menos importante com a proteção #ifndef, mas assumindo que o cabeçalho esteja incluído em duas bibliotecas separadas e o aplicativo esteja vinculado, duas instâncias seriam incluídas.

Superpolock
fonte
assumindo que por "bibliotecas" você quer dizer unidades de tradução , então não, os guardas de inclusão não fazem absolutamente nada para evitar múltiplas definições, visto que eles apenas protegem contra inclusões repetidas dentro da mesma unidade de tradução . portanto, eles não fazem absolutamente nada para tornar static"menos importante". e mesmo com ambos, você pode acabar com várias definições internamente vinculadas, o que provavelmente não era o pretendido.
underscore_d