Come funzionano le variabili in linea?


124

All'incontro 2016 sugli standard ISO C ++ di Oulu, una proposta chiamata Variabili in linea è stata votata in C ++ 17 dal comitato degli standard.

In parole povere, cosa sono le variabili in linea, come funzionano e per cosa sono utili? Come devono essere dichiarate, definite e utilizzate le variabili inline?


@jotik Immagino che l'operazione equivalente sarebbe sostituire qualsiasi occorrenza della variabile con il suo valore. Normalmente questo è valido solo se la variabile è const.
melpomene

5
Non è l'unica cosa che la inlineparola chiave fa per le funzioni. La inlineparola chiave, quando applicata alle funzioni, ha un altro effetto cruciale, che si traduce direttamente in variabili. Una inlinefunzione, presumibilmente dichiarata in un file di intestazione, non comporterà errori di "simbolo duplicato" al momento del collegamento, anche se l'intestazione riceve #included da più unità di traduzione. La inlineparola chiave, se applicata alle variabili, avrà lo stesso esatto risultato. La fine.
Sam Varshavchik

4
^ Nel senso di "sostituire qualsiasi chiamata a questa funzione con una copia sul posto del suo codice", inlineè solo una richiesta debole e non vincolante per l'ottimizzatore. I compilatori sono liberi di non incorporare le funzioni richieste e / o inline quelle che non hai annotato. Piuttosto, lo scopo effettivo della inlineparola chiave è aggirare più errori di definizione.
underscore_d

Risposte:


121

La prima frase della proposta:

L' inlineidentificatore può essere applicato a variabili come pure funzioni.

L'effetto garantito di inlinecome applicato a una funzione, è quello di consentire alla funzione di essere definita in modo identico, con collegamento esterno, in più unità di traduzione. Per la pratica ciò significa definire la funzione in un'intestazione, che può essere inclusa in più unità di traduzione. La proposta estende questa possibilità alle variabili.

Quindi, in termini pratici la proposta (ora accettata) consente di utilizzare la inlineparola chiave per definire una constvariabile di ambito dello spazio dei nomi di collegamento esterno , o qualsiasi staticmembro di dati di classe, in un file di intestazione, in modo che le definizioni multiple risultanti quando quell'intestazione è inclusa in più unità di traduzione vanno bene con il linker: ne sceglie solo una .

Fino al C ++ 14 incluso, il meccanismo interno per questo è stato lì, al fine di supportare le staticvariabili nei modelli di classe, ma non c'era un modo conveniente per usare quel meccanismo. Si doveva ricorrere a trucchi come

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`.

Da C ++ 17 in poi credo che si possa scrivere solo

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

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

... in un file di intestazione.

La proposta include la dicitura

" Un membro dati statico inline può essere definito nella definizione della classe e può specificare un inizializzatore di parentesi graffa o uguale. Se il membro viene dichiarato con lo constexprspecificatore, può essere nuovamente dichiarato nell'ambito dello spazio dei nomi senza inizializzatore (questo utilizzo è deprecato; vedere DX). Le dichiarazioni di altri membri di dati statici non devono specificare una parentesi graffa o uguale nell'itializzatore

... che consente di semplificare ulteriormente quanto sopra a solo

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

... come notato da TC in un commento a questa risposta.

Inoltre, lo  ​constexprspecificatore implica sia  inline i membri dei dati statici che le funzioni.


Note:
¹ Poiché una funzione inlineha anche un effetto di suggerimento sull'ottimizzazione, il compilatore dovrebbe preferire sostituire le chiamate di questa funzione con la sostituzione diretta del codice macchina della funzione. Questo suggerimento può essere ignorato.


2
Inoltre, la restrizione const si applica solo alle variabili di ambito dello spazio dei nomi. Quelli con ambito di classe (come Kath::hi) non devono essere const.
TC

4
I rapporti più recenti indicano che la constrestrizione è stata eliminata completamente.
TC

2
@ Nick: Dato che Richard Smith (l'attuale "editor del progetto" del comitato C ++) è uno dei due autori, e poiché è "il proprietario del codice del frontend Clang C ++", ha indovinato Clang. E il costrutto compilato con clang 3.9.0 su Godbolt . Avverte che le variabili inline sono un'estensione C ++ 1z. Non ho trovato alcun modo per condividere la sorgente e la scelta e le opzioni del compilatore, quindi il collegamento è solo al sito in generale, mi dispiace.
Saluti e salute. - Alf

1
perché è necessaria la parola chiave inline all'interno della dichiarazione class / struct? Perché non permettere semplicemente static std::string const hi = "Zzzzz...";?
sasha.sochka

2
@EmilianCioca: No, ti sbaglieresti contro il fiasco dell'ordine di inizializzazione statica . Un singleton è essenzialmente un dispositivo per evitarlo.
Saluti e salute. - Alf

15

Le variabili inline sono molto simili alle funzioni inline. Segnala al linker che dovrebbe esistere solo un'istanza della variabile, anche se la variabile è vista in più unità di compilazione. Il linker deve garantire che non vengano create più copie.

Le variabili inline possono essere utilizzate per definire globali nelle librerie di sola intestazione. Prima di C ++ 17, dovevano usare soluzioni alternative (funzioni inline o hack di modelli).

Ad esempio, una soluzione alternativa consiste nell'usare il singleton di Meyer con una funzione inline:

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

Ci sono alcuni inconvenienti con questo approccio, principalmente in termini di prestazioni. Questo sovraccarico potrebbe essere evitato dalle soluzioni dei modelli, ma è facile sbagliare.

Con le variabili inline, puoi dichiararlo direttamente (senza ottenere un errore di linker di definizioni multiple):

inline T global;

Oltre alle librerie di sola intestazione, ci sono altri casi in cui le variabili inline possono aiutare. Nir Friedman copre questo argomento nel suo discorso al CppCon: Cosa dovrebbero sapere gli sviluppatori C ++ sulle globali (e sul linker) . La parte sulle variabili in linea e le soluzioni alternative inizia a 18m9s .

Per farla breve, se è necessario dichiarare variabili globali condivise tra unità di compilazione, dichiararle come variabili inline nel file di intestazione è semplice ed evita i problemi con le soluzioni alternative pre-C ++ 17.

(Esistono ancora casi d'uso per il singleton di Meyer, ad esempio, se si desidera esplicitamente avere un'inizializzazione lenta.)


11

Esempio minimo eseguibile

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

  • utilizzare convenientemente un solo indirizzo di memoria per ogni costante
  • conservalo 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 .

Guarda 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 nell'intero processo ci sia solo un 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 constexpruna variabile intera 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-used, vedi anche: https://en.cppreference.com/w/cpp/language/static "Constant static members" e Defining constexpr static data membri

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
inlinenon ha quasi nulla a che fare con l'inlining, né per le funzioni né per le variabili, nonostante la parola stessa. inlinenon dice al compilatore di inline nulla. Indica al linker di assicurarsi che ci sia una sola definizione, che è stato tradizionalmente il lavoro del programmatore. Quindi "Qualche modo per integrarlo completamente?" è almeno una domanda completamente non correlata.
non-utente
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.