Cosa significa "const static" in C e C ++?


117
const static int foo = 42;

L'ho visto in un codice qui su StackOverflow e non sono riuscito a capire cosa fa. Poi ho visto alcune risposte confuse su altri forum. La mia ipotesi migliore è che sia usato in C per nascondere la costante fooda altri moduli. È corretto? Se è così, perché qualcuno dovrebbe usarlo in un contesto C ++ in cui puoi semplicemente farlo private?

Risposte:


113

Ha usi sia in C che in C ++.

Come hai intuito, la staticparte limita il suo ambito a quell'unità di compilazione . Fornisce inoltre l'inizializzazione statica. constdice solo al compilatore di non permettere a nessuno di modificarlo. Questa variabile viene inserita nel segmento dati o bss a seconda dell'architettura e potrebbe essere contrassegnata in memoria di sola lettura.

Tutto questo è il modo in cui C tratta queste variabili (o come C ++ tratta le variabili dello spazio dei nomi). In C ++, un membro contrassegnato staticè condiviso da tutte le istanze di una determinata classe. Che sia privata o meno non influisce sul fatto che una variabile è condivisa da più istanze. Avere constlì ti avviserà se qualche codice tenterà di modificarlo.

Se fosse strettamente privato, ogni istanza della classe otterrebbe la propria versione (nonostante l'ottimizzatore).


1
L'esempio originale parla di una "variabile privata". Pertanto, questo è un mebmer e l'elettricità statica non ha alcun effetto sul collegamento. Dovresti rimuovere "la parte statica limita il suo ambito a quel file".
Richard Corden

La "sezione speciale" è nota come segmento di dati, che condivide con tutte le altre variabili globali, come "stringhe" esplicite e array globali. Questo è opposto al segmento di codice.
spoulson

@Richard - cosa ti fa pensare che sia un membro di una classe? Non c'è niente nella domanda che dice che lo sia. Se è un membro di una classe, allora hai ragione, ma se è solo una variabile dichiarata nell'ambito globale, allora Chris ha ragione.
Graeme Perrow

1
Il poster originale menzionava il privato come possibile soluzione migliore, ma non come il problema originale.
Chris Arguin

@Graeme, OK, quindi non è "sicuramente" un membro - tuttavia, questa risposta sta facendo dichiarazioni che si applicano solo ai membri dello spazio dei nomi e quelle dichiarazioni sono sbagliate per le variabili dei membri. Data la quantità di voti, quell'errore può confondere qualcuno che non ha molta familiarità con la lingua - dovrebbe essere risolto.
Richard Corden

212

Molte persone hanno dato la risposta di base ma nessuno ha sottolineato che in C ++ il constvalore predefinito è statica namespacelivello (e alcuni hanno fornito informazioni sbagliate). Vedere la sezione 3.5.3 dello standard C ++ 98.

Prima alcuni retroscena:

Unità di traduzione: un file sorgente dopo il preprocessore (ricorsivamente) ha incluso tutti i suoi file di inclusione.

Collegamento statico: un simbolo è disponibile solo all'interno della sua unità di traduzione.

Collegamento esterno: un simbolo è disponibile da altre unità di traduzione.

A namespacelivello

Ciò include lo spazio dei nomi globale noto anche come variabili globali .

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

A livello di funzione

staticsignifica che il valore viene mantenuto tra le chiamate di funzione.
La semantica delle staticvariabili di funzione è simile alle variabili globali in quanto risiedono nel segmento di dati del programma (e non nello stack o nell'heap), vedere questa domanda per maggiori dettagli sulla staticdurata delle variabili.

A classlivello

staticsignifica che il valore è condiviso tra tutte le istanze della classe e constsignifica che non cambia.


2
A livello di funzione: statico non è riduttivo con const, possono comportarsi in modo diverso const int *foo(int x) {const int b=x;return &b};rispetto aconst int *foo(int x) {static const int b=x;return &b};
Hanczar

1
La domanda riguarda sia C che C ++, quindi dovresti includere una nota sull'implicire constsolo staticin quest'ultimo.
Nikolai Ruhe

@ Motti: ottima risposta. Potresti spiegare cosa rende ridondante a livello di funzione? Stai dicendo che la constdichiarazione implica anche staticlì? Come in, se elimini conste modifichi il valore, tutti i valori verranno modificati?
Cookie

1
@Motti constnon implica staticità a livello di funzione, sarebbe un incubo di concorrenza (const! = Espressione costante), tutto a livello di funzione è implicitamente auto. Poiché anche questa domanda è contrassegnata con [c], dovrei menzionare che un livello globale const intè implicitamente externin C. Le regole che hai qui descrivono perfettamente C ++, comunque.
Ryan Haining

1
E in C ++, in tutti e tre i casi, staticindica che la variabile è di durata statica (esiste solo una copia, che dura dall'inizio del programma fino alla sua fine), e ha un collegamento interno / statico se non diversamente specificato (questo è sovrascritto dalla funzione collegamento per variabili statiche locali o collegamento della classe per membri statici). Le differenze principali sono in ciò che questo implica in ogni situazione in cui staticè valido.
Justin Time - Ripristina Monica il

45

Quella riga di codice può effettivamente apparire in diversi contesti e sebbene si comporti approssimativamente allo stesso modo, ci sono piccole differenze.

Ambito dello spazio dei nomi

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

' i' sarà visibile in ogni unità di traduzione che include l'intestazione. Tuttavia, a meno che tu non utilizzi effettivamente l'indirizzo dell'oggetto (ad esempio. " &i"), Sono abbastanza sicuro che il compilatore tratterà " i" semplicemente come un tipo sicuro 0. Dove altre due unità di traduzione prendono il " &i", l'indirizzo sarà diverso per ciascuna unità di traduzione.

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

' i' ha un collegamento interno, quindi non è possibile fare riferimento dall'esterno di questa unità di traduzione. Tuttavia, ancora una volta, a meno che non si utilizzi il suo indirizzo, molto probabilmente verrà considerato come un tipo sicuro 0.

Una cosa degna di nota è che la seguente dichiarazione:

const int i1 = 0;

è esattamente lo stesso di static const int i = 0. Una variabile in uno spazio dei nomi dichiarato con conste non esplicitamente dichiarato con externè implicitamente statica. Se ci pensate, era intenzione del comitato C ++ consentire la constdichiarazione di variabili nei file di intestazione senza che fosse sempre necessaria la staticparola chiave per evitare di rompere l'ODR.

Ambito della classe

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

Nell'esempio sopra, lo standard specifica esplicitamente che " i" non deve essere definito se il suo indirizzo non è richiesto. In altre parole, se usi solo " i" come 0 indipendente dai tipi, il compilatore non lo definirà. Una differenza tra le versioni della classe e dello spazio dei nomi è che l'indirizzo di " i" (se utilizzato in due o più unità di traduzione) sarà lo stesso per il membro della classe. Dove viene utilizzato l'indirizzo, è necessario averne una definizione:

// 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

2
+1 per sottolineare che const statica è uguale a const nell'ambito dello spazio dei nomi.
Plumenator

In realtà non c'è differenza tra l'inserimento in "foo.h" o in "foo.cc" poiché .h è semplicemente incluso quando si compila l'unità di traduzione.
Mikhail

2
@ Mikhail: hai ragione. Si presume che un'intestazione possa essere inclusa in più TU e quindi è stato utile parlarne separatamente.
Richard Corden

24

È una piccola ottimizzazione dello spazio.

Quando dici

const int foo = 42;

Non stai definendo una costante, ma stai creando una variabile di sola lettura. Il compilatore è abbastanza intelligente da usare 42 ogni volta che vede foo, ma allocherà anche spazio nell'area dati inizializzata per esso. Questo viene fatto perché, come definito, foo ha un collegamento esterno. Un'altra unità di compilazione può dire:

extern const int foo;

Per accedere al suo valore. Non è una buona pratica poiché quell'unità di compilazione non ha idea di quale sia il valore di foo. Sa solo che è un const int e deve ricaricare il valore dalla memoria ogni volta che viene utilizzato.

Ora, dichiarando che è statico:

static const int foo = 42;

Il compilatore può eseguire la sua solita ottimizzazione, ma può anche dire "ehi, nessuno al di fuori di questa unità di compilazione può vedere pippo e so che è sempre 42, quindi non è necessario allocare spazio per esso."

Dovrei anche notare che in C ++, il modo preferito per impedire ai nomi di sfuggire all'unità di compilazione corrente è usare uno spazio dei nomi anonimo:

namespace {
    const int foo = 42; // same as static definition above
}

1
, u menzionato senza usare static "allocherà anche spazio nell'area dati inizializzata per esso". e usando statico "non c'è bisogno di allocare alcuno spazio per esso". (da dove il compilatore sta scegliendo il val allora?) Puoi spiegare in termini di heap e stack dove la variabile viene memorizzata. Correggimi se la sto interpretando sbagliato .
Nihar

@ N.Nihar - L'area dei dati statici è un blocco di memoria di dimensioni fisse che contiene tutti i dati con collegamento statico. Viene "allocato" dal processo di caricamento del programma in memoria. Non fa parte dello stack o dell'heap.
Ferruccio

Cosa succede se una funzione restituisce un puntatore a pippo? Ciò interrompe l'ottimizzazione?
nw.

@nw: Sì, dovrebbe.
Ferruccio

8

Manca un "int". Dovrebbe essere:

const static int foo = 42;

In C e C ++, dichiara una costante intera con ambito di file locale di valore 42.

Perché 42? Se non lo sai già (ed è difficile credere di non saperlo), è un riferimento alla Risposta alla Vita, all'Universo e a Tutto .


Grazie ... ora ogni volta ... per il resto della mia vita ... quando vedrò 42 anni, penserò sempre a questo. haha
Inisheer

Questa è una prova positiva che l'universo è stato creato essendo con 13 dita (la domanda e la risposta corrispondono effettivamente in base 13).
paxdiablo

Sono i topi. 3 dita su ogni piede, più una coda ti dà la base 13.
KeithB

In realtà non è necessario "int" in una dichiarazione, anche se è decisamente di buon gusto scriverlo. C assume sempre i tipi "int" per impostazione predefinita. Provalo!
effimero

"con ambito file locale di valore 42" ?? o è per l'intera unità di compilazione?
aniliitb10

4

In C ++,

static const int foo = 42;

è il modo preferito per definire e utilizzare le costanti. Cioè uso questo piuttosto che

#define foo 42

perché non sovverte il sistema di sicurezza dei tipi.


4

A tutte le ottime risposte, voglio aggiungere un piccolo dettaglio:

Se scrivi plugin (ad esempio DLL o librerie .so che devono essere caricate da un sistema CAD), allora static è un salvavita che evita collisioni di nomi come questa:

  1. Il sistema CAD carica un plugin A, che ha un "const int foo = 42;" dentro.
  2. Il sistema carica un plugin B, che ha "const int foo = 23;" dentro.
  3. Di conseguenza, il plugin B userà il valore 42 per foo, perché il caricatore del plugin si renderà conto che c'è già un "foo" con collegamento esterno.

Ancora peggio: il passaggio 3 potrebbe comportarsi in modo diverso a seconda dell'ottimizzazione del compilatore, del meccanismo di caricamento del plugin, ecc.

Ho avuto questo problema una volta con due funzioni di supporto (stesso nome, comportamento diverso) in due plugin. Dichiararli statici ha risolto il problema.


Qualcosa sembrava strano nelle collisioni di nomi tra i due plug-in, che mi ha portato a esaminare la mappa dei collegamenti per una delle mie numerose DLL che definisce m_hDfltHeap come un handle con collegamento esterno. Abbastanza sicuro, è lì che tutto il mondo può vedere e utilizzare, elencato nella mappa dei collegamenti come _m_hDfltHeap. Mi ero completamente dimenticato di questo fattoide.
David A. Gray

4

Secondo la specifica C99 / GNU99:

  • static

    • è l'identificatore della classe di archiviazione

    • gli oggetti con ambito a livello di file per impostazione predefinita hanno un collegamento esterno

    • gli oggetti di ambito a livello di file con specificatore statico hanno un collegamento interno
  • const

    • è il qualificatore del tipo (è una parte del tipo)

    • parola chiave applicata all'istanza immediatamente a sinistra, ad es

      • MyObj const * myVar; - puntatore non qualificato al tipo di oggetto qualificato const

      • MyObj * const myVar; - puntatore qualificato const a un tipo di oggetto non qualificato

    • Utilizzo più a sinistra: applicato al tipo di oggetto, non alla variabile

      • const MyObj * myVar; - puntatore non qualificato al tipo di oggetto qualificato const

COSI:

static NSString * const myVar; - puntatore costante a una stringa immutabile con collegamento interno.

L'assenza della staticparola chiave renderà il nome della variabile globale e potrebbe portare a conflitti di nome all'interno dell'applicazione.


4

inlineVariabili C ++ 17

Se hai cercato su Google "C ++ const static", è molto probabile che ciò che vuoi veramente usare siano le variabili inline C ++ 17 .

Questa fantastica funzionalità C ++ 17 ci consente di:

  • utilizzare convenientemente un solo indirizzo di memoria per ogni costante
  • memorizzalo come constexpr: Come dichiarare constexpr extern?
  • fallo in una singola riga da un'intestazione

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;
}

Compila ed esegui:

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 a monte .

Vedi anche: Come funzionano le variabili in linea?

Standard C ++ sulle variabili inline

Lo standard C ++ garantisce che gli indirizzi saranno gli stessi. C ++ 17 N4659 bozza standard 10.1.6 "The inline specifier":

6 Una funzione o una variabile inline con collegamento esterno deve avere lo stesso indirizzo in tutte le unità di traduzione.

cppreference https://en.cppreference.com/w/cpp/language/inline spiega che se staticnon viene fornito, ha un collegamento esterno.

Implementazione delle variabili inline di GCC

Possiamo osservare come viene implementato con:

nm main.o notmain.o

che contiene:

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 nmdice di u:

"u" Il simbolo è un simbolo globale univoco. Questa è un'estensione GNU al set standard di associazioni di simboli ELF. Per un tale simbolo, il linker dinamico farà in modo che durante l'intero processo sia presente un solo simbolo con questo nome e tipo in uso.

quindi vediamo che esiste un'estensione ELF dedicata per questo.

Pre-C ++ 17: extern const

Prima di C ++ 17 e in C, possiamo ottenere un effetto molto simile con un extern const, che porterà all'uso di un'unica posizione di memoria.

Gli svantaggi inlinesono:

  • non è possibile creare la variabile constexprcon questa tecnica, inlineconsente solo che: Come dichiarare constexpr extern?
  • è meno elegante in quanto devi dichiarare e definire la variabile separatamente nell'header e nel file 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 a monte .

Alternative solo all'intestazione pre-C ++ 17

Questi non sono buoni come la externsoluzione, ma funzionano e occupano solo una singola posizione di memoria:

Una constexprfunzione, perché constexprimplicainline e inline permette (forza) che la definizione compaia su ogni unità di traduzione :

constexpr int shared_inline_constexpr() { return 42; }

e scommetto che qualsiasi compilatore decente integrerà la chiamata.

Puoi anche usare una consto constexprvariabile statica come in:

#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;
}

ma non puoi fare cose come prendere il suo indirizzo, altrimenti diventa odr-usato, vedi anche: Definizione dei membri dati statici constexpr

C

In C la situazione è la stessa di C ++ pre C ++ 17, ho caricato un esempio su: Cosa significa "statico" in C?

L'unica differenza è che in C ++, constimplica staticper le globali, ma non nella semantica C: C ++ di `static const` vs` const`

Qualche modo per integrarlo completamente?

TODO: esiste un modo per incorporare completamente la variabile, senza utilizzare alcuna memoria?

Molto simile a ciò che fa il preprocessore.

Ciò richiederebbe in qualche modo:

  • proibire o rilevare se l'indirizzo della variabile è preso
  • aggiungi queste informazioni ai file oggetto ELF e lascia che LTO le ottimizzi

Relazionato:

Testato in Ubuntu 18.10, GCC 8.2.0.


2

Sì, nasconde una variabile in un modulo da altri moduli. In C ++, lo uso quando non voglio / ho bisogno di modificare un file .h che attiverà una ricostruzione non necessaria di altri file. Inoltre, ho messo prima la statica:

static const int foo = 42;

Inoltre, a seconda del suo utilizzo, il compilatore non allocherà nemmeno lo spazio di archiviazione per esso e semplicemente "inline" il valore dove viene utilizzato. Senza la statica, il compilatore non può presumere che non venga utilizzato altrove e non può inline.


2

Questa è una costante globale visibile / accessibile solo nel modulo di compilazione (file .cpp). BTW che utilizza statico per questo scopo è deprecato. Meglio usare uno spazio dei nomi anonimo e un'enumerazione:

namespace
{
  enum
  {
     foo = 42
  };
}

questo costringerebbe il compilatore a non trattare foo come una costante e come tale ostacola l'ottimizzazione.
Nils Pipenbrinck

i valori delle enumerazioni sono sempre costanti, quindi non vedo come ciò possa ostacolare eventuali ottimizzazioni
Roskoto

ah - vero .. errore mio. pensavo avessi usato una semplice variabile int.
Nils Pipenbrinck

Roskoto, non sono chiaro quale sia il vantaggio enumin questo contesto. Ti va di elaborare? Di enumssolito vengono utilizzati solo per impedire al compilatore di allocare spazio per il valore (sebbene i compilatori moderni non abbiano bisogno di questo enumhack) e per impedire la creazione di puntatori al valore.
Konrad Rudolph

Konrad, quale problema riscontri esattamente nell'usare un enum in questo caso? Gli enum vengono utilizzati quando sono necessari int costanti, il che è esattamente il caso.
Roskoto

1

Renderlo privato significherebbe comunque che appare nell'intestazione. Tendo a usare il modo "più debole" che funziona. Vedi questo articolo classico di Scott Meyers: http://www.ddj.com/cpp/184401197 (riguarda le funzioni, ma può essere applicato anche qui).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.