【问题标题】:Why can't I retrieve my flexible array member size?为什么我无法检索我的灵活数组成员大小?
【发布时间】:2016-07-28 16:59:53
【问题描述】:

好的,所以我正在阅读标准论文 (ISO C11) 中解释灵活数组成员的部分(在 6.7.2.1 p18)。它是这样说的:

作为一种特殊情况,结构的最后一个元素具有多个 命名成员可能具有不完整的数组类型;这被称为 灵活的数组成员。在大多数情况下,灵活的数组成员 被忽略。特别是,结构的大小就像 灵活的数组成员被省略了,除了它可能有更多 比遗漏所暗示的尾随填充。但是,当.(或 ->) 运算符的左操作数是(指向)具有灵活数组成员的结构,右操作数命名该成员, 它的行为就好像该成员被替换为最长的数组 (具有相同的元素类型)不会使结构变大 比被访问的对象;数组的偏移量应保持 灵活数组成员的那个,即使这与那个不同 的替换阵列。如果这个数组没有元素,它 表现得好像它只有一个元素,但如果有的话,行为是未定义的 尝试访问该元素或生成指针 one 过去了。

下面是一些示例(p20):

示例 2 声明后:


     struct s { int n; double d[]; };
结构 struct s 有一个灵活的数组成员 d。一种典型的方式 使用这个是:

     int m = /* some value */;

struct s *p = malloc(sizeof (struct s) + sizeof (double [m]));
并假设对 malloc 的调用成功,则指向的对象 在大多数情况下,by p 的行为就像 p 已被声明为:

     struct { int n; double d[m]; } *p;
(在某些情况下,这种等价性被打破;在 特别是成员 d 的偏移量可能不同)。

标准中添加的剧透示例不是文档。

现在是我的例子(从标准扩展):

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    struct s { int n; double d[]; };

    int m = 7;

    struct s *p = malloc(sizeof (struct s) + sizeof (double [m])); //create our object

    printf("%zu", sizeof(p->d)); //retrieve the size of the flexible array member

    free(p); //free out object
}

在线example.

现在编译器抱怨 p-&gt;d 的类型不完整 double[] 根据标准文件显然不是这种情况。这是 GCC 编译器的错误吗?

【问题讨论】:

  • 这很有趣,但我没有看到编译器真正知道这个大小的干净方法。
  • @EugeneSh。好吧,根据标准,它似乎应该这样做。
  • 标准明确规定sizeof运算符的操作数必须是完整类型的对象。
  • @CisNOTthatGOODbutISOisTHATBAD;它在哪里说它有完整的类型?
  • @hacks 它的行为就像该成员被替换为最长的数组(具有相同的元素类型),不会使结构大于被访问的对象 - 它可以理解为表现为一个完整的类型

标签: c arrays language-lawyer sizeof c11


【解决方案1】:

作为一种特殊情况,具有多个命名成员的结构的最后一个元素可能具有不完整的数组类型; ... C11dr 6.7.2.1 18

在下面d是一个不完整的类型。

struct s { int n; double d[]; };

sizeof 运算符不应应用于具有函数类型或不完整类型的表达式... C11dr §6.5.3.4 1

// This does not change the type of field `m`.
// It (that is `d`) behaves like a `double d[m]`, but it is still an incomplete type.
struct s *p = foo();

// UB
printf("%zu", sizeof(p->d));

【讨论】:

  • 但是如果该成员被替换为不会使结构大于被访问对象的最长数组(具有相同元素类型),则第二个引用似乎不适用,有效地使它成为一个完整的类型。代码中的注释应该有一些支持..
  • @Eugene Sh.是的,d 字段的行为就像该成员被替换为最长的数组一样......。这种行为并没有改变事实仍然是 不完整的数组类型
  • 好的...什么是“行为”?对我来说,如果 A 的行为与 B 一样,那么对于外部观察者来说,AB 无法区分。
  • @Eugene Sh.关于“代码中的注释应该有一些支持”。我反过来看:字段类型是指定的不完整数组类型。没有说明它改变类型的规范 - 只有在某些条件下的行为是详细的。
  • 但如果d 是灵活的或固定的,代码sizeof(p-&gt;d) 具有不同的行为
