Уникальный адрес для переменной 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.


person MvG    schedule 14.02.2013    source источник
comment
Я полагаю, что ваш foo имеет внутреннюю связь, поэтому вы видите две отдельные копии. Обычное решение вашей проблемы состоит в том, чтобы объявить extern const int foo в заголовке и реализовать как const int foo = 42; в одной единице перевода. Но тогда очевидно, что это не может быть константное выражение, поскольку int a[foo] должно быть разрешимым во время компиляции, а не только во время компоновки.   -  person Kerrek SB    schedule 14.02.2013
comment
Возможно, есть другой способ добиться того, что вы пытаетесь сделать. Итак... что именно пытается сделать с этим адресом?   -  person Nicol Bolas    schedule 14.02.2013
comment
@NicolBolas: Пока я пытаюсь разобраться с constexpr в целом. У меня была привычка использовать внешнюю связь для const глобальных переменных, чтобы избежать дублирования выделения памяти, даже если кто-то решит взять адрес такого зверя. Теперь с constexpr это больше не кажется возможным. Так что я на самом деле пытаюсь выяснить, есть ли способ избежать дублирования данных, даже если какой-то странный код, который я сейчас не могу себе представить, решит взять адреса этих вещей повсюду.   -  person MvG    schedule 14.02.2013
comment
В этом контексте не принято говорить о «выделении памяти», точно так же, как обычно не говорят, что 4 + 4 вообще включает в себя выделение памяти.   -  person Luc Danton    schedule 14.02.2013


Ответы (3)


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

фу.ч:

#ifndef FOO_H
#define FOO_H

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

#endif // FOO_H

foo.cpp:

#include "foo.hpp"

constexpr int Foo::foo; // definition

бар.cpp:

#include "foo.hpp"

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

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

основной.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
person mbeoayt    schedule 08.12.2014

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

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

Стандарт 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.

person Ciro Santilli 新疆再教育营六四事件ۍ    schedule 22.12.2018
comment
cppreference также объясняет, что статические данные-члены класса в области пространства имен имеют внешнюю связь, если сам класс имеет внешнюю связь (т. е. не является членом безымянного пространства имен). Итак, пока некоторый класс Foo не является членом безымянного пространства имен, тогда inline статический член Foo::bar будет иметь внешнюю связь, и применимо вышеизложенное. - person monkey0506; 15.08.2019

Я думаю, что 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() для получения значения вместо того, чтобы обращаться с ним как с переменной.

person Jonesinator    schedule 05.06.2013