O que 'const static' significa em C e C ++?

117
const static int foo = 42;

Eu vi isso em algum código aqui no StackOverflow e não consegui descobrir o que faz. Então eu vi algumas respostas confusas em outros fóruns. Meu melhor palpite é que ele é usado em C para ocultar a constante foode outros módulos. Isso está correto? Em caso afirmativo, por que alguém usaria em um contexto C ++ onde você pode simplesmente fazer isso private?

c0m4
fonte

Respostas:

113

Ele é usado em C e C ++.

Como você adivinhou, a staticparte limita seu escopo a essa unidade de compilação . Ele também fornece inicialização estática. constapenas diz ao compilador para não permitir que ninguém o modifique. Essa variável é colocada no segmento de dados ou bss dependendo da arquitetura e pode estar na memória marcada como somente leitura.

Tudo isso é como C trata essas variáveis ​​(ou como C ++ trata variáveis ​​de namespace). Em C ++, um membro marcado staticé compartilhado por todas as instâncias de uma determinada classe. Se é privado ou não, não afeta o fato de que uma variável é compartilhada por várias instâncias. Tendo constlá irá avisá-lo se algum código seria tentar modificar isso.

Se fosse estritamente privado, cada instância da classe obteria sua própria versão (não obstante o otimizador).

Chris Arguin
fonte
1
O exemplo original está falando sobre uma "variável privada". Portanto, este é um mebmer e a estática não afeta a ligação. Você deve remover "a parte estática limita seu escopo para aquele arquivo".
Richard Corden,
A "seção especial" é conhecida como o segmento de dados, que ela compartilha com todas as outras variáveis ​​globais, como "strings" explícitas e arrays globais. Isso é oposto ao segmento de código.
spoulson,
@Richard - o que o faz pensar que é um membro de uma classe? Não há nada na pergunta que diga que sim. Se for membro de uma classe, você está certo, mas se for apenas uma variável declarada no escopo global, então Chris está certo.
Graeme Perrow,
1
O autor da postagem original mencionou a questão privada como uma possível solução melhor, mas não como o problema original.
Chris Arguin,
@ Graeme, OK, então não é "definitivamente" um membro - no entanto, essa resposta é fazer declarações que se aplicam apenas a membros de namespace e essas declarações são erradas para variáveis ​​de membros. Dada a quantidade de votos, esse erro pode confundir alguém não muito familiarizado com o idioma - ele deve ser corrigido.
Richard Corden,
212

Muitas pessoas deram a resposta básica, mas ninguém apontou que em C ++ os constpadrões são staticno namespacenível (e alguns deram informações erradas). Consulte a seção 3.5.3 do padrão C ++ 98.

Primeiro, algumas informações básicas:

Unidade de tradução: um arquivo de origem após o pré-processador (recursivamente) incluir todos os seus arquivos de inclusão.

Ligação estática: um símbolo está disponível apenas em sua unidade de tradução.

Ligação externa: Um símbolo está disponível em outras unidades de tradução.

No namespacenível

Isso inclui o namespace global também conhecido como variáveis ​​globais .

static const int sci = 0; // sci is explicitly static
const int ci = 1;         // ci is implicitly static
extern const int eci = 2; // eci is explicitly extern
extern int ei = 3;        // ei is explicitly extern
int i = 4;                // i is implicitly extern
static int si = 5;        // si is explicitly static

No nível de função

staticsignifica que o valor é mantido entre as chamadas de função.
A semântica das staticvariáveis de função é semelhante às variáveis ​​globais no sentido de que residem no segmento de dados do programa (e não na pilha ou no heap). Consulte esta questão para obter mais detalhes sobre statico tempo de vida das variáveis.

No classnível

staticsignifica que o valor é compartilhado entre todas as instâncias da classe e constsignifica que ele não muda.

