【问题标题】:Is this hack valid according to standard?这个hack根据标准有效吗?
【发布时间】:2010-07-25 14:45:36
【问题描述】:

这就像 struct hack。 根据标准C是否有效?

 // error check omitted!

    typedef struct foo {
       void *data;
       char *comment;
       size_t num_foo;
    }foo;

    foo *new_Foo(size_t num, blah blah)
    {
        foo *f;
        f = malloc(num + sizeof(foo) + MAX_COMMENT_SIZE );
        f->data = f + 1;            // is this OK?
        f->comment = f + 1 + num;
        f->num_foo = num;
        ...
        return f;

}

【问题讨论】:

    标签: c


    【解决方案1】:

    是的,它完全有效。当它允许您避免不必要的额外分配(以及它们带来的错误处理和内存碎片)时,我强烈鼓励这样做。其他人可能有不同的看法。

    顺便说一句,如果您的数据不是void *,而是您可以直接访问的数据,那么将您的结构声明为更容易(并且更有效,因为它节省空间并避免了额外的间接性):

    struct foo {
        size_t num_foo;
        type data[];
    };
    

    并为您需要的数据量分配空间。 [] 语法仅在 C99 中有效,因此为了与 C8​​9 兼容,您应该改用 [1],但这可能会浪费一些字节。

    【讨论】:

    • 如您所知但可能值得指出的是,结构末尾的灵活数组成员仅在您有一个可变长度项时才有效。该问题有两个可变长度项(datacomment),不能轻易使用该技术。
    • 在这种情况下,将comment 存储为普通的malloc() 分配字符串。
    • FWIW,我在添加评论字段之前给出了这个答案。添加评论后,请遵循 Donal 的建议。
    【解决方案2】:

    您提出的问题是有效的 - 正如其他人所说的那样。

    有趣的是,您没有查询的下一行在语法上是有效的,但并未给您想要的答案(num == 0 的情况除外)。

    typedef struct foo
    {
       void *data;
       char *comment;
       size_t num_foo;
    } foo;
    
    foo *new_Foo(size_t num, blah blah)
    {
        foo *f;
        f = malloc(num + sizeof(foo) + MAX_COMMENT_SIZE );
        f->data = f + 1;            // This is OK
        f->comment = f + 1 + num;   // This is !!BAD!!
        f->num_foo = num;
        ...
        return f;
    }
    

    f + 1 的值是 foo *(通过赋值隐式强制转换为 void *)。

    f + 1 + num 的值也是一个foo *;它指向num+1thfoo

    你可能想到的是:

    foo->comment = (char *)f->data + num;
    

    或者:

    foo->comment = (char *)(f + 1) + num;
    

    请注意,虽然 GCC 允许您将 num 添加到 void 指针,并将其视为 sizeof(void) == 1,但 C 标准并未授予您该权限。

    【讨论】:

      【解决方案3】:

      这是一个古老的游戏,虽然通常的形式是这样的

      struct foo {
         size_t size
         char data[1]
      }
      

      然后根据需要分配空间并使用数组,就好像它具有所需的大小一样。

      有效的,但如果可能的话,我会鼓励你找到另一种方法:有很多机会搞砸这个。

      【讨论】:

      • 按照标准,这个旧 hack 不是*有效*(严格)。
      • 不,不是,但它malloc的任何符合实现的实现一起工作。 ::对病态分段内存架构进行了一些思考:: 几乎任何符合malloc.
      • 它作为标准的其他要求的结果是有效的,例如,如果一个结构与另一个结构的初始部分匹配,则通过 2 访问这些元素是兼容的。 C99 通过灵活的数组表示法 [] 明确支持它,但旧的 [1] 方法也必须工作!
      • @dmckee,任何病理性不能符合的东西。该标准要求malloc 返回的内存可以作为unsigned char 的数组访问,并且可以针对任何类型进行适当对齐。
      • @R:委员会实际上曾经讨论过这个问题,并决定它确实没有在 C89 中定义。访问超出定义的数组大小会导致未定义的行为。虽然这种情况很少见,但当您尝试在whatever[0] 之后访问内存时,编译器可以 进行范围检查并引发某种操作系统异常(或其他)。
      【解决方案4】:

      是的,hack 的总体思路是有效的,但至少在我阅读时,您还没有完全正确地实现它。你做对了这么多:

          f = malloc(num + sizeof(foo) + MAX_COMMENT_SIZE );
          f->data = f + 1;            // is this OK?
      

      但这是错误的:

          f->comment = f + 1 + num;
      

      因为ffoo *,所以f+1+num 是根据sizeof(foo) 计算的——也就是说,它相当于说f[1+num]——它(尝试)索引到1+numthfoo 在一个数组中。我很确定这不是你想要的。当你分配数据时,你传递了sizeof(foo)+num+MAX_COMMENT_SIZE,所以你分配空间的是num chars,你(大概)想要的是将f->comment指向内存中的一个位置num char s 在f->data 之后,会更像这样:

      f->comment = (char *)f + sizeof(foo) + num;
      

      f 转换为char * 会强制按照chars 而不是foos 来完成数学运算。

      OTOH,由于您总是为comment 分配MAX_COMMENT_SIZE,我可能会(相当)简化一些事情,并使用这样的东西:

      typedef struct foo {
         char comment[MAX_COMMENT_SIZE];
         size_t num_foo;
         char data[1];
      }foo;
      

      然后像这样分配它:

      foo *f = malloc(sizeof(foo) + num-1);
      f->num_foo = num;
      

      它可以在没有任何指针操作的情况下工作。如果你有 C99 编译器,你可以稍微修改一下:

      typedef struct foo {
         char comment[MAX_COMMENT_SIZE];
         size_t num_foo;
         char data[];
      }foo;
      

      并分配:

      foo *f = malloc(sizeof(foo) + num);
      f->num_foo = num;
      

      这具有标准实际上祝福它的额外优势,尽管在这种情况下优势很小(我相信带有data[1] 的版本将适用于现有的每个 C89/90 编译器)。

      【讨论】:

      • 您和我差不多在同一时间发现了这一点 - 我们并行编写了我们的答案。
      • @Jonathan Leffler:是的——尤其是像我这样冗长的答案,这几乎是不可避免的......
      【解决方案5】:

      另一个可能的问题可能是对齐。

      如果你只是 malloc 你的f->data,那么你可以安全地例如将您的 void* 转换为 double* 并使用它来读取/写入双精度(前提是 num 足够大)。但是,在您的示例中,您不能再这样做,因为 f->data 可能没有正确对齐。例如,要在 f->data 中存储一个 double,您需要使用 memcpy 之类的东西,而不是简单的类型转换。

      【讨论】:

      • 使用我的方法(最后是数组)可以自动解决对齐问题。它也可以通过分配足够的额外空间来解决,您可以将f->data 向上舍入到sizeof(double) 的下一个倍数或您需要的任何类型。
      【解决方案6】:

      我宁愿使用一些函数来动态分配数据并正确释放它。

      使用这个技巧只会为您省去初始化数据结构的麻烦,并且可能会导致非常糟糕的问题(请参阅 Jerry 的评论)。

      我会这样做:

      typedef struct foo {
             void *data;
             char *comment;
             size_t num_foo;
          }foo;
      foo *alloc_foo( void * data, size_t data_size, const char *comment)
      {
         foo *elem = calloc(1,sizeof(foo));
         void *elem_data = calloc(data_size, sizeof(char));
         char *elem_comment = calloc(strlen(comment)+1, sizeof(char));
         elem->data = elem_data;
         elem->comment = elem_comment;
      
         memcpy(elem_data, data, data_size);
         memcpy(elem_comment, comment, strlen(comment)+1);
         elem->num_foo = data_size + strlen(comment) + 1;
      }
      
      void free_foo(foo *f)
      {
         if(f->data)
            free(f->data);
         if(f->comment)
            free(f->comment);
         free(f);
      }
      

      请注意,我没有检查数据有效性,并且我的 alloc 可以优化(将 strlen() 调用替换为存储的长度值)。

      在我看来,这种行为更安全……可能是以散布数据块为代价的。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2010-12-05
        • 2011-05-22
        • 2013-09-21
        • 2012-06-04
        • 1970-01-01
        • 1970-01-01
        • 2021-12-04
        相关资源
        最近更新 更多