局部变量的地址是constexpr吗?

Is the address of a local variable a constexpr?

在 Bjarne Stroustrup 的书 "The C++ Programming Language (4th Edition)" 上。 267(第 10.4.5 节地址常量表达式),他使用了一个代码示例,其中局部变量的地址设置为 constexpr 变量。我觉得这看起来很奇怪,所以我尝试 运行 使用 g++ 版本 7.3.0 来设置示例,但无法获得相同的结果。这是他的代码示例逐字(虽然略有删节):

extern char glob;

void f(char loc) {
    constexpr const char* p0 = &glob; // OK: &glob's is a constant
    constexpr const char* p2 = &loc;  // OK: &loc is constant in its scope
}

当我 运行 这个时,我得到:

error: ‘(const char*)(& loc)’ is not a constant expression

g++ 是否发生了一些我不知道的事情,或者 Bjarne 的示例是否还有其他问题?

Bjarne Stroustrup 的书 "The C++ Programming Language (4th Edition)" 在第 1 页的早期印刷。 267 有 OP 问题中概述的错误。当前的印刷版和电子版已经 "corrected" 但引入了稍后描述的另一个错误。它现在指的是以下代码:

constexpr const char* p1="asdf";

这没关系,因为 "asdf" 存储在固定的内存位置。本书早先印刷错误在这里:

void f(char loc) {
    constexpr const char* p0 = &glob; // OK: &glob's is a constant
    constexpr const char* p2 = &loc;  // OK: &loc is constant in its scope
}

但是,loc不在固定的内存位置。它在堆栈上,并且会根据调用时间的不同而有不同的位置。

不过,目前第4版印刷还有一个错误。这是 10.5.4 中的逐字代码:

int main() {
    constexpr const char* p1 = "asdf";
    constexpr const char* p2 = p1;      // OK
    constexpr const char* p3 = p1+2;    // error:  the compiler does not know the value of p1
}

这是错误的。 compiler/linker 确实知道 p1 的值,并且可以在 link 时确定 p1+2 的值。它编译得很好。

我的 "The C++ Programming Language (4th Edition)" 硬拷贝中提供的第 10.4.5 节中的示例似乎不正确。所以我得出结论,局部变量的地址不是 constexpr.

该示例似乎已在某些 pdf 版本中更新,如下所示:

只是为了补充指出错误的其他答案,C++ 标准只允许 constexpr 指针指向 静态存储持续时间 的对象,一个超过这样的结束,或者nullptr。具体见[expr.const/8]#8.2

值得注意的是:

  • 字符串文字具有静态存储持续时间:
  • 基于声明 extern 变量的约束,它们将固有地具有 静态存储持续时间 线程本地存储持续时间 .

因此这是有效的:

#include <string>

extern char glob;
std::string boom = "Haha";

void f(char loc) {
    constexpr const char* p1 = &glob;
    constexpr std::string* p2 = nullptr;
    constexpr std::string* p3 = &boom;
}

这个答案试图通过分析 x86-64 体系结构的示例来阐明为什么局部变量的地址不能 constexpr

考虑以下玩具函数 print_addr(),它显示其局部变量的地址 local_var 并递归调用自身 n 次:

void print_addr(int n) {
   int local_var{};
   std::cout << n << " " << &local_var << '\n';

   if (!n)
      return; // base case

   print_addr(n-1);  // recursive case
}

print_addr(2) 的调用在我的 x86-64 系统上产生了以下输出:

2 0x7ffd89e2cd8c
1 0x7ffd89e2cd5c
0 0x7ffd89e2cd2c

可以看到,每次调用print_addr()local_var对应的地址都不一样。也可以看到函数调用越深,局部变量的地址越低local_var。这是因为堆栈在 x86-64 平台上向下增长(即从高地址到低地址)。

对于上面的输出,call stack 在 x86-64 平台上看起来像下面这样:

                |     . . .     |
Highest address ----------------- <-- call to print_addr(2) 
                | print_addr(2) |    
                -----------------
                | print_addr(1) |
                -----------------
                | print_addr(0) | <-- base case, end of recursion
Lowest address  ----------------- Top of the stack

上面的每个矩形代表每次调用 print_addr()stack frame。每个调用的 local_var 位于其对应的堆栈帧中。由于对 print_addr() 的每次调用的 local_var 位于其自己的(不同的)堆栈帧中,因此 local_var 的地址不同。

综上所述,由于函数中局部变量的地址在每次调用函数时可能都不相同(即,每次调用的堆栈帧可能位于内存中的不同位置),因此这样的变量无法在编译时确定,因此不能限定为 constexpr.