Comment fonctionnent les variables en ligne?

124

Lors de la réunion Oulu ISO C ++ Standards 2016, une proposition appelée Variables en ligne a été votée en C ++ 17 par le comité des normes.

En termes simples, que sont les variables en ligne, comment fonctionnent-elles et à quoi servent-elles? Comment les variables en ligne doivent-elles être déclarées, définies et utilisées?

jotik
la source
@jotik Je suppose que l'opération équivalente remplacerait toute occurrence de la variable par sa valeur. Normalement, cela n'est valide que si la variable l'est const.
melpomene
5
Ce n'est pas la seule chose que le inlinemot-clé fait pour les fonctions. Le inlinemot-clé, lorsqu'il est appliqué aux fonctions, a un autre effet crucial, qui se traduit directement en variables. Une inlinefonction, qui est vraisemblablement déclarée dans un fichier d'en-tête, n'entraînera pas d'erreurs de «symbole en double» au moment de la liaison, même si l'en-tête obtient #included par plusieurs unités de traduction. Le inlinemot-clé, lorsqu'il est appliqué aux variables, aura le même résultat exact. La fin.
Sam Varshavchik
4
^ Dans le sens de «remplacer tout appel à cette fonction par une copie en place de son code», il inlinene s'agit que d'une requête faible et non contraignante adressée à l'optimiseur. Les compilateurs sont libres de ne pas intégrer les fonctions demandées et / ou d'intégrer celles que vous n'avez pas annotées. Le but réel du inlinemot-clé est plutôt d'éviter de multiples erreurs de définition.
underscore_d

Réponses:

121

La première phrase de la proposition:

» Le inlineprescripteur peut être appliqué aux variables ainsi que des fonctions.

L'effet garanti de inlinetel qu'appliqué à une fonction, est de permettre à la fonction d'être définie de manière identique, avec un lien externe, dans plusieurs unités de traduction. Pour la pratique, cela signifie définir la fonction dans un en-tête, qui peut être inclus dans plusieurs unités de traduction. La proposition étend cette possibilité aux variables.

Ainsi, en termes pratiques, la proposition (maintenant acceptée) vous permet d'utiliser le inlinemot - clé pour définir une constvariable d'étendue d'espace de noms de lien externe , ou tout staticmembre de données de classe, dans un fichier d'en-tête, de sorte que les multiples définitions qui résultent lorsque cet en-tête est inclus dans plusieurs unités de traduction sont OK avec l'éditeur de liens - il en choisit simplement une .

Jusqu'au C ++ 14 inclusivement, la machinerie interne pour cela était là, afin de prendre en charge les staticvariables dans les modèles de classe, mais il n'y avait aucun moyen pratique d'utiliser cette machinerie. Il fallait recourir à des trucs comme

template< class Dummy >
struct Kath_
{
    static std::string const hi;
};

template< class Dummy >
std::string const Kath_<Dummy>::hi = "Zzzzz...";

using Kath = Kath_<void>;    // Allows you to write `Kath::hi`.

À partir de C ++ 17 et au-delà, je pense que l'on peut simplement écrire

struct Kath
{
    static std::string const hi;
};

inline std::string const Kath::hi = "Zzzzz...";    // Simpler!

… Dans un fichier d'en-tête.

La proposition comprend le libellé

Un membre de données statique en ligne peut être défini dans la définition de classe et peut spécifier un initialiseur entre accolades ou égales. Si le membre est déclaré avec le constexprspécificateur, il peut être redéclaré dans la portée de l'espace de noms sans initialiseur (cette utilisation est obsolète; voir DX). Les déclarations d'autres membres de données statiques ne doivent pas spécifier d'itialiseur entre accolades ou égales

… Ce qui permet de simplifier davantage ce qui précède en

struct Kath
{
    static inline std::string const hi = "Zzzzz...";    // Simplest!
};

… Comme l'a noté TC dans un commentaire à cette réponse.

En outre, le  ​constexprspécificateur implique  inline pour les membres de données statiques ainsi que pour les fonctions.