Motti
fonte
2
No nível da função: estático não é redundante com const, eles podem se comportar de forma diferente em const int *foo(int x) {const int b=x;return &b};relação aconst int *foo(int x) {static const int b=x;return &b};
Hanczar
1
A pergunta é sobre C e C ++, então você deve incluir uma nota sobre constapenas implicar staticno último.
Nikolai Ruhe
@Motti: Ótima resposta. Você poderia explicar o que torna o que redundante em um nível de função? Você está dizendo que a constdeclaração implica staticisso também? Tipo, se você jogar constfora e modificar o valor, todos os valores serão modificados?
Cookie
1
@Motti constnão implica estático no nível da função, isso seria um pesadelo de simultaneidade (const! = Expressão constante), tudo no nível da função é implicitamente auto. Uma vez que essa questão também está marcada como [c], devo mencionar que um nível global const intestá implicitamente externem C. No entanto, as regras que você tem aqui descrevem perfeitamente C ++.
Ryan Haining
1
E em C ++, em todos os três casos, staticindica que a variável tem duração estática (existe apenas uma cópia, que dura do início do programa até o seu fim), e tem ligação interna / estática se não for especificado de outra forma (isso é substituído pela função linkage para variáveis ​​estáticas locais ou linkage da classe para membros estáticos). As principais diferenças estão no que isso implica em cada situação em que staticé válido.
Justin Time - Reintegrar Monica
45

Essa linha de código pode realmente aparecer em vários contextos diferentes e, embora se comporte aproximadamente da mesma forma, existem pequenas diferenças.

Escopo do namespace

// foo.h
static const int i = 0;

' i' estará visível em todas as unidades de tradução que incluem o cabeçalho. No entanto, a menos que você realmente use o endereço do objeto (por exemplo. ' &i'), Tenho certeza de que o compilador tratará ' i' simplesmente como um tipo seguro 0. Quando mais duas unidades de tradução assumirem o ' &i', o endereço será diferente para cada unidade de tradução.

// foo.cc
static const int i = 0;

' i' tem ligação interna e, portanto, não pode ser referido de fora desta unidade de tradução. No entanto, novamente, a menos que você use seu endereço, ele provavelmente será tratado como um tipo seguro 0.

Algo que vale a pena ressaltar é que a seguinte declaração:

const int i1 = 0;

é exatamente o mesmo que static const int i = 0. Uma variável em um namespace declarada com conste não explicitamente declarada com externé implicitamente estática. Se você pensar sobre isso, era intenção do comitê C ++ permitir que constvariáveis ​​fossem declaradas em arquivos de cabeçalho sem sempre precisar da staticpalavra-chave para evitar quebrar o ODR.

Escopo da classe

class A {
public:
  static const int i = 0;
};

No exemplo acima, o padrão especifica explicitamente que ' i' não precisa ser definido se seu endereço não for obrigatório. Em outras palavras, se você usar apenas ' i' como um 0 de tipo seguro, o compilador não o definirá. Uma diferença entre as versões de classe e espaço de nomes é que o endereço de ' i' (se usado em duas ou mais unidades de tradução) será o mesmo para o membro da classe. Onde o endereço é usado, você deve ter uma definição para ele:

// a.h
class A {
public:
  static const int i = 0;
};

// a.cc
#include "a.h"
const int A::i;            // Definition so that we can take the address
Richard Corden
fonte
2
1 por apontar que const estático é o mesmo que const no escopo do namespace.
Plumenator 01 de
Na verdade, não há diferença entre colocar em "foo.h" ou em "foo.cc", já que .h é simplesmente incluído ao compilar a unidade de tradução.
Mikhail
2
@Mikhail: Você está correto. Presume-se que um cabeçalho pode ser incluído em várias TUs e, por isso, foi útil falar sobre isso separadamente.
Richard Corden
24

É uma pequena otimização de espaço.

Quando voce diz

const int foo = 42;

Você não está definindo uma constante, mas criando uma variável somente leitura. O compilador é inteligente o suficiente para usar 42 sempre que encontrar foo, mas também alocará espaço na área de dados inicializada para ele. Isso é feito porque, conforme definido, foo tem ligação externa. Outra unidade de compilação pode dizer:

