constexpr变量的唯一地址

问题描述 投票:10回答:3

是否可以为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,这样所有翻译单元都可以在其常量表达式中使用它,并且所有翻译单元都同意该符号的地址?我希望有一些额外的代码来表示这个符号实际所属的单个翻译单元,就像extern和没有extern的非constexpr变量一样。

c++ c++11 extern memory-address constexpr
3个回答
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
投票

我认为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()来获取值而不是像变量一样对待它。


1
投票

C ++ 17 inline变量

这个令人敬畏的C ++ 17功能允许我们:

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

“你”这个符号是一个独特的全球符号。这是标准ELF符号绑定集的GNU扩展。对于这样的符号,动态链接器将确保在整个过程中只有一个符号具有此名称和类型。

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

© www.soinside.com 2019 - 2024. All rights reserved.