Уникальный адрес для переменной constexpr

Возможно ли иметь уникальный адрес, выделенный для переменной constexpr, то есть для всех единиц перевода, где эта переменная доступна (обычно через заголовок)? Рассмотрим следующий пример:

// foo.hh
#include <iostream>
constexpr int foo = 42;

// a.cc
#include "foo.hh"
void a(void) { std::cout << "a: " << &foo << std::endl; }

// b.cc
#include "foo.hh"
extern void a(void);
int main(int argc, char** argv) {
  a();
  std::cout << "b: " << &foo << std::endl;
}

Компилируя a.cc и b.cc отдельно и связывая их вместе, используя gcc 4.7, я вижу два разных адреса. Если я добавлю ключевое слово extern в заголовок, я получаю ошибку компоновщика duplicate symbol _foo in: a.o and b.o, которую я нахожу довольно неожиданным, потому что я думал, что добавление extern скорее приведет к тому, что компилятор импортирует этот символ из другого объекта вместо экспортируя его из текущего объекта. Но, похоже, мое понимание того, как все работает, было неправильным.

Есть ли разумный способ иметь constexpr, объявленный в одном заголовке, так что все единицы перевода могут использовать его в своих постоянных выражениях и что все единицы перевода согласны относительно адреса этого символа? Я ожидаю, что некоторый дополнительный код будет обозначать единую единицу перевода, где этот символ действительно принадлежит, как и переменные extern и не extern без constexpr.

Ответ 1

Если вам нужно взять адрес переменной constexpr, объявите его как статическую переменную-член. Он может использоваться как постоянное выражение таким образом (в отличие от использования функции, возвращающей константу).

foo.hpp:

#ifndef FOO_HPP
#define FOO_HPP

struct Foo {
  static constexpr int foo { 42 }; // declaration
};

#endif // FOO_HPP

foo.cpp:

#include "foo.hpp"

constexpr int Foo::foo; // definition

bar.cpp:

#include "foo.hpp"

const int* foo_addr() {
  return &Foo::foo;
}

int foo_val() {
  return Foo::foo;
}

main.cpp:

#include <iostream>
#include "foo.hpp"

extern const int* foo_addr();
extern int foo_val();

constexpr int arr[Foo::foo] {}; // foo used as constant expression

int main() {
  std::cout << foo_addr() << " = " << foo_val() << std::endl;
  std::cout << &Foo::foo << " = " << Foo::foo << std::endl;
}

Вывод:

$ g++ -std=c++11 foo.cpp bar.cpp main.cpp -o test && ./test
0x400a44 = 42
0x400a44 = 42

Ответ 2

Я думаю, что constexpr больше предназначен для функций, возвращаемое значение которых является постоянным. Вы можете привязать постоянную переменную к возвращаемому значению функции constexpr и выставить ее вместо этого. Например:

// constexpr.h
#ifndef __CONSTEXPR_H
#define __CONSTEXPR_H

extern const int foo;

#endif // __CONSTEXPR_H

// constexpr.cpp
#include "constexpr.h"

constexpr int foo_expr()
{
    return 42;
}

const int foo = foo_expr();

// unit1.cpp
#include <iostream>
#include "constexpr.h"

void unit1_print_foo()
{
    std::cout << &foo << " = " << foo << std::endl;
}

// unit2.cpp
#include <iostream>
#include "constexpr.h"

void unit2_print_foo()
{
    std::cout << &foo << " = " << foo << std::endl;
}

// main.cpp
extern void unit1_print_foo();
extern void unit2_print_foo();

int main(int, char**)
{
    unit1_print_foo();
    unit2_print_foo();
}

Мой результат:

$ g++-4.7 -std=c++11 constexpr.cpp unit1.cpp unit2.cpp main.cpp -o test && ./test
0x400ae4 = 42
0x400ae4 = 42

Однако обычно должно быть достаточно, чтобы функция foo_expr была внешне видимой, а вызывающие использовали foo_expr(), чтобы получить значение вместо того, чтобы рассматривать его как переменную.

Ответ 3

C++ 17 inline переменных

Эта удивительная функция C++ 17 позволяет нам:

  • удобно использовать только один адрес памяти для каждой константы
  • сохранить его как constexpr: Как объявить constexpr extern?
  • сделать это в одной строке из одного заголовка

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;
}

Скомпилируйте и запустите:

Скомпилируйте и запустите:

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 вверх по течению. Смотрите также: Как работают встроенные переменные?

C++ стандарт для встроенных переменных

Стандарт C++ гарантирует, что адреса будут одинаковыми. C++ 17 Типовой проект N4659 10.1.6 " Встроенный спецификатор":

6 Встроенная функция или переменная с внешней связью должны иметь одинаковый адрес во всех единицах перевода.

cppreference https://en.cppreference.com/w/cpp/language/inline объясняет, что если static не задан, то он имеет внешнюю связь.

Реализация встроенных переменных

Мы можем наблюдать, как это реализовано с:

nm main.o notmain.o

который содержит:

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

и man nm говорит о u:

"U" Символ является уникальным глобальным символом. Это расширение GNU для стандартного набора привязок символов ELF. Для такого символа динамический компоновщик будет следить за тем, чтобы во всем процессе был только один символ с этим именем и типом.

Итак, мы видим, что для этого есть выделенное расширение ELF.