inline
Variáveis C ++ 17
Se você pesquisou "C ++ const static" no Google, é muito provável que o que você realmente queira usar sejam as variáveis inline do C ++ 17 .
Este incrível recurso do C ++ 17 nos permite:
- use convenientemente apenas um único endereço de memória para cada constante
- armazene-o como um
constexpr
: Como declarar constexpr extern?
- faça isso em uma única linha de um cabeçalho
main.cpp
#include <cassert>
#include "notmain.hpp"
int main() {
// Both files see the same memory address.
assert(¬main_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 ¬main_i;
}
Compile e execute:
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 upstream .
Consulte também: Como funcionam as variáveis embutidas?
Padrão C ++ em variáveis embutidas
O padrão C ++ garante que os endereços serão os mesmos. Rascunho do padrão C ++ 17 N4659
10.1.6 "O especificador embutido":
6 Uma função ou variável embutida com ligação externa deve ter o mesmo endereço em todas as unidades de tradução.
cppreference https://en.cppreference.com/w/cpp/language/inline explica que se static
não for fornecido, então tem uma ligação externa.
Implementação de variável inline GCC
Podemos observar como isso é implementado com:
nm main.o notmain.o
que contém:
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 nm
fala sobre u
:
"u" O símbolo é um símbolo global único. Esta é uma extensão GNU para o conjunto padrão de associações de símbolos ELF. Para tal símbolo, o vinculador dinâmico garantirá que em todo o processo haja apenas um símbolo com esse nome e tipo em uso.
portanto, vemos que há uma extensão ELF dedicada para isso.
Pré-C ++ 17: extern const
Antes do C ++ 17, e no C, podemos obter um efeito muito semelhante com um extern const
, que levará ao uso de um único local de memória.
As desvantagens inline
são:
- não é possível fazer a variável
constexpr
com esta técnica, apenas inline
permite que: Como declarar constexpr extern?
- é menos elegante porque você deve declarar e definir a variável separadamente no cabeçalho e no arquivo cpp
main.cpp
#include <cassert>
#include "notmain.hpp"
int main() {
// Both files see the same memory address.
assert(¬main_i == notmain_func());
assert(notmain_i == 42);
}
notmain.cpp
#include "notmain.hpp"
const int notmain_i = 42;
const int* notmain_func() {
return ¬main_i;
}
notmain.hpp
#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP
extern const int notmain_i;
const int* notmain_func();
#endif
GitHub upstream .
Alternativas apenas de cabeçalho pré-C ++ 17
Eles não são tão bons quanto a extern
solução, mas funcionam e ocupam apenas um único local de memória:
Uma constexpr
função, porque constexpr
implicainline
e inline
permite (força) que a definição apareça em cada unidade de tradução :
constexpr int shared_inline_constexpr() { return 42; }
e aposto que qualquer compilador decente fará a chamada inline.
Você também pode usar uma variável const
ou constexpr
estática como em:
#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;
}
mas você não pode fazer coisas como pegar seu endereço, ou então ele se torna usado por odr, veja também: Definindo membros de dados estáticos constexpr
C
Em C, a situação é a mesma que em C ++ pré C ++ 17, enviei um exemplo em: O que significa "estático" em C?
A única diferença é que em C ++, const
implica static
para globais, mas não em C: C ++ semântica de `const estático` vs` const`
Há alguma maneira de embuti-lo totalmente?
TODO: existe alguma maneira de incorporar totalmente a variável, sem usar qualquer memória?
Muito parecido com o que o pré-processador faz.
Isso exigiria de alguma forma:
- proibir ou detectar se o endereço da variável é levado
- adicione essa informação aos arquivos de objeto ELF e deixe LTO otimizá-la
Relacionado:
Testado em Ubuntu 18.10, GCC 8.2.0.