Rus*_*lan 17 c++ initialization undefined-behavior language-lawyer
请考虑以下代码:
#include <iostream>
struct Data
{
int x, y;
};
Data fill(Data& data)
{
data.x=3;
data.y=6;
return data;
}
int main()
{
Data d=fill(d);
std::cout << "x=" << d.x << ", y=" << d.y << "\n";
}
Run Code Online (Sandbox Code Playgroud)
这d是从返回值复制初始化的fill(),但在返回结果之前fill()写入d自身.我关心的是d在初始化之前非常简单地使用它,并且在一些(所有?)情况下使用未初始化的变量会导致不确定的行为.
那么这段代码是有效的,还是有未定义的行为?如果它有效,一旦Data停止POD或在其他情况下,行为是否会变得不确定?
Sha*_*our 13
这似乎不是有效的代码.它类似于问题中概述的情况:将C++对象传递给自己的构造函数是否合法?,虽然在这种情况下代码是有效的.机制并不完全相同,但基本推理至少可以让我们开始.
我们从缺陷报告363开始,询问:
如果是这样,UDT的自我初始化的语义是什么?例如
Run Code Online (Sandbox Code Playgroud)#include <stdio.h> struct A { A() { printf("A::A() %p\n", this); } A(const A& a) { printf("A::A(const A&) %p %p\n", this, &a); } ~A() { printf("A::~A() %p\n", this); } }; int main() { A a=a; }可以编译和打印:
Run Code Online (Sandbox Code Playgroud)A::A(const A&) 0253FDD8 0253FDD8 A::~A() 0253FDD8
拟议的决议是:
3.8 [basic.life]第6段表明此处的引用是有效的.允许在完全初始化之前获取类对象的地址,并且只要引用可以直接绑定,就允许将它作为参数传递给引用参数.[...]
因此,尽管d未完全初始化,我们可以将其作为参考传递.
我们开始遇到麻烦的地方在这里:
data.x=3;
Run Code Online (Sandbox Code Playgroud)
草案C++标准部分3.8(缺陷报告引用的相同部分和段落)说(强调我的):
类似地,在对象的生命周期开始之前但在对象将占用的存储已经被分配之后,或者在对象的生命周期结束之后并且在对象占用的存储被重用或释放之前,任何引用的glvalue之前可以使用原始对象,但仅限于有限的方式.对于正在建造或销毁的物体,见12.7.否则,这样的glvalue指的是已分配的存储(3.7.4.2),并且使用不依赖于其值的glvalue的属性是明确定义的.如果出现以下情况,该程
左值到右值的转换(4.1)应用于这样的glvalue,
glvalue用于访问非静态数据成员或调用对象的非静态成员函数,或
glvalue绑定到对虚基类(8.5.3)的引用,或
glvalue用作dynamic_cast(5.2.7)的操作数或typeid的操作数.
那么访问意味着什么?通过缺陷报告1531澄清了这一点,该报告将访问定义为:
访问
读取或修改对象的值
因此fill 访问非静态数据成员,因此我们有未定义的行为.
这也符合以下部分12.7:
[...]要形成指向对象obj的直接非静态成员(或访问其值)的指针,obj的构造应该已经开始并且其销毁不应该已经完成,否则计算指针值(或访问成员值)导致未定义的行为.
因为您无论如何都在使用副本,所以您也可以在其中创建一个Data实例fill并对其进行初始化.你不必通过d.
正如TC指出的那样,明确引用生命周期开始时的详细信息非常重要.来自部分3.8:
对象的生命周期是对象的运行时属性.如果一个对象属于类或聚合类型,并且它或其成员之一由除了普通默认构造函数之外的构造函数初始化,则称该对象具有非平凡的初始化.[注意:通过简单的复制/移动构造函数进行初始化是非平凡的初始化. - 结束注释]类型T对象的生命周期从以下开始:
获得具有适当对齐和T型尺寸的存储,并且
如果对象具有非平凡的初始化,则其初始化完成.
由于我们通过复制构造函数初始化,因此初始化非常重要.
我不认为有什么问题。访问未初始化的整数成员是有效的,因为您访问的目的是为了写入。阅读它们会导致UB。
| 归档时间: |
|
| 查看次数: |
945 次 |
| 最近记录: |