Que signifie «const static» en C et C ++?

117
const static int foo = 42;

J'ai vu cela dans un code ici sur StackOverflow et je ne pouvais pas comprendre ce que cela faisait. Ensuite, j'ai vu des réponses confuses sur d'autres forums. Ma meilleure hypothèse est qu'il est utilisé en C pour masquer la constante foodes autres modules. Est-ce correct? Si tel est le cas, pourquoi quelqu'un l'utiliserait-il dans un contexte C ++ où vous pouvez simplement le faire private?

c0m4
la source

Réponses:

113

Il a des utilisations en C et C ++.

Comme vous l'avez deviné, la staticpartie limite sa portée à cette unité de compilation . Il prévoit également une initialisation statique. constdit simplement au compilateur de ne laisser personne le modifier. Cette variable est placée dans le segment data ou bss selon l'architecture, et peut être en mémoire marquée en lecture seule.

Tout cela est comment C traite ces variables (ou comment C ++ traite les variables d'espace de noms). En C ++, un membre marqué staticest partagé par toutes les instances d'une classe donnée. Que ce soit privé ou non n'affecte pas le fait qu'une variable est partagée par plusieurs instances. Avoir constdessus vous avertira si du code essaierait de modifier cela.

Si elle était strictement privée, alors chaque instance de la classe aurait sa propre version (malgré l'optimiseur).

Chris Arguin
la source
1
L'exemple original parle d'une "variable privée". Par conséquent, il s'agit d'un mebmer et statique n'a aucun effet sur la liaison. Vous devez supprimer "la partie statique limite sa portée à ce fichier".
Richard Corden
La "section spéciale" est connue sous le nom de segment de données, qu'elle partage avec toutes les autres variables globales, telles que les "chaînes" explicites et les tableaux globaux. Ceci est opposé au segment de code.
spoulson
@Richard - qu'est-ce qui vous fait penser que c'est un membre d'une classe? Il n'y a rien dans la question qui dit que c'est le cas. Si c'est un membre d'une classe, alors vous avez raison, mais si c'est juste une variable déclarée à portée globale, alors Chris a raison.
Graeme Perrow
1
L'affiche originale mentionnait le privé comme une meilleure solution possible, mais pas comme le problème initial.
Chris Arguin
@Graeme, OK donc ce n'est pas "définitivement" un membre - cependant, cette réponse fait des déclarations qui ne s'appliquent qu'aux membres de l'espace de noms et ces déclarations sont fausses pour les variables membres. Compte tenu du nombre de votes, cette erreur peut dérouter quelqu'un qui ne connaît pas très bien la langue - elle devrait être corrigée.
Richard Corden
212

Beaucoup de gens ont donné la réponse de base mais personne n'a fait remarquer qu'en C ++, la valeur par constdéfaut était staticau namespaceniveau (et certains ont donné des informations erronées). Consultez la section 3.5.3 de la norme C ++ 98.

Tout d'abord un peu de contexte:

Unité de traduction: un fichier source après que le pré-processeur a inclus (récursivement) tous ses fichiers d'inclusion.

Liaison statique: un symbole n'est disponible que dans son unité de traduction.

Lien externe: un symbole est disponible auprès d'autres unités de traduction.

Au namespaceniveau

Cela inclut l'espace de noms global aka les variables globales .

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

Au niveau de la fonction

staticsignifie que la valeur est conservée entre les appels de fonction.
La sémantique des staticvariables de fonction est similaire aux variables globales en ce qu'elles résident dans le segment de données du programme (et non dans la pile ou le tas), voir cette question pour plus de détails sur staticla durée de vie des variables.

Au classniveau

staticsignifie que la valeur est partagée entre toutes les instances de la classe et constsignifie qu'elle ne change pas.

Motti
la source
2
Au niveau de la fonction: statique n'est pas redondant avec const, ils peuvent se comporter différemment const int *foo(int x) {const int b=x;return &b};par rapport àconst int *foo(int x) {static const int b=x;return &b};
Hanczar
1
La question concerne à la fois le C et le C ++, vous devriez donc inclure une note sur la constseule implication staticdans ce dernier.
Nikolai Ruhe
@Motti: Excellente réponse. Pouvez-vous expliquer ce qui rend ce qui est redondant au niveau de la fonction? Dites-vous que la constdéclaration implique staticlà aussi? Comme dans, si vous rejetez constet modifiez la valeur, toutes les valeurs seront modifiées?
Cookie du
1
@Motti constn'implique pas de statique au niveau de la fonction, ce serait un cauchemar de concurrence (const! = Expression constante), tout au niveau de la fonction l'est implicitement auto. Puisque cette question est également étiquetée [c], je dois mentionner qu'un niveau global const intest implicitement externen C. Les règles que vous avez ici décrivent parfaitement le C ++, cependant.
Ryan Haining
1
Et en C ++, dans les trois cas, staticindique que la variable est de durée statique (une seule copie existe, qui dure du début du programme jusqu'à sa fin), et qu'elle a un lien interne / statique si ce n'est pas spécifié autrement (ceci est remplacé par la fonction lien pour les variables statiques locales, ou le lien de classe pour les membres statiques). Les principales différences sont dans ce que cela implique dans chaque situation où staticest valable.
Justin Time - Réintègre Monica le
45

Cette ligne de code peut en fait apparaître dans plusieurs contextes différents et bien qu'elle se comporte à peu près de la même manière, il existe de petites différences.

Étendue de l'espace de noms

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

' i' sera visible dans chaque unité de traduction qui comprend l'en-tête. Cependant, à moins que vous n'utilisiez réellement l'adresse de l'objet (par exemple. ' &i'), Je suis à peu près sûr que le compilateur traitera ' i' simplement comme un type sûr 0. Là où deux autres unités de traduction prennent le « &i», l'adresse sera différente pour chaque unité de traduction.

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

' i' a un lien interne et ne peut donc pas être référencé de l'extérieur de cette unité de traduction. Cependant, à moins que vous n'utilisiez son adresse, elle sera très probablement traitée comme un type sécurisé 0.

Il convient de souligner que la déclaration suivante:

const int i1 = 0;

est exactement le même que static const int i = 0. Une variable dans un espace de noms déclaré avec constet non explicitement déclarée avec externest implicitement statique. Si vous pensez à cela, il était l'intention du comité C ++ de permettre aux constvariables d'être déclarées dans les fichiers d'en-tête sans toujours avoir besoin du staticmot - clé pour éviter de casser l'ODR.

Portée de la classe

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

Dans l'exemple ci-dessus, la norme spécifie explicitement que « i» n'a pas besoin d'être défini si son adresse n'est pas requise. En d'autres termes, si vous n'utilisez « i» que comme un 0 de type sécurisé, le compilateur ne le définira pas. Une différence entre les versions de classe et d'espace de noms est que l'adresse de « i» (si elle est utilisée dans deux unités de traduction ou plus) sera la même pour le membre de la classe. Lorsque l'adresse est utilisée, vous devez en avoir une définition:

// 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
la source
2
+1 pour souligner que statique const est identique à simplement const dans la portée de l'espace de noms.
Plumenator
Il n'y a en fait aucune différence entre placer dans "foo.h" ou dans "foo.cc" puisque .h est simplement inclus lors de la compilation de l'unité de traduction.
Mikhail
2
@Mikhail: Vous avez raison. Il y a une hypothèse selon laquelle un en-tête peut être inclus dans plusieurs UT et il était donc utile d'en parler séparément.
Richard Corden
24

C'est une optimisation de petit espace.

Quand tu dis

const int foo = 42;

Vous ne définissez pas une constante, mais créez une variable en lecture seule. Le compilateur est assez intelligent pour utiliser 42 chaque fois qu'il voit foo, mais il allouera également de l'espace dans la zone de données initialisée pour cela. Ceci est fait parce que, comme défini, foo a un lien externe. Une autre unité de compilation peut dire:

extern const int foo;

Pour accéder à sa valeur. Ce n'est pas une bonne pratique car cette unité de compilation n'a aucune idée de la valeur de foo. Il sait juste que c'est un const int et doit recharger la valeur de la mémoire chaque fois qu'il est utilisé.

Maintenant, en déclarant qu'il est statique:

static const int foo = 42;

Le compilateur peut faire son optimisation habituelle, mais il peut aussi dire "hé, personne en dehors de cette unité de compilation ne peut voir foo et je sais qu'il est toujours 42 donc il n'est pas nécessaire de lui allouer de l'espace".

Je dois également noter qu'en C ++, la meilleure façon d'empêcher les noms de s'échapper de l'unité de compilation actuelle est d'utiliser un espace de noms anonyme:

namespace {
    const int foo = 42; // same as static definition above
}
Ferruccio
la source
1
, u mentionné sans utiliser statique "il allouera également de l'espace dans la zone de données initialisée pour cela". et en utilisant statique "pas besoin d'allouer d'espace pour cela". (d'où le compilateur choisit alors le val?) pouvez-vous expliquer en terme de tas et de pile où la variable est stockée. Corrigez-moi si je l'interprète faux .
Nihar
@ N.Nihar - La zone de données statiques est un bloc de mémoire de taille fixe qui contient toutes les données qui ont une liaison statique. Il est «alloué» par le processus de chargement du programme en mémoire. Cela ne fait pas partie de la pile ou du tas.
Ferruccio
Que se passe-t-il si une fonction renvoie un pointeur vers foo? Cela brise-t-il l'optimisation?
nw.
@nw: Oui, il le faudrait.
Ferruccio
8

Il manque un «int». Ça devrait être:

const static int foo = 42;

En C et C ++, il déclare une constante entière avec une portée de fichier local de valeur 42.

Pourquoi 42? Si vous ne savez pas déjà (et il est difficile de croire que vous ne le faites pas), c'est une référence à la réponse à la vie, à l'univers et à tout .

Kevin
la source
Merci ... maintenant à chaque fois ... pour le reste de ma vie ... quand j'en verrai 42, je penserai toujours à ça. haha
Inisheer
C'est la preuve positive que l'univers a été créé en étant avec 13 doigts (la question et la réponse correspondent en fait à la base 13).
paxdiablo
Ce sont les souris. 3 orteils sur chaque pied, plus une queue, vous donne la base 13.
KeithB
Vous n'avez pas vraiment besoin du «int» dans une déclaration, même si c'est vraiment bon de l'écrire. C suppose toujours les types 'int' par défaut. Essayez-le!
éphémère
"avec une portée de fichier local de valeur 42" ?? ou est-ce pour l'ensemble de l'unité de compilation?
aniliitb10
4

En C ++,

static const int foo = 42;

est la meilleure façon de définir et d'utiliser des constantes. Ie utiliser ceci plutôt que

#define foo 42

parce qu'il ne subvertit pas le système de sécurité de type.

paxos1977
la source
4

À toutes les bonnes réponses, je veux ajouter un petit détail:

Si vous écrivez des plugins (par exemple des DLL ou des bibliothèques .so à charger par un système de CAO), alors statique est un sauveur de vie qui évite les collisions de noms comme celle-ci:

  1. Le système CAO charge un plugin A, qui a un "const int foo = 42;" dedans.
  2. Le système charge un plugin B, qui a "const int foo = 23;" dedans.
  3. En conséquence, le plugin B utilisera la valeur 42 pour foo, car le chargeur de plugin se rendra compte qu'il existe déjà un "foo" avec un lien externe.

Pire encore: l'étape 3 peut se comporter différemment selon l'optimisation du compilateur, le mécanisme de chargement du plugin, etc.

J'ai eu ce problème une fois avec deux fonctions d'assistance (même nom, comportement différent) dans deux plugins. Les déclarer statiques a résolu le problème.

Noir
la source
Quelque chose semblait étrange à propos des collisions de noms entre les deux plug-ins, ce qui m'a amené à examiner la carte de liens pour l'une de mes nombreuses DLL qui définit m_hDfltHeap comme un handle avec un lien externe. Effectivement, il est là pour tout le monde à voir et à utiliser, répertorié dans la carte de liaison comme _m_hDfltHeap. J'avais tout oublié de ce factoïde.
David A. Gray
4

Selon la spécification C99 / GNU99:

  • static

    • est un spécificateur de classe de stockage

    • les objets de portée de niveau fichier ont par défaut une liaison externe

    • les objets de portée de niveau fichier avec spécificateur statique ont une liaison interne
  • const

    • est un qualificatif de type (fait partie du type)

    • mot-clé appliqué à l'instance de gauche immédiate - ie

      • MyObj const * myVar; - pointeur non qualifié vers le type d'objet qualifié const

      • MyObj * const myVar; - pointeur qualifié const vers un type d'objet non qualifié

    • Utilisation la plus à gauche - appliquée au type d'objet, pas variable

      • const MyObj * myVar; - pointeur non qualifié vers le type d'objet qualifié const

DONC:

static NSString * const myVar; - pointeur constant vers une chaîne immuable avec liaison interne.

L'absence du staticmot - clé rendra le nom de la variable global et pourrait entraîner des conflits de nom au sein de l'application.

Alexey Pelekh
la source
4

inlineVariables C ++ 17

Si vous avez googlé "C ++ const static", alors c'est très probablement ce que vous voulez vraiment utiliser sont des variables en ligne C ++ 17 .

Cette fonctionnalité impressionnante de C ++ 17 nous permet de:

  • utilisez simplement une seule adresse mémoire pour chaque constante
  • le stocker en tant que constexpr: Comment déclarer constexpr extern?
  • faites-le en une seule ligne à partir d'un en-tête

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

Compilez et exécutez:

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 en amont .

Voir aussi: Comment fonctionnent les variables en ligne?

Norme C ++ sur les variables en ligne

La norme C ++ garantit que les adresses seront les mêmes. C ++ 17 N4659 standard draft 10.1.6 "Le spécificateur en ligne":

6 Une fonction ou une variable en ligne avec un lien externe doit avoir la même adresse dans toutes les unités de traduction.

cppreference https://en.cppreference.com/w/cpp/language/inline explique que si ce staticn'est pas donné, alors il a un lien externe.

Implémentation de variables en ligne GCC

On peut observer comment il est implémenté avec:

nm main.o notmain.o

qui contient:

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

et man nmdit à propos de u:

"u" Le symbole est un symbole mondial unique. Il s'agit d'une extension GNU de l'ensemble standard de liaisons de symboles ELF. Pour un tel symbole, l'éditeur de liens dynamique s'assurera que dans l'ensemble du processus, il n'y a qu'un seul symbole avec ce nom et ce type en cours d'utilisation.

on voit donc qu'il existe une extension ELF dédiée à cela.

Pré-C ++ 17: extern const

Avant C ++ 17, et en C, nous pouvons obtenir un effet très similaire avec un extern const, ce qui conduira à l'utilisation d'un seul emplacement mémoire.

Les inconvénients inlinesont:

  • il n'est pas possible de faire la variable constexpravec cette technique, inlinepermet seulement que: Comment déclarer constexpr extern?
  • c'est moins élégant car vous devez déclarer et définir la variable séparément dans l'en-tête et le fichier 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 en amont .

Alternatives d'en-tête pré-C ++ 17 uniquement

Ce ne sont pas aussi bons que la externsolution, mais ils fonctionnent et n'occupent qu'un seul emplacement mémoire:

Une constexprfonction, car constexprimpliqueinline et inline permet (force) la définition d'apparaître sur chaque unité de traduction :

constexpr int shared_inline_constexpr() { return 42; }

et je parie que tout compilateur décent intégrera l'appel.

Vous pouvez également utiliser une variable statique constou constexprcomme dans:

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

mais vous ne pouvez pas faire des choses comme prendre son adresse, sinon il devient utilisé odr, voir aussi: Définition des membres de données statiques constexpr

C

En C, la situation est la même que C ++ avant C ++ 17, j'ai téléchargé un exemple à l' adresse : Que signifie «statique» en C?

La seule différence est qu'en C ++, cela constimplique staticpour les globaux, mais ce n'est pas le cas en C: C ++ sémantique de `static const` vs` const`

Un moyen de l'intégrer entièrement?

TODO: existe-t-il un moyen d'intégrer complètement la variable, sans utiliser de mémoire du tout?

Tout comme ce que fait le préprocesseur.

Cela nécessiterait en quelque sorte:

  • interdire ou détecter si l'adresse de la variable est prise
  • ajoutez ces informations aux fichiers objets ELF et laissez LTO l'optimiser

En relation:

Testé dans Ubuntu 18.10, GCC 8.2.0.

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

Oui, il masque une variable dans un module des autres modules. En C ++, je l'utilise lorsque je ne veux / n'ai pas besoin de modifier un fichier .h qui déclenchera une reconstruction inutile d'autres fichiers. Aussi, j'ai mis le statique en premier:

static const int foo = 42;

En outre, en fonction de son utilisation, le compilateur n'allouera même pas de stockage pour lui et simplement "en ligne" la valeur où il est utilisé. Sans le statique, le compilateur ne peut pas supposer qu'il n'est pas utilisé ailleurs et ne peut pas en ligne.

Jim Buck
la source
2

C'est une constante globale visible / accessible uniquement dans le module de compilation (fichier .cpp). BTW utilisant statique à cette fin est obsolète. Mieux vaut utiliser un espace de noms anonyme et une énumération:

namespace
{
  enum
  {
     foo = 42
  };
}
Roskoto
la source
cela obligerait le compilateur à ne pas traiter foo comme une constante et, en tant que tel, entrave l'optimisation.
Nils Pipenbrinck
les valeurs enums sont toujours constantes, donc je ne vois pas en quoi cela
gênera
ah - vrai .. mon erreur. pensé que vous avez utilisé une simple variable int.
Nils Pipenbrinck
Roskoto, je ne sais pas exactement quel avantage il enuma dans ce contexte. Voulez-vous élaborer? Ils ne enumssont généralement utilisés que pour empêcher le compilateur d'allouer de l'espace pour la valeur (bien que les compilateurs modernes n'aient pas besoin de ce enumhack pour cela) et pour empêcher la création de pointeurs vers la valeur.
Konrad Rudolph
Konrad, Quel problème voyez-vous exactement en utilisant une énumération dans ce cas? Les énumérations sont utilisées lorsque vous avez besoin d'entiers constants, ce qui est exactement le cas.
Roskoto
1

Le rendre privé signifierait toujours qu'il apparaît dans l'en-tête. J'ai tendance à utiliser la méthode «la plus faible» qui fonctionne. Voir cet article classique de Scott Meyers: http://www.ddj.com/cpp/184401197 (il s'agit de fonctions, mais peut également être appliqué ici).

annee
la source