是否允许打印悬空引用的地址



考虑一下这个代码,它从这里稍微修改了一下:

#include <iostream>
void foo() {
int i;
static auto f = [&i]() { std::cout << &i << "n";};
f();
}
int main() {
foo();
foo();
}

lambdaf仅在第一次调用时初始化,在第二次调用期间,捕获的变量不再存在,lambda保留一个悬空引用,但只打印其地址。gcc没有明显的问题,输出看起来不错:

0x7ffc25301ddc
0x7ffc25301ddc

获取悬挂引用的地址是未定义的行为,还是可以?

对于一个非常相似的例子,gcc(-Wall -Werror -pedantic -O3)产生一个警告:

#include <iostream>
auto bar() {
int i;
return [&i]() {std::cout << &i << "n"; };
}
int main() {
bar()();
}

警告:

source>:5:14: error: address of stack memory associated with local variable 'i' returned [-Werror,-Wreturn-stack-address]
return [&i]() {std::cout << &i << "n"; };
^
<source>:5:14: note: captured by reference here
return [&i]() {std::cout << &i << "n"; };

当然,gcc编译第一个例子并产生预期的(?)输出,同时警告第二个例子,这并不意味着什么。在标准中,我可以在哪里找到使用悬挂引用的地址是否合适?

附言:我想答案在[基本生活]的某个地方,尽管我浏览了好几次,但我很难看到什么适用,它试图告诉我什么。

我认为这一点规定得很差,但可能是由实现定义的。

该问题和另一个答案假定i是一个悬空引用。这就假定它根本就是一个参考。但这是不对的!

值得注意的是,引用捕获不是引用。该标准明确而有意地指出,引用捕获可能不会导致闭包类型的非静态数据成员。[expr.prim.lambda/12]:

未指定是否在通过引用捕获的实体的闭包类型中声明其他未命名的非静态数据成员。

这就是为什么实体名称的重写只发生在复制捕获时。[expr.prim.lambda/11]:

lambda表达式复合语句中的每一个id表达式

引用捕获的情况并非如此。lambda主体内的id表达式i引用原始实体。正如人们可能合理地假设的那样,它不是充当int&的闭包类型的非静态成员。

据我所知,这可以追溯到C++11之前N2927中的一些重新编码。在此之前,在标准化过程中,引用捕获显然会导致闭包类型成员,并像副本捕获一样触发主体中的重写。这种改变是有意的。

所以。。。lambda主体命名类型为int的对象i,该对象在第二次调用时不仅在其生存期之外,而且存储也已释放。

考虑到这一点,让我们试着推断一下是否可以。

该标准明确允许在使用寿命之外但在存储重复使用之前使用该名称。[基本寿命/7]:

在对象的生存期结束后,在对象占用的存储被重用或释放之前,可以使用任何引用原始对象的glvalue,但只能以有限的方式使用。对于正在构建或销毁的对象,请参见[class.cdtor]。否则,这样的glvalue指的是已分配的存储([basic.stc.dynamic.allocation]),并且使用glvalue的不依赖于其值的属性是定义良好的。

这实际上并不适用,因为这里释放了存储。然而,当存储没有被释放时,你可以推断委员会通常打算命名不依赖于它的值的实体是可以的。在实践中,大多数情况下,避免左值到右值的转换。

该标准还明确地使存储版本上的指针无效。【basic.stc.general/4】:

当到达存储区域的持续时间结束时,表示该存储区域任何部分地址的所有指针的值都将变为无效指针值。通过无效指针值的间接操作和将无效指针值传递给释放函数具有未定义的行为。对无效指针值的任何其他使用都具有实现定义的行为。

我们没有指针。值得注意的是,参考文献并不是";zapped";,但我们也没有参考资料。

那么,我们该如何把这些放在一起呢?

单独命名i是个问题吗?明确允许在i的生存期之后但在存储发布之前对其进行命名。我找不到任何禁止在存储释放后命名i的规定。它必须引用同一个对象,该对象在其生存期之外。换句话说,规则说i是表示某个对象的左值,并且还说它在对象生存期之后继续。他们没有说它在存储释放时停止。

使用而不是访问i是否有问题?通过获取地址,我们不会触发左值到右值的转换;访问";i。我找不到禁令。地址运算符([expr.unary.op/3])表示,它将返回指定对象的地址,即左值命名的对象。

&i的结果是什么?关于指针转换的语言可以理解为,结果(表示已释放的存储地址的指针)必须是无效的指针值。

我们可以打印&i吗?关于无效指针值的语言很清楚,间接寻址和释放是未定义的,但其他一切都是由实现定义的。

所以。。。它可以是实现定义的。

是的,这是未定义的行为,因为将运算符的&地址应用于引用会检索被引用对象的地址,在您的示例中,该对象已不存在,因为它已超出范围并被销毁。您不能获取不存在的对象的地址。

相关内容

  • 没有找到相关文章