C++17:编译器为(静态存储持续时间)const 引用绑定(bind)创建的临时对象(和存储)是否可修改?

标签 c++ constants language-lawyer c++17 temporary-objects

让我们编译下面的顶级声明

const int& ri = 5;

clang++ .与 -std=c++14在它下面将临时对象(和代表引用的指针)放入 .rodata 中部分:

        .type   _ZGR2ri_,@object        # @_ZGR2ri_
        .section        .rodata,"a",@progbits
        .p2align        2
_ZGR2ri_:
        .long   5                       # 0x5
        .size   _ZGR2ri_, 4

        .type   ri,@object              # @ri
        .globl  ri
        .p2align        3
ri:
        .quad   _ZGR2ri_
        .size   ri, 8

但是如果我们把标准版改成-std=c++17 (或以上),对象将被放入 .data部分(尽管指针仍在 .rodata 中):

        .type   _ZGR2ri_,@object        # @_ZGR2ri_
        .data
        .p2align        2
_ZGR2ri_:
        .long   5                       # 0x5
        .size   _ZGR2ri_, 4

        .type   ri,@object              # @ri
        .section        .rodata,"a",@progbits
        .globl  ri
        .p2align        3
ri:
        .quad   _ZGR2ri_
        .size   ri, 8

这种行为的原因是什么?这是一个错误吗?事实上,它仍然取代了 ri 的所有用途在同一 TU 中按其初始值 5表明这是一个错误。

我的假设是 [dcl.init.ref]/5.2

If the converted initializer is a prvalue, its type T4 is adjusted to type “cv1 T4” ([conv.qual]) and the temporary materialization conversion is applied.

它天真地丢弃(或者更确切地说不添加)cv1 - 来自(到)纯右值类型的限定符。

有趣的是,如果用非引用相关但可转换类型的纯右值替换初始化表达式

const int& ri = 5.0;

它开始放置值为5 的对象进入.rodata再次节。

现在标准中是否有任何内容需要这种可变性?换句话说:

  • 它是ri指定的对象可以通过符合代码修改吗? (显然,涉及 UB 的代码可能会尝试更改它,并且编译器不需要做出努力来允许这样做)
  • 是可通过符合代码修改的对象的存储,通过重新使用它来创建另一个大小不大于 ri 临时“别名”(“引用是别名”)大小的对象那是 sizeof (int)

最佳答案

分析一下

const int& ri = 5;

来自 C++ 草案:引用的初始化 [dcl.init.ref]/5

A reference to type “cv1 T1” is initialized by an expression of type “cv2 T2” as follows:

这里cv1 = const, T1 = int, cv2 = "", T2 = int

跳过不适用的条款,我们来到这里[dcl.init.ref]/5.3 :

Otherwise, if the initializer expression (5.3.1) is an rvalue (but not a bit-field) (...) and “cv1 T1” is reference-compatible with “cv2 T2”, or (...) then the value of the initializer expression (...) is called the converted initializer.

转换后的初始值是 5 个纯右值。

If the converted initializer is a prvalue, its type T4 is adjusted to type “cv1 T4” ([conv.qual]) and the temporary materialization conversion ([conv.rval]) is applied. In any case, the reference is bound to the resulting glvalue (...)

cv1 T4 = const int

因此创建了一个 const int 类型的对象并将引用绑定(bind)到它。

“临时物化转换”是此处解释的新概念 [conv.rval] :

A prvalue of type T can be converted to an xvalue of type T. This conversion initializes a temporary object ([class.temporary]) of type T from the prvalue by evaluating the prvalue with the temporary object as its result object, and produces an xvalue denoting the temporary object. T shall be a complete type.

所以我们有一个转换 prvalue -> xvalue -> lvalue。

临时的生命周期在[class.temporary]/6中描述。 :

The temporary object to which the reference is bound or (...) persists for the lifetime of the reference if the glvalue to which the reference is bound was obtained through one of the following:

(6.1) a temporary materialization conversion ([conv.rval]), (...)

情况就是这样,临时对象的生命周期“在引用的生命周期内持续存在”

[basic.life]/5

A program may end the lifetime of any object by reusing the storage which the object occupies

但不是每个对象存储都可以这样使用:[basic.memobj]/10

Creating a new object within the storage that a const complete object with static, thread, or automatic storage duration occupies, or within the storage that such a const object used to occupy before its lifetime ended, results in undefined behavior.

存储期限在这里定义[basic.stc]

The storage duration is the property of an object that defines the minimum potential lifetime of the storage containing the object. The storage duration is determined by the construct used to create the object and is one of the following: (1.1) static storage duration (1.2) thread storage duration (1.3) automatic storage duration (1.4) dynamic storage duration 2 Static, thread, and automatic storage durations are associated with objects introduced by declarations ([basic.def]) and implicitly created by the implementation.

但是文本只提到了变量,没有提到对象。我没有看到临时文件的存储期限在哪里定义!

编辑: @LanguageLawyer 指出了这个核心缺陷:

1634. Temporary storage duration

The apparent intent of the reference to 15.2 [class.temporary] is that a temporary whose lifetime is extended to be that of a reference with one of those storage durations is considered also to have that storage duration.

(...) the specification of lifetime extension of temporaries (also in 15.2 [class.temporary] paragraph 5) does not say anything about storage duration. Also, nothing is said in either of these locations about the storage duration of a temporary whose lifetime is not extended.

所以规范中确实有遗漏的部分;实现创建的这些对象的生命周期没有明确规定。 C++ 中的生命周期规范很困难,正如您从更新的标准中对生命周期、 union 、子对象和“嵌套”的规范中添加的许多内容所看到的那样;其中一些新条款甚至适用于不使用新 C++ 功能的代码,这些代码旨在在 ARM 的准标准时代得到支持(但没有很好地描述),例如代码只做改变“union 的活跃成员”。

如果按照 DR 声明的方式解释规范,则 const int 临时值 5 的生命周期将具有静态存储持续时间; 其内存不可合法修改,可以放在只读部分

(另一种解决方案:委员会还可以为临时对象创建一个特定的存储类。)

关于C++17:编译器为(静态存储持续时间)const 引用绑定(bind)创建的临时对象(和存储)是否可修改?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54413490/

相关文章:

c++ - 在 Linux/Windows 上使用 c++ 的 GUI 应用程序

c++11 - 试图将 const 作为非常量传递的 map 上的 remove_if - 为什么?

c++ - x = std::move(x) 未定义吗?

c++ - reinterpret_cast 指向 void(*&)() 的成员函数指针

C++:数组的递归函数

c++ - 迭代 unordered_map 时修改/删除条目的简洁方法?

c++ - CArray 和 const 模板参数

c++ - constexpr 函数中的嵌套结构,在 clang 中编译,在 gcc 中失败

c++ - 为什么方法不起作用?

ruby-on-rails - Rails中的常量哈希的国际化3