extern const int foo;

Para obter acesso ao seu valor. Essa não é uma boa prática, já que essa unidade de compilação não tem ideia do valor de foo. Ele apenas sabe que é um const int e precisa recarregar o valor da memória sempre que for usado.

Agora, declarando que é estático:

static const int foo = 42;

O compilador pode fazer sua otimização usual, mas também pode dizer "ei, ninguém fora desta unidade de compilação pode ver foo e sei que é sempre 42, então não há necessidade de alocar espaço para ele."

Devo também observar que em C ++, a maneira preferida de evitar que os nomes escapem da unidade de compilação atual é usar um namespace anônimo:

namespace {
    const int foo = 42; // same as static definition above
}
Ferruccio
fonte
1
, vc mencionou sem usar static "ele também alocará espaço na área de dados inicializados para ele". e usando estático "não há necessidade de alocar espaço para ele". (de onde o compilador está pegando o val então?) você pode explicar em termos de heap e pilha onde a variável está sendo armazenada. Corrija-me se eu a estou interpretando errado.
Nihar
@ N.Nihar - A área de dados estáticos é um bloco de memória de tamanho fixo que contém todos os dados que possuem ligação estática. É "alocado" pelo processo de carregamento do programa na memória. Não faz parte da pilha ou heap.
Ferruccio
O que acontece se eu tiver uma função que retorne um ponteiro para foo? Isso quebra a otimização?
nw.
@nw: Sim, teria que ser.
Ferruccio de
8

Está faltando um 'int'. Deveria ser:

const static int foo = 42;

Em C e C ++, ele declara uma constante inteira com escopo de arquivo local de valor 42.

Por que 42? Se você ainda não sabe (e é difícil acreditar que não sabe), é uma referência à Resposta à Vida, ao Universo e a tudo .

Kevin
fonte
Obrigada ... agora toda vez ... pelo resto da minha vida ... quando eu vir 42, sempre vou pensar nisso. haha
Inisheer
Isso é uma prova positiva de que o universo foi criado por ter 13 dedos (a pergunta e a resposta realmente correspondem na base 13).
paxdiablo,
São os ratos. 3 dedos em cada pé, mais uma cauda lhe dá a base 13.
KeithB,
Na verdade, você não precisa do 'int' em uma declaração, embora seja definitivamente um bom gosto escrevê-lo. C sempre assume tipos 'int' por padrão. Tente!
efemiente
"com escopo de arquivo local de valor 42" ?? ou é para toda a unidade de compilação?
aniliitb10 de
4

Em C ++,

static const int foo = 42;

é a forma preferida de definir e usar constantes. Ou seja, use isso ao invés de

#define foo 42

porque não subverte o sistema de segurança de tipo.

paxos1977
fonte
4

A todas as ótimas respostas, quero adicionar um pequeno detalhe:

Se você escrever plug-ins (por exemplo, DLLs ou bibliotecas .so para serem carregadas por um sistema CAD), a estática é um salva-vidas que evita colisões de nomes como esta:

  1. O sistema CAD carrega um plugin A, que tem um "const int foo = 42;" iniciar.
  2. O sistema carrega um plugin B, que tem "const int foo = 23;" iniciar.
  3. Como resultado, o plug-in B usará o valor 42 para foo, porque o carregador do plug-in perceberá que já existe um "foo" com ligação externa.

Pior ainda: a etapa 3 pode se comportar de maneira diferente dependendo da otimização do compilador, mecanismo de carregamento do plugin, etc.

Tive esse problema uma vez com duas funções auxiliares (mesmo nome, comportamento diferente) em dois plug-ins. Declará-los estáticos resolveu o problema.

Preto
fonte
Algo parecia estranho sobre as colisões de nomes entre os dois plug-ins, o que me levou a examinar o mapa de links de uma de minhas muitas DLLs que define m_hDfltHeap como um identificador com ligação externa. Com certeza, lá está para todo o mundo ver e usar, listado no mapa de ligação como _m_hDfltHeap. Eu tinha me esquecido completamente desse factóide.
David A. Gray
4