Notes:
¹ Pour une fonction a inlineégalement un effet d'indication sur l'optimisation, que le compilateur devrait préférer remplacer les appels de cette fonction par une substitution directe du code machine de la fonction. Cette allusion peut être ignorée.

Bravo et hth. - Alf
la source
2
En outre, la restriction const s'applique uniquement aux variables de portée de l'espace de noms. Ceux qui ont une portée de classe (comme Kath::hi) n'ont pas besoin d'être const.
TC
4
Des rapports plus récents indiquent que la constrestriction est entièrement supprimée.
TC
2
@Nick: Puisque Richard Smith (l'actuel "éditeur de projet" du comité C ++) est l'un des deux auteurs, et puisqu'il est "le propriétaire du code du frontend Clang C ++", a deviné Clang. Et la construction compilée avec clang 3.9.0 sur Godbolt . Il avertit que les variables en ligne sont une extension C ++ 1z. Je n'ai trouvé aucun moyen de partager le choix et les options de la source et du compilateur, donc le lien est juste vers le site en général, désolé.
Bravo et hth. - Alf du
1
Pourquoi avoir besoin d'un mot clé en ligne à l'intérieur de la déclaration de classe / struct? Pourquoi ne pas permettre simplement static std::string const hi = "Zzzzz...";?
sasha.sochka
2
@EmilianCioca: Non, vous vous heurteriez au fiasco de l'ordre d'initialisation statique . Un singleton est essentiellement un appareil pour éviter cela.
Bravo et hth. - Alf
15

Les variables en ligne sont très similaires aux fonctions en ligne. Il signale à l'éditeur de liens qu'une seule instance de la variable doit exister, même si la variable est vue dans plusieurs unités de compilation. L'éditeur de liens doit s'assurer qu'aucune copie supplémentaire n'est créée.

Les variables en ligne peuvent être utilisées pour définir des globaux dans les bibliothèques d'en-tête uniquement. Avant C ++ 17, ils devaient utiliser des solutions de contournement (fonctions en ligne ou hacks de modèles).

Par exemple, une solution de contournement consiste à utiliser le singleton de Meyer avec une fonction en ligne:

inline T& instance()
{
  static T global;
  return global;
}

Cette approche présente quelques inconvénients, principalement en termes de performances. Cette surcharge pourrait être évitée par des solutions modèles, mais il est facile de se tromper.

Avec les variables en ligne, vous pouvez le déclarer directement (sans obtenir une erreur de l'éditeur de liens à définitions multiples):

inline T global;

En dehors des bibliothèques d'en-tête uniquement, il existe d'autres cas où les variables en ligne peuvent aider. Nir Friedman couvre ce sujet dans son discours à CppCon: Ce que les développeurs C ++ devraient savoir sur les globals (et le linker) . La partie sur les variables en ligne et les solutions de contournement commence à 18m9s .

Pour faire court, si vous avez besoin de déclarer des variables globales qui sont partagées entre des unités de compilation, les déclarer comme variables en ligne dans le fichier d'en-tête est simple et évite les problèmes avec les solutions de contournement pré-C ++ 17.

(Il existe encore des cas d'utilisation pour le singleton de Meyer, par exemple, si vous souhaitez explicitement avoir une initialisation paresseuse.)

Philipp Claßen
la source
11

Exemple exécutable minimal

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?
  • 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

Le standard 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.

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 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 entière 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, ou bien elle devient utilisée odr, voir aussi: https://en.cppreference.com/w/cpp/language/static "Membres statiques constants" et Définition des données statiques constexpr membres

C

En C, la situation est la même que C ++ avant C ++ 17, j'ai téléchargé un exemple sur: 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
inlinen'a presque rien à voir avec l'inlining, ni pour les fonctions ni pour les variables, malgré le mot lui-même. inlinene dit pas au compilateur d'insérer quoi que ce soit. Il dit à l'éditeur de liens de s'assurer qu'il n'y a qu'une seule définition, ce qui était traditionnellement le travail du programmeur. Alors, "Un moyen de l'intégrer complètement?" est au moins une question totalement indépendante.
not-a-user