14

是否可以为 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.ccb.cc使用 gcc 4.7 将它们链接在一起,我看到打印了两个不同的地址。extern如果我在标题中添加关键字,我会收到一个链接器错误duplicate symbol _foo in: a.o and b.o,我觉得这有点令人惊讶,因为我认为添加extern更有可能导致编译器从另一个对象导入该符号,而不是从当前对象导出它。但似乎我对事情如何运作的理解在这里是错误的。

是否有一种合理的方法可以在一个标头中声明一个 constexpr,以便所有翻译单元都可以在其常量表达式中使用它,并且所有翻译单元都同意该符号的地址?我希望有一些额外的代码来表示这个符号实际属于的单个翻译单元,就像 withextern和 non- externvariables without一样constexpr

4

3 回答 3

14

如果需要获取 constexpr 变量的地址,则声明为静态成员变量。它可以通过这种方式用作常量表达式(与使用返回 const 的函数相反)。

富.h:

#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

bar.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
于 2014-12-08T00:15:35.763 回答
9

C++17inline变量

这个很棒的 C++17 特性使我们能够:

主文件

#include <cassert>

#include "notmain.hpp"

int main() {
    // Both files see the same memory address.
    assert(&notmain_i == notmain_func());
    assert(notmain_i == 42);
}

不是main.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

inline constexpr int notmain_i = 42;

const int* notmain_func();

#endif

非main.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 nmu

"u" 该符号是唯一的全局符号。这是标准 ELF 符号绑定集的 GNU 扩展。对于这样的符号,动态链接器将确保在整个过程中只有一个具有此名称和类型的符号在使用中。

所以我们看到有一个专门的 ELF 扩展。

于 2018-12-22T19:28:38.280 回答
1

我认为 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()来获取值,而不是将其视为变量。

于 2013-06-05T01:43:44.537 回答