【问题标题】:Is the address of a local variable a constexpr?局部变量的地址是 constexpr 吗?
【发布时间】:2019-09-06 00:08:32
【问题描述】:

在 Bjarne Stroustrup 的书“C++ 编程语言(第 4 版)”中,第 1 页。 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 的示例是否还有其他内容?

【问题讨论】:

  • 显然,&loc 不能是 constexpr。但是,这些代码行并没有出现在我的 kindle 版本中。他确实在本地函数中显示了“C”样式字符串的地址的 constexpr。这是合法的,因为它们在全局空间中,而 loc 是堆栈上的参数而不是常量。你指的是那个例子吗?
  • char loc 是本地声明的非静态字符。下次调用f() 时,不能保证loc 将具有相同的地址。 10.4.5 对链接器分配的地址和编译器分配的地址进行了区分。 2013 Stroustrup - The C++ Programming Language 4th Edition.pdf
  • @DavidC.Rankin 看起来您的版本(pdf)与我的版本(硬拷贝)不同。这一定是一个错误,并且在您的 pdf 创建时已更新。
  • 是的,我对&glob 的来源有点摸不着头脑,但&loc 是可识别的。
  • @DavidC.Rankin 好点,我应该在我的问题中说明这一点。但是,是的,我认为这是因为我们的版本不同

标签: c++ c++11 constexpr memory-address


【解决方案1】:

Bjarne Stroustrup 的书“C++ 编程语言(第 4 版)”的早期印刷版本,第 4 页。 267有OP的问题中概述的错误。当前的印刷和电子副本已被“纠正”,但引入了稍后描述的另一个错误。它现在引用以下代码:

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
}

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

【讨论】:

  • 所以,你是说 Bjarne 不应该说 &loc 会“OK”,对吧?
  • 我在问题中使用的示例代码是逐字记录的。看了@doug 的 pdf 后,我认为我拥有的硬拷贝书是不正确的。我认为这个错误在以后的版本中被更新了。
  • @jackw11111 我的版本与该链接中提供的 pdf 版本不同。 doug 提供了指向同一 pdf 的链接。我的硬拷贝和那个 pdf 显示了第 10.4.5 节的不同示例
  • 如果明确说明书中的例子是错误的,这个答案会清楚很多。
【解决方案2】:

我的“C++ 编程语言(第 4 版)”硬拷贝中提供的第 10.4.5 节中的示例似乎不正确。所以我得出结论,局部变量的地址不是constexpr

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

【讨论】:

  • 有趣的是,后来印刷中的“更正”样本也是不正确的。 p2+2 为编译器所知。
【解决方案3】:

此答案试图通过分析 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

如你所见,local_var 的对应地址对于print_addr() 的每次调用都是不同的。还可以看到,函数调用越深,局部变量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

【讨论】:

  • 很有用,因为它清楚地显示了局部变量地址是如何不固定的。
【解决方案4】:

只是为了添加指出错误的其他答案,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;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-06-22
    • 1970-01-01
    • 2018-01-15
    • 2015-10-25
    • 2021-04-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多