De acordo com a especificação C99 / GNU99:

  • static

    • é especificador de classe de armazenamento

    • objetos de escopo de nível de arquivo por padrão têm ligação externa

    • objetos de escopo de nível de arquivo com especificador estático têm ligação interna
  • const

    • é qualificador de tipo (é uma parte do tipo)

    • palavra-chave aplicada à instância esquerda imediata - ou seja

      • MyObj const * myVar; - ponteiro não qualificado para o tipo de objeto qualificado const

      • MyObj * const myVar; - ponteiro qualificado const para tipo de objeto não qualificado

    • Uso mais à esquerda - aplicado ao tipo de objeto, não variável

      • const MyObj * myVar; - ponteiro não qualificado para o tipo de objeto qualificado const

PORTANTO:

static NSString * const myVar; - ponteiro constante para string imutável com ligação interna.

A ausência da staticpalavra - chave tornará o nome da variável global e pode levar a conflitos de nome no aplicativo.

Alexey Pelekh
fonte
4

inlineVariáveis C ++ 17

Se você pesquisou "C ++ const static" no Google, é muito provável que o que você realmente queira usar sejam as variáveis ​​inline do C ++ 17 .

Este incrível recurso do C ++ 17 nos permite:

  • use convenientemente apenas um único endereço de memória para cada constante
  • armazene-o como um constexpr: Como declarar constexpr extern?
  • faça isso em uma única linha de um cabeçalho

main.cpp

#include <cassert>

#include "notmain.hpp"

int main() {
    // Both files see the same memory address.
    assert(&notmain_i == notmain_func());
    assert(notmain_i == 42);
}

notmain.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

inline constexpr int notmain_i = 42;

const int* notmain_func();

#endif

notmain.cpp

#include "notmain.hpp"

const int* notmain_func() {
    return &notmain_i;
}

Compile e execute:

g++ -c -o notmain.o -std=c++17 -Wall -Wextra -pedantic notmain.cpp
g++ -c -o main.o -std=c++17 -Wall -Wextra -pedantic main.cpp
g++ -o main -std=c++17 -Wall -Wextra -pedantic main.o notmain.o
./main

GitHub upstream .

Consulte também: Como funcionam as variáveis ​​embutidas?

Padrão C ++ em variáveis ​​embutidas

O padrão C ++ garante que os endereços serão os mesmos. Rascunho do padrão C ++ 17 N4659 10.1.6 "O especificador embutido":

6 Uma função ou variável embutida com ligação externa deve ter o mesmo endereço em todas as unidades de tradução.

cppreference https://en.cppreference.com/w/cpp/language/inline explica que se staticnão for fornecido, então tem uma ligação externa.

Implementação de variável inline GCC

Podemos observar como isso é implementado com:

nm main.o notmain.o

que contém:

main.o:
                 U _GLOBAL_OFFSET_TABLE_
                 U _Z12notmain_funcv
0000000000000028 r _ZZ4mainE19__PRETTY_FUNCTION__
                 U __assert_fail
0000000000000000 T main
0000000000000000 u notmain_i

notmain.o:
0000000000000000 T _Z12notmain_funcv
0000000000000000 u notmain_i

e man nmfala sobre u:

"u" O símbolo é um símbolo global único. Esta é uma extensão GNU para o conjunto padrão de associações de símbolos ELF. Para tal símbolo, o vinculador dinâmico garantirá que em todo o processo haja apenas um símbolo com esse nome e tipo em uso.

portanto, vemos que há uma extensão ELF dedicada para isso.

Pré-C ++ 17: extern const

Antes do C ++ 17, e no C, podemos obter um efeito muito semelhante com um extern const, que levará ao uso de um único local de memória.

As desvantagens inlinesão:

  • não é possível fazer a variável constexprcom esta técnica, apenas inlinepermite que: Como declarar constexpr extern?
  • é menos elegante porque você deve declarar e definir a variável separadamente no cabeçalho e no arquivo cpp

