【问题标题】:Visual studio treats incomplete arrays as zero-length arraysVisual Studio 将不完整的数组视为零长度数组
【发布时间】:2016-08-24 19:43:49
【问题描述】:

我有一个看起来像这样的结构:

typedef struct foo {
    int this;
    int that;
    int length;
    int info[];     // legal for last element of a struct 
} Foo;

当我编译它时,我收到这样的警告:

C4200 nonstandard extension used: zero-sized array in struct/union

我只是忍受警告,还是我可以设置一些属性来告诉 Visual Studio 使用 C-99?

【问题讨论】:

  • 不要“忍受”警告。 struct 的大小未知。如果不知道数组的大小,将该成员定义为指针,并根据需要分配内存。
  • 不,MSVC 不支持 0 长度数组。如果您希望struct 溢出到分配的内存(从它的最后一个成员),请将成员字符串定义为[1] 并让它溢出到正确分配的内存中。
  • msdn.microsoft.com/en-us/library/79wf64bc.aspx -- 关键文本是“声明零大小数组是 Microsoft 扩展”
  • MSVC 不符合 C99。 (适用于一些)
  • 实际上,在struct 中,成员char z[] 已被MSVC 接受为零长度成员,因此请接受我的道歉。

标签: c visual-studio-2015 c99


【解决方案1】:

Visual Studio 2015 [几乎] 完全实现了 C99,但仍将所有 C99 功能视为语言扩展(例如,禁用语言扩展也会禁用 C99 支持)。其中一些功能会触发虚假警告,例如您观察到的警告。

只要 C99 支持仍处于这种半官方的“扩展”状态,只需忽略/禁用此类警告。

请注意,VS2015 Update 3 不再针对此类 C 代码发出此警告。

【讨论】:

    【解决方案2】:

    对于那些对零长度数组或“灵活数组”习语感到好奇的人,可能值得花一点时间来解释一下。这个习语和 C 本身一样古老。

    假设您要传递一个由标头和可变数据量组成的结构。在分配结构之前不知道需要向其中添加多少数据。

    原来的习惯是这样声明结构:

    /* Variation 1 */
    struct mydata {
        int type;
        int datalen;
        char data[1];
    };
    

    然后假设我们想要返回这些对象之一:

    struct mydata *
    get_some_data()
    {
        int len;
        struct mydata *rval;
        len = find_out_how_much_data();
        /* Allocate the struct AND enough extra space to hold the data */
        rval = malloc(sizeof(*rval) + len - 1);
        rval->datalen = len;
        read_data(&rval->data[0], len);
        return rval;
    }
    

    调用者会像这样访问它:

    void caller()
    {
        struct mydata *foo = get_some_data();
        /* Start accessing foo->datalen bytes of data starting at
         * foo->data[0]
         */
        free(foo);     /* And free it all */
    }
    

    这个习语的重点是char data[1] 声明是一个谎言,因为数据肯定会比这更长,但是 C 编译器不做范围检查,所以一切都很酷。

    但请注意 malloc 中的 len - 1 表达式。这是必要的,因为将数据声明为长度为 1 会在所有内容中引入一个错误,并导致编码人员犯错误。

    因此 GNU 和 Microsoft 都在语言中添加了一个扩展,允许您声明一个长度为零的数组:

    /* Variation 2 */
    struct mydata {
        int type;
        int datalen;
        char data[0];
    };
    

    虽然从表面上看,这是荒谬的,但它与这里使用的成语巧妙地吻合。现在我们可以简单地做:

    rval = malloc(sizeof(*rval) + len);
    

    而且代码更简洁。

    C99 通过承认数组的长度是一个谎言来正式化这个习惯用法,但是在结构末尾有额外数据的能力非常方便。所以现在你声明:

    /* Variation C99 */
    struct mydata {
        int type;
        int datalen;
        char data[];
    };
    

    所有的代码都与 Gnu/Microsoft 扩展完全一样。

    不幸的是,微软似乎没有在他们的编译器中采用 C99 标准,所以无论你做什么,变体 2 和 C99 都会产生警告。看来我唯一的选择是要么接受警告消息,要么添加一个编译指示来抑制它。

    Linux 用户可以通过执行 gr -r '\[0\]' /usr/include 并查看 许多 地方如何使用零长度数组来自娱自乐。这是一个非常常用的成语。

    至于我自己的问题:我正在使用的结构实际上是 ioctl 的一部分。驱动已经写好了,改不了。我能做的最多就是将数组从零长度重新定义为灵活的。不幸的是,这两个选项都不能让 MSVC 编译器满意。

    【讨论】:

    • "...看看有多少地方使用了零长度数组。这是一个非常常用的成语。"由于某些 Linux 程序员的懒惰和/或无能(以及迎合这种懒惰的 GCC 编译器),这是一个不成熟的习语。该成语在 C99 之前的语言中的正确形式意味着使用 [1] 作为灵活成员(如您的原始示例中所示),而不是 [0]
    • 您的- 1 参数无效,因为在[1] 变体中计算大小的替代(正确)方法是offsetof(mydata, data) + len。看?无需再减去1。令人惊讶的是,大量所谓的“专业”程序员完全没有意识到 offsetof 宏等语言的标准特性(以至于试图手动重新实现它,尽管它已经很容易使用了)。
    • 也许吧。我自己从来没有用过这个成语。我一直只使用char *datap = &mydatap->data[0]; 之类的东西,或者(在struct mydata 根本没有data 元素的情况下)char *datap = ((char *)mydatap) + sizeof(*mydatap);
    • 正如我上面所说,在 VS 2015 Update 3 中不再为 C 代码生成此警告。Microsoft 已修复此问题。
    • @AnT:如果代码试图访问除第一个成员之外的任何内容,则 size-one 表单将调用 UB。处理该构造的正确方法应该是允许某些 C89 之前的编译器所做的事情,即将零大小的数组视为针对适当类型对齐的零字节分配,并避免对有关用于访问它们的索引。实际上,避免 UB 需要声明一个足够大的数组以容纳将要使用的最大索引。
    【解决方案3】:

    当您创建一个未定义长度的数组(在您的情况下是一个结构)时,不知道需要为其分配多少内存。因此,当使用变量“信息”时,程序在计算机的高速缓存中写入或读取的位置是未知的。这会使程序崩溃。通常,当您要使用未定义长度的数组时,会使用指针。当知道数组应该有多大时,可以为这个指针分配内存。

    编译器会就我上面描述的问题向您发出警告,但允许您执行代码(至少在 Microsoft Visual Studio 2013 中是这样)。

    希望对您有所帮助!

    【讨论】:

    • OP 中的代码是 C 语言的一个完全合法且众所周知的特性。它不是“未定义长度的数组”。它是 C99 灵活阵列成员。不,如果你使用得当,它不会使程序崩溃。
    • 另外,您提到的缓存内存超出了主题。
    猜你喜欢
    • 2021-02-17
    • 2010-09-22
    • 1970-01-01
    • 2018-06-15
    • 2012-09-06
    • 2017-02-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多