首页 文章

在C练习中使用灵活的阵列成员?

提问于
浏览
63

我最近读到在C中使用灵活的阵列成员是糟糕的软件工程实践 . 但是,该声明没有任何论据支持 . 这是公认的事实吗?

Flexible array members是C99中引入的C特性,其中可以将最后一个元素声明为未指定大小的数组 . 例如:)

struct header {
    size_t len;
    unsigned char data[];
};

7 回答

  • 4

    我不赞成的原因是,为了使用这个功能,将代码绑定到C99是不值得的 .

    关键是你总是可以使用以下习语:

    struct header {
      size_t len;
      unsigned char data[1];
    };
    

    这是完全便携的 . 然后,在为数组 data 中的n个元素分配内存时,可以考虑1:

    ptr = malloc(sizeof(struct header) + (n-1));
    

    如果由于任何其他原因已经有C99作为构建代码的要求,或者你的目标是特定的编译器,我认为没有坏处 .

  • 4

    作为旁注,对于C89兼容性,此类结构应分配如下:

    struct header *my_header
      = malloc(offsetof(struct header, data) + n * sizeof my_header->data);
    

    或者使用宏:

    #define FLEXIBLE_SIZE SIZE_MAX /* or whatever maximum length for an array */
    #define SIZEOF_FLEXIBLE(type, member, length) \
      ( offsetof(type, member) + (length) * sizeof ((type *)0)->member[0] )
    
    struct header {
      size_t len;
      unsigned char data[FLEXIBLE_SIZE];
    };
    
    ...
    
    size_t n = 123;
    struct header *my_header = malloc(SIZEOF_FLEXIBLE(struct header, data, n));
    

    将FLEXIBLE_SIZE设置为SIZE_MAX几乎可以确保这将失败:

    struct header *my_header = malloc(sizeof *my_header);
    
  • 22

    有一些缺点与结构有时如何使用有关,如果你没有考虑其影响,那就很危险 .

    对于您的示例,如果您启动一个函数:

    void test(void) {
      struct header;
      char *p = &header.data[0];
    
      ...
    }
    

    然后结果是未定义的(因为没有为数据分配存储空间) . 这是您通常会注意到的事情,但有些情况下,C程序员可能习惯于对结构使用值语义,这会以各种其他方式分解 .

    例如,如果我定义:

    struct header2 {
      int len;
      char data[MAXLEN]; /* MAXLEN some appropriately large number */
    }
    

    然后我可以简单地通过赋值复制两个实例,即:

    struct header2 inst1 = inst2;
    

    或者如果它们被定义为指针:

    struct header2 *inst1 = *inst2;
    

    但是这不起作用,因为不会复制变量数组 data . 你想要的是动态malloc结构的大小和复制数组与 memcpy 或等效 .

    同样,编写一个接受结构的函数也行不通,因为函数调用中的参数再次按值复制,因此你得到的结果可能只是数组的第一个元素 data .

    这并不是一个坏主意使用,但你必须记住,始终动态分配这些结构,只传递它们作为指针 .

  • 18

    你的意思是...

    struct header
    {
     size_t len;
     unsigned char data[];
    };
    

    在C中,这是一个常见的习语 . 我想很多编译器也接受:

    unsigned char data[0];
    

    是的,它很危险,但是再一次,它确实没有比普通的C阵列更危险 - 即,非常危险;-) . 小心使用它,并且仅在您真正需要未知大小的数组的情况下使用它 . 确保你使用malloc并使用以下内容正确释放内存: -

    foo = malloc(sizeof(header) + N * sizeof(data[0]));
      foo->len = N;
    

    另一种方法是使数据只是指向元素的指针 . 然后,您可以根据需要将数据重新分配()到正确的大小 .

    struct header
        {
         size_t len;
         unsigned char *data;
        };
    

    当然,如果你问的是C,那么这些都是不好的做法 . 然后你通常会使用STL向量 .

  • 10

    使用goto的软件工程实践很差,这是一个公认的“事实” . 这并不是真的 . 有时候goto很有用,特别是在处理清理和从汇编程序移植时 .

    灵活的阵列成员让我觉得有一个主要用途,在我的头顶,这是在RiscOS上映射遗留数据格式,如窗口模板格式 . 在大约15年前,它们对于这一点非常有用,而且我确信仍有人在那里处理那些会发现它们有用的东西 .

    如果使用灵活的阵列成员是不好的做法,那么我建议我们都告诉作者C99规范这个 . 我怀疑他们可能有不同的答案 .

  • 6

    我见过这样的东西:从C接口和实现 .

    struct header {
        size_t len;
        unsigned char *data;
    };
    
       struct header *p;
       p = malloc(sizeof(*p) + len + 1 );
       p->data = (unsigned char*) (p + 1 );  // memory after p is mine!
    

    注意:数据不必是最后一个成员 .

  • 4

    不,在C中使用flexible array members并不是一种不好的做法 .

    该语言特征首先在ISO C99,6.7.2.1(16)中标准化 . 对于当前标准ISO C11,它在第6.7.2.1节(18)中规定 .

    您可以像这样使用它们:

    struct Header {
        size_t d;
        long v[];
    };
    typedef struct Header Header;
    size_t n = 123; // can dynamically change during program execution
    // ...
    Header *h = malloc(sizeof(Header) + sizeof(long[n]));
    h->n = n;
    

    或者,您可以像这样分配它:

    Header *h = malloc(sizeof *h + n * sizeof h->v[0]);
    

    请注意 sizeof(Header) 包含最终的填充字节,因此,以下分配不正确并可能产生缓冲区溢出:

    Header *h = malloc(sizeof(size_t) + sizeof(long[n])); // invalid!
    

    具有灵活数组成员的结构将其分配数量减少1/2,即对于一个结构对象而不是2个分配,只需要1.意味着内存分配器簿记开销占用更少的工作量和更少的内存 . 此外,您还可以为一个额外的指针保存存储空间 . 因此,如果必须分配大量此类结构实例,则可以显着改善程序的运行时和内存使用(通过常数因子) .

    与此相反,对于产生未定义行为的灵活数组成员使用非标准化构造(例如在 long v[0];long v[1]; 中)显然是不好的做法 . 因此,任何未定义的行为都应该避免 .

    自ISO C99于1999年发布以来,差不多20年前,争取ISO C89兼容性是一个弱论点 .

相关问题