【问题标题】:Why use array size 1 instead of pointer?为什么使用数组大小​​ 1 而不是指针?
【发布时间】:2011-09-17 10:24:56
【问题描述】:

在一个 C++ 开源项目中,我看到了这个。

struct SomeClass {
  ...
  size_t data_length;
  char data[1];
  ...
}

这样做而不是使用指针有什么好处?

struct SomeClass {
  ...
  size_t data_length;
  char* data;
  ...
}

我唯一能想到的是大小为 1 的数组版本,用户不应该看到 NULL。还有什么吗?

【问题讨论】:

  • 这比指针有很多潜在的好处,但我想不出一个简单的char
  • 这是可变长度结构的 ISO C90 习惯用法。你是对的,因为指针会做同样的事情,但是这个允许更舒适的访问。在 ISO C99 中,您将使用空括号。单元素数组也适用于 C++,我不确定空括号是否也适用(尽管 gcc 确实支持它)。
  • @larsman:如果你想要一个可变大小的数组,而不需要第二次内存分配的开销。
  • @larsmans:简单地说明标准中的某些内容(C99 中的灵活数组成员,或者在这种情况下最接近它们的 C89 或 C++ 近似值)“几乎没有好处”可能准确地代表您的意见,但可能没有帮助,除非你说明为什么它是错误的。我猜你可能会说,“它是 C++,使用向量”,响应是“与 C API 接口”,所以我的问题仍然是“为什么这在 C 中没有好处”,因此,“为什么灵活数组成员是 C99 的错误特征?”

标签: c++ c


【解决方案1】:

有了这个,你不必在别处分配内存并使指针指向它。

  • 没有额外的内存管理
  • 对内存的访问将更有可能(更多)命中内存缓存

诀窍是分配比sizeof (SomeClass) 更多的内存,并让SomeClass* 指向它。然后初始内存将由您的SomeClass 对象使用,剩余内存可由data 使用。也就是说,您可以说p->data[0],也可以说p->data[1] 等等,直到您分配的内存用完为止。

可以指出,这种使用会导致未定义的行为,因为您将数组声明为只有一个元素,但访问它时就好像它包含更多元素一样。但是真正的编译器确实允许这样做并具有预期的含义,因为 C++ 没有替代语法来表达这些方法(C99 有,它在那里被称为“灵活数组成员”)。

【讨论】:

  • @larsmans 因为不允许声明大小为零的数组 - char x[0]; 在 C++ 和 C 中无效,它需要诊断消息(错误/警告消息)。
  • 如果要相信 OP 的代码示例,那么我认为这个答案是不正确的。他在char data[1] 后面有“...”,表示后面还有更多字段。
  • @Oli 这是一个很好的观点,我没有注意到。尽管如果您考虑到提问者不知道特殊含义,他也不知道这仅对结构的最后一个成员有意义,因此他可能只是在两者处都插入了“...”结束以保持对称。
  • @Steve 因为指针指向char[1] 类型数组的元素。所以从技术上讲,你只能说data[0]data[1]。但是data[2] 是未定义的行为(不一定是因为取消引用,而是因为data + 2 是未定义的行为)。但在实践中,我认为这并不重要,所以因为允许它实际上是有用的,所以真正的编译器确实允许它。
  • @Steve 不幸的是,规范不清楚 char 数组何时开始生命周期以及何时停止。规范说,一旦你有适当的记忆,非类类型就会开始生命周期。但这不可能是真的,因为那样别名规则将无法工作。规范尚未修复,所以我会假设最坏的情况:int *x = (int*)malloc(2 * sizeof(int)); x + 2; 是当前定义的行为,C++ 标准没有指定。如果您知道它在哪里以及它如何与别名一起使用,如果您能解释一下,我会很高兴。
【解决方案2】:

通常您将其视为结构的最终成员。那么无论是谁mallocs 结构体,都会将内存中的所有数据字节连续分配为一个块来“跟随”结构体。

所以如果你需要 16 字节的数据,你应该像这样分配一个实例:

SomeClass * pObj = malloc(sizeof(SomeClass) + (16 - 1));

然后你可以像访问数组一样访问数据:

pObj->data[12] = 0xAB;

当然,你也可以一键释放所有东西。

data 成员按照惯例是一个单项数组,因为旧的 C 编译器(显然是当前的 C++ 标准)不允许零大小的数组。很好的进一步讨论:http://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html

【讨论】:

  • 这被称为(甚至在 C 标准的索引中)struct hack。
  • 并且此类代码的行为是未定义 - C 编译器可以进行范围检查
【解决方案3】:

这通常是避免多次内存分配和释放的一种快速(而且很脏?)的方法,尽管它比 C++ 更时尚。

也就是说,而不是这个:

struct SomeClass *foo = malloc(sizeof *foo);
foo->data = malloc(data_len);
memcpy(foo->data,data,data_len);

....
free(foo->data);
free(foo);

你做这样的事情:

struct SomeClass *foo = malloc(sizeof *foo + data_len);
memcpy(foo->data,data,data_len);

...
free(foo);

除了节省(取消)分配调用之外,这还可以节省一些内存,因为没有指针空间,您甚至可以使用原本可能是结构填充的空间。

【讨论】:

    【解决方案4】:

    这个特殊的东西背后的想法是data 的其余部分直接在结构之后放入内存中。当然,你也可以这样做。

    【讨论】:

      【解决方案5】:

      在您的示例中,它们在语义上有所不同。

      char data[1] 是一个有效的 char 数组,在堆栈上分配了一个未初始化的元素。你可以写data[0] = 'w',你的程序是正确的。

      char* data; 只是声明了一个指针,该指针在初始化为指向有效地址之前是无效的。

      【讨论】:

      • 我不确定这是如何被忽略的。指针和字符不是一回事。
      【解决方案6】:
      1. 结构可以简单地分配为单个内存块,而不是必须释放的多个分配。

      2. 它实际上使用的内存更少,因为它不需要存储指针本身。

      3. 由于内存是连续的,缓存还可能具有性能优势。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-02-09
        • 2019-11-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多