main.cpp

#include <cassert>

#include "notmain.hpp"

int main() {
    // Both files see the same memory address.
    assert(&notmain_i == notmain_func());
    assert(notmain_i == 42);
}

notmain.cpp

#include "notmain.hpp"

const int notmain_i = 42;

const int* notmain_func() {
    return &notmain_i;
}

notmain.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

extern const int notmain_i;

const int* notmain_func();

#endif

GitHub upstream .

Alternativas apenas de cabeçalho pré-C ++ 17

Eles não são tão bons quanto a externsolução, mas funcionam e ocupam apenas um único local de memória:

Uma constexprfunção, porque constexprimplicainline e inline permite (força) que a definição apareça em cada unidade de tradução :

constexpr int shared_inline_constexpr() { return 42; }

e aposto que qualquer compilador decente fará a chamada inline.

Você também pode usar uma variável constou constexprestática como em:

#include <iostream>

struct MyClass {
    static constexpr int i = 42;
};

int main() {
    std::cout << MyClass::i << std::endl;
    // undefined reference to `MyClass::i'
    //std::cout << &MyClass::i << std::endl;
}

mas você não pode fazer coisas como pegar seu endereço, ou então ele se torna usado por odr, veja também: Definindo membros de dados estáticos constexpr

C

Em C, a situação é a mesma que em C ++ pré C ++ 17, enviei um exemplo em: O que significa "estático" em C?

A única diferença é que em C ++, constimplica staticpara globais, mas não em C: C ++ semântica de `const estático` vs` const`

Há alguma maneira de embuti-lo totalmente?

TODO: existe alguma maneira de incorporar totalmente a variável, sem usar qualquer memória?

Muito parecido com o que o pré-processador faz.

Isso exigiria de alguma forma:

  • proibir ou detectar se o endereço da variável é levado
  • adicione essa informação aos arquivos de objeto ELF e deixe LTO otimizá-la

Relacionado:

Testado em Ubuntu 18.10, GCC 8.2.0.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fonte
2

Sim, ele esconde uma variável em um módulo de outros módulos. Em C ++, eu o uso quando não quero / preciso alterar um arquivo .h que irá acionar uma reconstrução desnecessária de outros arquivos. Além disso, coloco a estática primeiro:

static const int foo = 42;

Além disso, dependendo de seu uso, o compilador nem mesmo aloca armazenamento para ele e simplesmente "inline" o valor onde é usado. Sem a estática, o compilador não pode assumir que não está sendo usado em outro lugar e não pode embutir.

Jim Buck
fonte
2

Esta é uma constante global visível / acessível apenas no módulo de compilação (arquivo .cpp). BTW, o uso de estático para essa finalidade está obsoleto. É melhor usar um namespace anônimo e um enum:

namespace
{
  enum
  {
     foo = 42
  };
}
Roskoto
fonte
isso forçaria o compilador a não tratar foo como uma constante e, como tal, atrapalha a otimização.
Nils Pipenbrinck,
os valores de enums são sempre constantes, então não vejo como isso atrapalhe as otimizações
Roskoto,
ah - verdade .. meu erro. pensei que você usou uma variável int simples.
Nils Pipenbrinck,
Roskoto, não estou certo de quais benefícios o enumtem neste contexto. Cuidado ao elaborar? Tal enumsgeralmente só são utilizados para evitar o compilador de alocar qualquer espaço para o valor (embora compiladores modernos não precisam desta enumcorte para ele) e para evitar a criação de ponteiros para o valor.
Konrad Rudolph,
Konrad, qual é exatamente o problema que você vê em usar um enum neste caso? Enums são usados ​​quando você precisa de ints constantes, o que é exatamente o caso.
Roskoto,
1

Torná-lo privado ainda significa que ele aparece no cabeçalho. Eu tendo a usar a maneira "mais fraca" que funciona. Veja este artigo clássico de Scott Meyers: http://www.ddj.com/cpp/184401197 (é sobre funções, mas pode ser aplicado aqui também).

ano
fonte