web-dev-qa-db-fra.com

Que signifie "const statique" en C et C ++?

const static int foo = 42;

J'ai vu cela dans certains codes ici sur StackOverflow et je ne pouvais pas comprendre ce que ça 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 foo des autres modules. Est-ce correct? Si tel est le cas, pourquoi l’utiliserait-il dans un contexte C++ où vous pouvez simplement le rendre private?

106
c0m4

Il a des utilisations à la fois en C et en C++.

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

C’est ainsi que C traite ces variables (ou comment C++ traite les variables d’espace de nommage). En C++, un membre marqué static est partagé par toutes les instances d'une classe donnée. Qu'elle soit privée ou non n'affecte pas le fait qu'une variable est partagée par plusieurs instances. Avoir const là vous avertira si un code tente de le modifier.

S'il était strictement privé, chaque instance de la classe obtiendrait sa propre version (nonobstant l'optimiseur).

101
Chris Arguin

Beaucoup de gens ont donné la réponse de base, mais personne n'a signalé qu'en C++, const par défaut était static au niveau namespace (et certains ont donné des informations erronées). Voir la section 3.5.3 du standard C++ 98.

D'abord un fond:

Unité de traduction: Un fichier source après le pré-processeur (de manière récursive) incluait tous ses fichiers d'inclusion.

Liaison statique: Un symbole est uniquement disponible dans son unité de traduction.

Lien externe: Un symbole est disponible à partir d'autres unités de traduction.

Au niveau namespace

Ceci inclut l'espace de noms global, autrement dit 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

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

Au niveau class

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

195
Motti

Cette ligne de code peut en fait apparaître dans plusieurs contextes différents. Même si son comportement est à peu près identique, il existe de petites différences.

Portée de l'espace de noms

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

'i' sera visible dans chaque unité de traduction incluant 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 safe 0. Lorsque 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 consulté de l'extérieur de cette unité de traduction. Cependant, encore une fois, à moins que vous n'utilisiez son adresse, elle sera probablement traitée comme un type sûr 0.

Une chose à noter est la déclaration suivante:

const int i1 = 0;

est exactement identique à static const int i = 0. Une variable dans un espace de noms déclaré avec const et non explicitement déclarée avec extern est implicitement statique. Si vous y réfléchissez, le comité C++ avait l'intention d'autoriser la déclaration des variables const dans les fichiers d'en-tête sans toujours avoir besoin du mot clé static pour ne pas rompre 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 qu'il n'est pas nécessaire de définir 'i' si son adresse n'est pas requise. En d'autres termes, si vous utilisez uniquement 'i' en tant que type 0, 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 avoir une définition pour cela:

// 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
44
Richard Corden

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. Ceci est fait parce que, comme défini, foo a un lien externe. Une autre unité de compilation peut dire:

extern externe int foo;

Pour avoir accès à 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 int entier et doit recharger la valeur de la mémoire chaque fois qu'il est utilisé.

Maintenant, en déclarant que c'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 la place".

Je devrais également noter qu'en C++, le moyen préféré d'empêcher les noms d'échapper à l'unité de compilation en cours consiste à utiliser un espace de noms anonyme:

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

Il manque un 'int'. CA devrait etre:

const static int foo = 42;

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

Pourquoi 42? Si vous ne le savez pas déjà (et que vous avez du mal à le croire), c’est une référence à la réponse à la vie, à l’univers et à tout .

7
Kevin

En C++,

static const int foo = 42;

est le moyen préféré pour définir et utiliser des constantes. C'est à dire. utiliser ceci plutôt que

#define foo 42

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

4
paxos1977

Pour 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 CAD)), alors statique est un économiseur de vie évitant les conflits de noms, comme celui-ci:

  1. Le système CAD charge un plugin A, qui contient "const int foo = 42;".
  2. Le système charge un plugin B, qui a "const int foo = 23;" en elle.
  3. En conséquence, le plugin B utilisera la valeur 42 pour foo, car le chargeur de plugins réalisera qu'il existe déjà un "foo" avec une liaison externe.

