【问题标题】:Effective types of allocated objects and structs分配对象和结构的有效类型
【发布时间】:2019-06-24 02:31:21
【问题描述】:

从 c99 的规范中,我不太明白下面分配的对象的有效类型是怎么回事。

typedef struct {
    int x;
    char y;
} MyStruct ;

MyStruct *make_struct (void) {
    MyStruct *p = malloc(sizeof(MyStruct));
    p->x = 1;
    p->y = 2;

    /* what is the effective type of the allocated object at this point? */

    return p;
}

当你给一个已分配对象赋值时,分配对象的有效类型变成了用于存储的左值的类型,但是这里使用的左值是什么?

据我了解,从 6.5.2.3p4..​​.

后缀表达式后跟 -> 运算符和标识符指定结构或联合对象的成员。该值是第一个表达式指向的对象的命名成员的值,并且是一个左值。如果第一个表达式是指向限定类型​​的指针,则结果具有指定成员类型的限定版本。

..."x->y" 表达式的类型是 y 的类型(但仅当 x 指向限定类型​​时)。
那么我有一个没有有效类型的分配对象和两个类型为 int 和 char 的“内部对象”?

多么令人困惑..

编辑: 假设 *p 的有效类型以 int 结尾。那么这是未定义的行为吗?有人最终会通过 MyStruct 类型的左值访问该对象。访问成员是否也意味着访问聚合类型? 这一直在给..

【问题讨论】:

  • p 仍然是指向 MyStruct 类型的指针,x 是 int,y 是 char,如果这是你要问的。
  • 嗯,*p 有一个声明的类型。
  • 不,它没有; *p 是一个分配的对象,没有声明的类型。
  • @James:C 标准对对象的有效类型 有特定的规则,而不仅仅是使用什么类型的指针来访问它们。 malloc 返回 void * 的事实在这方面没有提供信息。
  • @James "4 后缀表达式后跟 -> 运算符和标识符指定结构或联合对象的成员。该值是该对象的命名成员的值第一个表达式指向,并且是一个左值。如果第一个表达式是一个指向限定类型​​的指针,则结果具有指定成员类型的限定版本。"

标签: c struct malloc language-lawyer c99


【解决方案1】:

报价来自 C99 6.5/6

用于访问其存储值的对象的有效类型是对象的声明类型,如果有的话。

  • malloc(sizeof(MyStruct));此时返回的数据没有有效类型。
  • MyStruct *p = malloc(sizeof(MyStruct)); 仍然没有有效类型,p 只是指向数据而不存储任何内容。
  • p->x = 1;有效类型规则:

    如果一个值通过 左值的类型不是字符类型,则左值的类型将成为该访问以及不修改存储值的后续访问的对象的有效类型。

    由于我们有int x;,表达式p->x = 1;的左值是int,它成为存储在p->x的有效类型。

  • p->y 的情况下,用于对象访问的左值是字符类型,因此上述规则不适用。它也不是作为字符数组复制的。我们以规则的最后一句话结束:

    对于没有声明类型的对象的所有其他访问,对象的有效类型只是用于访问的左值的类型。

    表示p->y 的有效类型变为char,因为表达式p->y = 2; 的左值是char

6.5.2.3/4 在这里没有相关性,除了“...and is an lvalue”。

*p 本身没有有效类型,因为我们从未通过完整的结构类型访问内存区域。但是,MyStruct m = *make_struct(); 这样的表达式仍然是明确定义的,因为严格的别名规则允许结构访问对象,因为结构包含与有效类型兼容的成员。在这种情况下,结构包含 intchar 成员,它们与通过 p->xp->y 引用的数据最终得到的有效类型完全兼容。

【讨论】:

  • 我认为“通过左值”很明显是指表达式“p->x”,而不是表示正在存储的值(也称为右值)的 1。
  • @zagortenay333 我可能确实误读了那部分。标准的这一部分写得非常糟糕。我会编辑。结果类型是相同的。
  • 认为我这次的语言律师是正确的。校对赞赏:)
  • 我看不出有效的类型最终会变成 char。如果“p->x”的类型是int,那么对于所有不修改的操作,有效类型仍然是int(因为“p->y”在这里不能改变任何东西。)但我不明白“p->x”的类型是什么;这对我来说完全不明显。
  • 另外,如果最终的有效类型是 int,那么上面的代码会调用未定义的行为,因为有人会使用一个将是 MyStruct 的左值访问一个具有有效类型 int 的对象。我的意思是,你甚至可以访问一个结构还是只访问它的成员?
【解决方案2】:

分配的块没有有效的类型,因为(1)它没有声明类型,(2)它没有被分配。与成员xy 对应的部分块具有有效类型,但不是整个块。

虽然没有有效类型并不构成未定义的行为:从make_struct 返回的MyStruct 的每个成员都已分别被赋予适当的有效类型,因此访问返回的struct 成员的代码仍然有效。

您的代码片段可以修改为使用compound literal 来初始化整个MyStruct,而不是初始化其组件。这将使分配块的有效类型MyStruct

MyStruct *make_struct () {
    MyStruct *p = malloc(sizeof(MyStruct));
    *p = (MyStruct){.x = 1, .y = 2};
    return p;
}

注意:在更新问题后,此答案已被大幅修改。

【讨论】:

    【解决方案3】:

    除了 6.5p6(“有效类型规则”)之外,C11 草案 (N1570) 中到处使用的术语“对象”是指与某些特定类型相关联的存储区域。如果int *p 是一个有效的非空指针,它将指向“指向”或“刚刚过去”,一个int 类型的对象。似乎 6.5p6 使用术语“对象”来指代某种类型的存储区域,可能实际上是对象,也可能不是对象,但标准的其余部分并未以这种方式使用术语“对象”。除此之外,malloc 的规范没有说它返回一个指向对象的指针,也没有说它创建一个对象,而是它返回一个指向足够大的存储区域的指针容纳给定大小的对象。

    因为 6.5p6 使用术语“对象”的方式与其在其他地方的用法相反,其含义将取决于人们选择如何定义该术语。在没有脚注 87(“分配的对象没有声明的类型。”)的情况下,可以通过简单地观察每个对象的有效类型就是它的类型来解决这个问题。如果将存储区域识别为包含所有可以放入其中的对象的叠加,这实际上会很好用,但解释了 6.5p7 的脚注 88(“此列表的目的是指定对象可能或可能的情况不能被别名。”)表示该规则适用的唯一“对象”是那些在与左值相同的上下文中使用而没有在该左值的派生中新鲜且明显使用的对象。

    然而,脚注 87 明确指出,6.5p6 必须使用与标准中其他所有内容不同的“对象”含义,但没有明确说明该含义是什么。我认为不可能制定一个合理地处理所有极端情况的定义,而且对于 6.5p6 或 6.5p7 的目的,标准的作者对于什么是或不是“对象”有一个共识的含义似乎值得怀疑.因此,6.5p6 和 6.5p7 的含义,以及基于它们允许的内容,将在很大程度上取决于读者如何选择隐瞒“对象”的含义。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-12
      • 2018-02-11
      • 2021-09-06
      • 1970-01-01
      • 2021-12-03
      • 1970-01-01
      相关资源
      最近更新 更多