Visual studio 将不完整的数组视为零长度数组
Visual studio treats incomplete arrays as zero-length arrays
我有一个看起来像这样的结构:
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?
当您创建一个未定义长度的数组(在您的例子中是一个结构)时,不知道需要为其分配多少内存。因此,当使用变量 'info' 时,程序正在写入或读取计算机高速缓存中的哪个位置是未知的。这会使程序崩溃。通常,当您想使用未定义长度的数组时,会使用指针。当知道数组应该有多大时,可以为这个指针分配内存。
编译器会警告您我上面描述的问题,但允许您执行代码(至少在 Microsoft Visual studio 2013 中是这样)。
希望对您有所帮助!
对于那些对 zero-length 数组或 "flexible array" 习语感到好奇的人,可能值得花一点时间来解释它们。这个习语和 C 本身一样古老。
假设您要传递一个由 header 和可变数据量组成的结构。在分配结构之前不知道需要向其中添加多少数据。
原来的成语是这样声明结构的:
/* Variation 1 */
struct mydata {
int type;
int datalen;
char data[1];
};
然后假设我们想要 return 其中之一 objects:
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 会在所有内容中引入 off-by-one 错误,并邀请编码人员犯错误。
因此 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 扩展名完全相同。
不幸的是,Microsoft 似乎并未在其编译器中采用 C99 标准,因此无论您做什么,变体 2 和 C99 都会生成警告。看起来我唯一的选择是接受警告消息或添加 pragma 来抑制它。
Linux 用户可以通过执行 gr -r '\[0\]' /usr/include
并查看 许多 地方如何使用 zero-length 数组来自娱自乐。这是一个很常用的成语。
关于我自己的问题:我正在使用的结构实际上是 ioctl 的一部分。 driver 已经写入,无法更改。我最多能做的就是将数组从 zero-length 重新定义为 flexible。不幸的是,这两个选项都不会让 MSVC 编译器满意。
Visual Studio 2015 [几乎] 完全实现了 C99,但仍然将所有 C99 功能视为 语言扩展 (例如,禁用语言扩展也会禁用 C99 支持)。其中一些功能会触发虚假警告,就像您观察到的那样。
只要 C99 支持保持这种半官方 "extension" 状态,就只是 ignore/disable 这样的警告。
请注意,VS2015 Update 3 不再对此类 C 代码发出此警告。
我有一个看起来像这样的结构:
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?
当您创建一个未定义长度的数组(在您的例子中是一个结构)时,不知道需要为其分配多少内存。因此,当使用变量 'info' 时,程序正在写入或读取计算机高速缓存中的哪个位置是未知的。这会使程序崩溃。通常,当您想使用未定义长度的数组时,会使用指针。当知道数组应该有多大时,可以为这个指针分配内存。
编译器会警告您我上面描述的问题,但允许您执行代码(至少在 Microsoft Visual studio 2013 中是这样)。
希望对您有所帮助!
对于那些对 zero-length 数组或 "flexible array" 习语感到好奇的人,可能值得花一点时间来解释它们。这个习语和 C 本身一样古老。
假设您要传递一个由 header 和可变数据量组成的结构。在分配结构之前不知道需要向其中添加多少数据。
原来的成语是这样声明结构的:
/* Variation 1 */
struct mydata {
int type;
int datalen;
char data[1];
};
然后假设我们想要 return 其中之一 objects:
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 会在所有内容中引入 off-by-one 错误,并邀请编码人员犯错误。
因此 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 扩展名完全相同。
不幸的是,Microsoft 似乎并未在其编译器中采用 C99 标准,因此无论您做什么,变体 2 和 C99 都会生成警告。看起来我唯一的选择是接受警告消息或添加 pragma 来抑制它。
Linux 用户可以通过执行 gr -r '\[0\]' /usr/include
并查看 许多 地方如何使用 zero-length 数组来自娱自乐。这是一个很常用的成语。
关于我自己的问题:我正在使用的结构实际上是 ioctl 的一部分。 driver 已经写入,无法更改。我最多能做的就是将数组从 zero-length 重新定义为 flexible。不幸的是,这两个选项都不会让 MSVC 编译器满意。
Visual Studio 2015 [几乎] 完全实现了 C99,但仍然将所有 C99 功能视为 语言扩展 (例如,禁用语言扩展也会禁用 C99 支持)。其中一些功能会触发虚假警告,就像您观察到的那样。
只要 C99 支持保持这种半官方 "extension" 状态,就只是 ignore/disable 这样的警告。
请注意,VS2015 Update 3 不再对此类 C 代码发出此警告。