【问题标题】:C struct hack at workC struct hack 在工作中
【发布时间】:2013-05-14 21:59:31
【问题描述】:

这是如何使用 C struct hack 时分配的“额外”内存?

问题:

我在下面有一个 C struct hack 实现。我的问题是如何使用我分配给黑客的“额外”内存。有人可以给我一个使用额外内存的例子吗?

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

int main()
{

    struct mystruct {

        int len;
        char chararray[1];

    };

    struct mystruct *ptr = malloc(sizeof(struct mystruct) + 10 - 1);
    ptr->len=10;


    ptr->chararray[0] = 'a';
    ptr->chararray[1] = 'b';
    ptr->chararray[2] = 'c';
    ptr->chararray[3] = 'd';
    ptr->chararray[4] = 'e';
    ptr->chararray[5] = 'f';
    ptr->chararray[6] = 'g';
    ptr->chararray[7] = 'h';
    ptr->chararray[8] = 'i';
    ptr->chararray[9] = 'j';


}

【问题讨论】:

  • 你能给我们更多的背景信息吗?什么是 c 结构黑客?您的实际问题是什么?
  • @RobertHarvey 我相信将数组从 1 个元素扩展到 9 个元素。
  • @RobertHarvey 通过在末尾声明一个长度为 1 的数组来伪造一个灵活的数组成员。在 C99 中添加灵活的数组成员之前,这是 使结构具有可变(和运行时确定的)长度的数组的方式。 (但未定义的行为。)但我怀疑你的问题是反问。
  • @RobertHarvey 第一次看到它时会感到害怕,但实际上它的效果很好。
  • @RobertHarvey 它被广泛使用,以至于没有编译器能够承受破坏它。但根据标准,它始终是明确未定义的行为[嗯,显然不是在第一个标准之前]。现在我们已经标准化了灵活的数组成员,因此 struct hack 应该遵循 dodo。

标签: c


【解决方案1】:

是的,这是(并且曾经是)C 中创建和处理可变大小 struct 的标准方式。

这个例子有点冗长。大多数程序员会更熟练地处理它:

struct mystruct {
        int len;
        char chararray[1];  // some compilers would allow [0] here
    };
    char *msg = "abcdefghi";
    int n = strlen (msg);

    struct mystruct *ptr = malloc(sizeof(struct mystruct) + n + 1);

    ptr->len = n;
    strcpy (ptr->chararray, msg);
}

【讨论】:

  • 曾经。现在的标准方法是使用灵活的数组成员。
  • @LewsTherin struct foo { int length; other_type other_member; double flexible_array_member[]; };.
  • @wallyk strcpy 在这里工作是因为我们在 malloc 中分配了额外的内存,chararray 可以安全地指向这些内存,而不会有超出数组边界的危险?对吧?
  • @abc:没错。然而,当有人半知半解地改变了其中的一部分而没有认识到另一部分的依赖关系时,这也经常是潜在的错误来源。
【解决方案2】:

自从我阅读这篇文章 (http://blogs.msdn.com/b/oldnewthing/archive/2004/08/26/220873.aspx) 以来,我就喜欢这样使用 struct hack:

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

  int main()
  {

      struct mystruct {

          int len;
          char chararray[1];
      };

      int number_of_elements = 10;

      struct mystruct *ptr = malloc(offsetof(struct mystruct, chararray[number_of_elements]));
      ptr->len = number_of_elements;

      for (i = 0; i < number_of_elements; ++i) {
        ptr->chararray[i] = 'a' + i;
      }

  }

我发现不必记住是否需要减去(或添加或其他)1 很好。这也有在数组定义中使用0 的情况下工作的好处,并非所有编译器都支持,但有些编译器支持。如果分配基于offsetof(),您无需担心可能导致您的数学错误的细节。

结构体是 C99 灵活的数组成员。

【讨论】:

  • 在这篇博文中很难忽略 C99 允许大小为 0 的数组的声明(C99 在 6.2.5:20 中有关键字“非空”)。大小为 n 的数组的大小是元素大小的 n 倍也是标准化的,并且标准隐含地假设对象的大小在几个地方永远不会为零(尽管 GCC 冒着风险允许零大小的数组作为扩展。我不明白他们是如何接受这种风险的,但也许如果它只是在结构的末尾,没关系)
  • 我认为文章中提到的零长度数组在 C99 之前是不合法的,这是一个有点不精确的组合,清楚地表明它在 C90 中是不合法的,并且 C99 允许在结尾的不完整数组类型struct(即使不是这样,也不会从零大小的数组中拉伸太多)。
【解决方案3】:

由于可能存在对齐问题,我建议不要这样做:

struct my_struct
{
    char *arr_space;
    unsigned int len;
}

struct my_struct *ptr = malloc(sizeof(struct my_struct) + 10);
ptr->arr_space = ptr + 1;
ptr->len = 10;

这将为您提供位置和安全性:) 并避免奇怪的对齐问题。

对齐问题是指访问未对齐的内存可能会出现访问延迟。

在原始示例中,如果您添加一个字节或非字对齐成员(字节、字符、短),那么编译器可能会扩展结构的大小,但就您的指针而言,您是在结构的结尾(非对齐)。这意味着,如果您有一个对齐类型的数组,例如int,那么每次访问都会对因读取未对齐内存而受到影响的 CPU 造成性能损失。

struct
{
    byte_size data;
    char *var_len;
    some_align added by compiler;
}

在原始情况下,您将从 some_align 区域读取,这只是填充,但在我的情况下,您将在之后从对齐的额外内存中读取(这会浪费一些空间,但通常没关系)。

这样做的另一个好处是,可以通过在一次分配中为 struct 的可变长度成员分配所有空间而不是单独分配它们来从分配中获得更多的局部性(避免多次分配调用开销并为您提供一些缓存局部性,而不是在内存中弹跳)。

【讨论】:

  • char 数组没有任何对齐要求——它们可以在任何边界上对齐。
  • @AhmedMasud : int len; 在可变大小的部分之前怎么可能错位?
  • @Jesus:对不起,不:结构中的最后一个元素是一个大小为 1 的数组。它已经正确对齐。如果您为其他元素分配空间,那么这些元素也将正确对齐。
  • @JesusRamos - 但这仍然不是对齐问题。这只是一个不同的问题。
  • @JesusRamos:灵活的数组成员只能是结构的最后一个成员;对于较旧的“struct hack”也是如此。
【解决方案4】:

这是“正确的”,但您需要有充分的理由通过更合理的解决方案来做到这一点。更常见的是,您可能会使用这种技术“覆盖”一些现有数组,以对其施加某种标题结构。

请注意,GCC 通过扩展允许 zero length array member 用于此目的,而 ISO C99 通过允许带空括号的成员(仅作为最后一个成员)来“合法化”这种做法。

请注意,存在一些语义问题 - 结构的 sizeof 当然不会考虑最终成员的“灵活”大小,并且“按值”传递结构只会传递标题和第一个元素(或没有元素使用 GCC 扩展或 C99 灵活数组成员)。同样直接结构赋值不会复制所有数据。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-12-20
    • 2020-08-23
    • 1970-01-01
    • 2012-02-05
    • 2017-03-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多