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

How to access an object's storage through an aggregate

本文关键字:对象 存储 访问 何通过      更新时间:2023-10-16

在"Lvalues and 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;                 // ???

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

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

struct foo
{
    char x; 
    float y; 
    int z; 
    bool w;
};
void func( foo &F, int &I, double &D )
{
    //...
}

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

func(F, F.z, D); 

另一方面,您可以安全地假设无法访问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.

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

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

类型

T1 的左值表达式可以转换为类型"引用 T2 "如果类型的表达式"指向T1的指针"可以显式 使用reinterpret_cast转换为类型"指向T2的指针"。那 是,引用强制转换reinterpret_cast<T&>(x)具有与 转换*reinterpret_cast<T*>(&x)内置&* 运算符(对于reinterpret_cast<T&&>(x)也是如此(。结果 引用与源左值相同的对象,但具有不同的 类型。结果是左值引用类型的左值或 对函数类型的 R 值引用和 R 值的 xValue 对对象类型的引用。没有临时是 已创建,不创建副本,构造函数 不调用 (12.1( 或转换函数 (12.3(。71


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

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

指向

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

您具有具有兼容对齐约束的标准布局类型。 所以,你有一个产生左值的类型双关语。 您列出的规则本身不会使其未定义。

那么,是什么可能使它未定义呢? 首先,[class.mem] (9.2( 第 21 段提醒我们,指向标准布局结构对象的指针指向其初始成员,反之亦然。 因此,在键入双关语之后,您留下了对Foo的引用,这样Fooxa位于同一位置。

而且......这就是我的语言律师逐渐消失的地方。 我知道,通过该弗兰肯引用访问Foo充其量是已定义或未指定的实现。 我找不到它在哪里被明确放逐到未定义行为的领域。

但是,我想我回答了你最初的问题:为什么聚合规则在那里? 它为您提供了一种非常基本的方式来裁定潜在的别名,而无需进一步的指针分析。

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