web-dev-qa-db-fra.com

Mélanger extern et const

Puis-je mélanger extern et const, comme const externe? Si oui, le qualificatif const impose-t-il son règne uniquement dans la portée dans laquelle il est déclaré ou doit-il correspondre exactement à la déclaration de l'unité de traduction dans laquelle il est déclaré? C'est à dire. puis-je déclarer dire extern const int i; même lorsque le réel i n'est pas un const et vice versa?

51
legends2k
  • Oui, vous pouvez les utiliser ensemble.
  • Et oui, il doit correspondre exactement à la déclaration dans l'unité de traduction dans laquelle il est réellement déclaré. À moins bien sûr que vous ne participiez au Concours de programmation C en sous-main :-)

Le schéma habituel est:

  • file.h:
    extern const int a_global_var;
  • file.c:
    #include "file.h"
    const int a_global_var = /* some const expression */;

Edit: commentaire de legends2k incorporé. Merci.

50
edgar.holleis

Vous pouvez les utiliser ensemble. Mais vous devez être cohérent dans votre utilisation de const car lorsque C++ nomme la décoration, const est inclus dans les informations de type utilisées pour décorer les noms de symboles. donc extern const int i fera référence à une variable différente de extern int i

Sauf si vous utilisez extern "C" {}. La décoration du nom C ne fait pas attention à la const.

5
John Knoeller

C++ 17 inline variables

Si vous pensez que vous voulez un extern const, il est plus probable que vous souhaitiez réellement utiliser C++ 17 inline variables .

Cette superbe fonctionnalité 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?
  • le faire 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

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

6 Une fonction ou variable en ligne avec 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 donné, alors il a un lien externe.

Implémentation des variables en ligne

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 nm dit à propos de u:

"u" Le symbole est un symbole global 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 tout le processus, il n'y a qu'un seul symbole avec ce nom et ce type en cours d'utilisation. .

nous voyons donc qu'il existe une extension ELF dédiée pour cela.

Pré-C++ 17: extern const

extern const fonctionne comme dans l'exemple ci-dessous, mais les inconvénients par rapport à inline sont:

  • il n'est pas possible de faire 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 .

Alternatives à l'en-tête Pre-C++ 17 uniquement

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

Une fonction constexpr, car constexpr implique inline et inlinepermet (force) que la définition apparaisse sur chaque unité de traduction :

constexpr int shared_inline_constexpr() { return 42; }

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

Vous pouvez également utiliser une variable d'entier 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 faire des choses comme prendre son adresse, sinon il devient odr-utilisé, voir aussi: https://en.cppreference.com/w/cpp/language/static "Statique constante membres "et Définition des membres de données statiques constexpr

Y a-t-il un moyen de l'intégrer complètement?

TODO: existe-t-il un moyen de complètement aligner 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 les optimiser

En relation:

Testé dans Ubuntu 18.10, GCC 8.2.0.

Vous pouvez les utiliser ensemble et vous pouvez faire toutes sortes de choses qui ignorent le mot clé const, car c'est tout; un mot-clé. Il indique au compilateur que vous ne changerez pas une variable, ce qui à son tour permet au compilateur de faire des optimisations utiles et vous empêche de changer des choses que vous ne vouliez pas.

Possibility.com a n article décent avec un peu plus de fond.

1
Jon Cage