是否可以为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
一样。
答案 0 :(得分:12)
如果需要获取constexpr变量的地址,请将其声明为静态成员变量。它可以这种方式用作常量表达式(与使用返回const的函数相反)。
foo.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;
}
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
答案 1 :(得分: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()
来获取值,而不是将其视为变量。
答案 2 :(得分:1)
C ++ 17 inline
变量
这项令人敬畏的C ++ 17功能使我们能够:
constexpr
:How to declare constexpr extern? 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;
}
编译并运行:
编译并运行:
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。 另请参阅:How do inline variables work?
内联变量的C ++标准
C ++标准保证地址相同。 C++17 N4659 standard draft 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”该符号是唯一的全局符号。这是对ELF符号绑定的标准集合的GNU扩展。对于这样的符号,动态链接器将确保在整个过程中 只有一个使用此名称和类型的符号。
所以我们看到有一个专用的ELF扩展。