如何通过聚合访问对象的存储

Ker*_* SB 13 c++ lvalue language-lawyer

在"Lvalues和rvalues",[basic.lval](3.10)中,C++标准包含一个类型列表,使得通过这种类型的glvalue"访问对象的存储值"是有效的(第10段) .具体来说,它说:

如果程序试图通过以下类型之一以外的glvalue访问对象的存储值,则行为未定义:

  • 对象的动态类型,

  • [关于简历和签名/未签名的一些不重要的细节]

  • 聚合或联合类型,包括其元素或非静态数据成员中的上述类型之一(递归地,包括子聚合或包含联合的元素或非静态数据成员),

  • [更多东西]

"聚合"规则到底意味着什么?如何通过某些常规聚合类型的glvalue访问对象的存储值?!

我想象的是这样的:

int a = 10;                                      // my "stored value"

struct Foo { char x; float y; int z; bool w; };  // an aggregate

reinterpret_cast<Foo&>(a).y = 0;                 // ???
Run Code Online (Sandbox Code Playgroud)

最终的强制转换是否会产生"包含动态类型的聚合类型"的glvalue a,从而使其有效?

Joe*_*e Z 7

该列表的目的不是为您提供访问对象的替代方法,而是作为列表的脚注指示,列出对象可能具有别名的所有方式.请考虑以下示例:

struct foo
{
    char x; 
    float y; 
    int z; 
    bool w;
};

void func( foo &F, int &I, double &D )
{
    //...
}
Run Code Online (Sandbox Code Playgroud)

该列表所说的是访问F也可以访问与访问相同的底层对象I.如果您将引用传递给F.zin for I,可能会发生这种情况,如下所示:

func(F, F.z, D); 
Run Code Online (Sandbox Code Playgroud)

另一方面,您可以安全地假设无法访问F相同的底层对象D,因为struct foo它不包含任何类型的成员double.

即使一些小丑这样做也是如此:

union onion
{
    struct foo F;
    double D;
};

onion o; 
int i;

func( o.F, i, o.D );  // [class.union] (9.5) wants a word with you.  UB.
Run Code Online (Sandbox Code Playgroud)

我不确定这union是你问题的核心.但是union示例之前的部分突出了聚合规则存在的原因.

现在让我们考虑你的例子: reinterpret_cast<Foo&>(a).y = 0; [expr.reinterpret.cast](5.2.10),第11段有这样的说法:

如果类型"指向" 的表达式可以使用a 显式转换为"指向" 的类型,则T1可以将类型的左值表达式强制转换为"引用 T2"类型.也就是说,引用转换与使用内置和 运算符(以及类似地)的转换具有相同的效果.结果引用与源左值相同的对象,但具有不同的类型.结果是左值引用类型的左值或函数类型的右值引用,以及对象类型的右值引用的x值.没有创建临时,没有复制,并且不调用构造函数(12.1)或转换函数(12.3).71T1T2reinterpret_castreinterpret_cast<T&>(x)*reinterpret_cast<T*>(&x)&*reinterpret_cast<T&&>(x)


71这有时被称为类型双关语.

在您的示例的上下文中,它表示如果将指针转换为指针int指向是合法的Foo,那么您reinterpret_cast<Foo&)(a)的合法并产生左值.(第1段告诉我们它将是一个左值.)而且,正如我所读到的那样,根据第7段,指针转换本身就可以了:

指向对象的指针可以显式转换为指向不同对象类型的指针.当prvalue v类型的"指针T1"被转换为类型"指针CV T2 ",结果是static_cast<cv T2*>(static_cast<cv void*>(v))如果两个T1和T2是标准布局类型(3.9)和的对准要求T2并不比那些更严格T1.将"指向T1"的类型的prvalue转换为"指向"的类型T2(其中T1和T2是对象类型,并且对齐要求T2不比那些更严格T1)并返回其原始类型,产生原始指针值.未指定任何其他此类指针转换的结果.

您具有兼容对齐约束的标准布局类型.所以,你所拥有的是一个产生左值的类型双关语.您列出的规则不会对自己让它不确定的.

那么是什么可能使它未定义?好吧,对于一个,[class.mem](9.2)第21段提醒我们,指向标准布局结构对象的指针指向其初始成员,反之亦然.因此,在你的类型双关语之后,你留下了一个引用Foo,这就是它Foo的x位置a.

而且......这是我的语言律师逐渐消失的地方.我知道,在我的内心在访问Foo通过弗兰肯参考是最好的实现定义或指定.我无法找到明确放逐到未定义行为领域的地方.

但是,我想我回答了你原来的问题:为什么汇总规则存在?它为您提供了一种非常基本的方法来规划潜在的别名而无需进一步的指针分析.


Die*_*ühl 2

该子句的 item 仅指对任何聚合( 、 或数组)的成员的正常访问struct或class:union您需要能够访问对象的存储值而不会导致未定义的行为。该条款仅规定了必要条件:至少其中一项必须为真。它没有规定充分的条件,即除了这些条件之外,还可能需要满足其他条件。