Pire encore: l'étape 3 peut se comporter différemment selon l'optimisation du compilateur, le mécanisme de chargement du plug-in, 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.

4
Black

Selon les spécifications C99/GNU99:

  • static

    • est spécificateur de classe de stockage

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

    • les objets de la portée du niveau de fichier avec un spécificateur statique ont interne lien
  • const

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

    • mot-clé appliqué à l'instance de gauche immédiate - c'est-à-dire.

      • MyObj const * myVar; - pointeur non qualifié sur 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é sur le type d'objet qualifié const

THUS:

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

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

3
Alexey Pelekh

Oui, il cache une variable dans un module à partir d'autres modules. En C++, je l'utilise quand je ne veux pas/n'ai pas besoin de changer un fichier .h qui déclenchera une reconstruction inutile d'autres fichiers. Aussi, je mets d'abord la statique:

static const int foo = 42;

De plus, selon son utilisation, le compilateur ne lui allouera même pas de stockage et se contente de "mettre en ligne" la valeur à laquelle il est utilisé. Sans la statique, le compilateur ne peut pas supposer qu'il n'est pas utilisé ailleurs ni en ligne.

2
Jim Buck

Cette constante globale est 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
  };
}
2
Roskoto

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

1
yrp

C++ 17 inline variables

Si vous recherchez "une constante statique C++" dans Googled, c'est très probablement ce que vous voulez vraiment utiliser: variables en ligne C++ 17 .

Cette fonctionnalité géniale de C++ 17 nous permet de:

  • utilisez simplement une seule adresse mémoire pour chaque constante
  • stockez-le 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;
}

Compiler et exécuter:

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. projet de norme C++ 17 N4659 10.1.6 "Le spécificateur en ligne":

6 Une fonction ou une variable en ligne avec une liaison 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 static n'est pas indiqué, il est associé à un lien externe.

Implémentation de la variable en ligne GCC

Nous pouvons observer comment cela 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 nm dit à propos de u:

"u" Le symbole est un symbole global unique. Il s’agit d’une extension GNU à l’ensemble standard de liaisons de symboles ELF. Pour un tel symbole, l’éditeur de liens dynamique s’assurera qu’un seul symbole porte ce nom et ce type utilisés dans le processus. .

nous voyons 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 entraînera l’utilisation d’un seul emplacement mémoire.

Les inconvénients de inline sont les suivants:

  • il n’est pas possible de créer la variable constexpr avec cette technique, seul inline permet que: Comment déclarer constexpr extern?
  • il 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 .

En-tête pré-C++ 17 uniquement alternatives

Celles-ci ne sont pas aussi bonnes que la solution extern, mais elles fonctionnent et n'occupent qu'un seul emplacement mémoire:

Une fonction constexpr, car constexpr implique inline et inlinepermet à la définition d'apparaître chaque unité de traduction :

constexpr int shared_inline_constexpr() { return 42; }

et je parie que tout compilateur décent s'inscrira dans l'appel.

Vous pouvez également utiliser une variable statique const ou constexpr comme 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 prendre des choses comme prendre son adresse ou sinon il devient odr-utilisé, voir aussi: Définition des membres de données statiques constexpr

[~ # ~] c [~ # ~]

En C, la situation est la même que celle de C++ pré C++ 17, j'ai téléchargé un exemple à l'adresse suivante: Que signifie "statique" en C?

La seule différence est qu'en C++, const implique static pour les globals, mais pas dans C: sémantique C++ de `static const` vs` const`

Y at-il un moyen de l’intégrer complètement?

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

Cela ressemble beaucoup à ce que fait le pré-processeur.

Cela nécessiterait en quelque sorte:

  • interdire ou détecter si l'adresse de la variable est prise
  • ajouter cette information aux fichiers objet ELF et laisser LTO l'optimiser

Apparenté, relié, connexe:

Testé sous Ubuntu 18.10, GCC 8.2.0.