【解决方案2】:

这看起来像是标准中的一个缺陷。我们可以从灵活数组成员被标准化的论文N791 "Solving the struct hack problem" 中看到,结构定义替换仅适用于评估上下文(借用 C++ 术语);我的重点:

当一个左值的类型是一个结构 具有灵活的数组成员用于访问对象,它的行为类似于 如果该成员被最长的数组替换 结构大于被访问的对象。

比较最终的标准语言:

[当].(或-&gt;)运算符有一个左操作数(指向)具有灵活数组成员的结构并且右操作数命名该成员时,它的行为就像该成员一样被替换为最长的数组(具有相同的 元素类型)不会使结构大于被访问的对象 [...]

某种形式的语言,如“.(或-&gt;)运算符左操作数是(指向)具有灵活数组成员的结构和正确的操作数名称该成员被评估 [...]”似乎可以解决它。

(请注意,sizeof 不会评估其参数,但可变长度数组除外,这是另一锅鱼。)

没有对应的defect report 通过JTC1/SC22/WG14 website 可见。您可以考虑通过您的 ISO 国家成员机构提交缺陷报告,或要求您的供应商这样做。

【讨论】:

  • 一个从未被解决的更广泛的问题是没有一致的“对象”定义来充分描述分配存储的行为,这是灵活数组成员可用的唯一上下文。鉴于委员会尚未设法提出符合现代解释的别名规则的“对象”定义,相比之下,灵活数组成员的问题似乎微不足道。
【解决方案3】:

标准说:

C11-§6.5.3.4/2

sizeof 运算符产生其操作数的大小(以字节为单位),它可以是表达式或带括号的类型名称。 大小由操作数的类型决定

它还说
C11-§6.5.3.4/1

sizeof 运算符不得应用于具有函数类型或不完整类型的表达式,[...]

p-&gt;d 是不完整类型,不能是sizeof 运算符的操作数。声明

它的行为就像该成员被替换为最长的数组(具有相同的元素类型),不会使结构大于被访问的对象

不适用于sizeof 运算符,因为它通过必须是完整类型的对象类型确定对象的大小。

【讨论】:

    【解决方案4】:

    首先,就标准而言,发生的事情是正确的,声明为[] 的数组是不完整的,您不能使用sizeof 运算符。

    但你的情况也有一个简单的原因。您从未告诉您的编译器,在这种特殊情况下,d 成员应该被视为特定大小。您只告诉malloc 要保留的总内存大小并放置p 指向它。编译器没有获得可以帮助他推断数组大小的类型信息。

    这与分配可变长度数组 (VLA) 或指向 VLA 的指针不同:

     double (*q)[m] = malloc(sizeof(double[m]));
    

    这里编译器可以知道q指向的是什么类型的数组。但不是因为您告诉malloc 总大小(该信息不是从malloc 调用返回的)而是因为mq 类型规范的一部分。

    【讨论】:

      【解决方案5】:

      当涉及到在某些上下文中某些术语的定义时,C 标准有点松散。给定类似的东西:

      struct foo {uint32_t x; uint16_t y[]; };
      char *p = 1024+(char*)malloc(1024);  // Point to end of region
      struct foo *q1 = (struct foo *)(p -= 512); // Allocate some space from it
      ... some code which uses *q1
      struct foo *q2 = (struct foo *)(p -= 512); // Allocate more space from it
      

      没有真正明确的指示对象占用了哪些存储空间 *q1 或 *q2,也不是 q1->y 或 q2->y。如果之后永远不会访问 *q1, 那么 q2->y 可能会被视为 uint16_t[509],但写入 *q1 会造成垃圾 q2->y[254] 及以上的内容,写 q2->y[254] 及以上将 垃圾*q1。因为编译器通常无法知道会发生什么 将来发生*q1,它将无法明智地报告大小 对于 q2->y。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-10-27
        • 1970-01-01
        • 1970-01-01
        • 2015-03-07
        • 2020-07-14
        • 1970-01-01
        • 2019-12-30
        • 1970-01-01
        相关资源
        最近更新 更多