Array of length zero

看了下在C中Array of length zero的用法,感觉脑洞大开啊。不过从标准角度(非编译器扩展)来说,这个特性只存在于C语言(C99之后),C++中是不存在的。先挖个坑,来分析一下。

首先,先来了解一下什么是不完全类型incomplete type:

[ISO/IEC 14882:2014]A class that has been declared but not defined, an enumeration type in certain contexts (7.2), or an array of unknown size or of incomplete element type, is an incompletely-defined object type.Incompletely-defined object types and the void types are incomplete types (3.9.1)

而C语言([ISO/IEC 9899:1999])中的不完全类型(incomplete type)是:

  • The void type comprises an empty set of values; it is an incomplete type that cannot be completed.
  • An array type of unknown size is an incomplete type.
  • A structure or union type of unknown content (as described in 6.7.2.3) is an incomplete type.

OK,铺垫好了,接下来继续看Array of length zero的内容。

C语言中的struct可以包含一个不完全类型(incomplete types)(但不是随意包含):

[ISO/IEC 9899:1999]A structure or union shall not contain a member with incomplete or function type (hence,A structure or union shall not contain a member with incomplete or function type (hence,a structure shall not contain an instance of itself, but may contain a pointer to an instance of itself), except that the last member of a structure with more than one named member may have incomplete array type; such a structure (and any union containing, possibly recursively, a member that is such a structure) shall not be a member of a structure or an element of an array.

意思就是:具有多个成员的结构的最后一个成员可以是一个不完全数组。

注意:这个特性是C99才引入的,可以看一下ANSI C89(ISOC90)中对于structunion的成员要求:

[ISO/IEE 9899:1990]A structure or union shall not contain a member with incomplete or function type. Hence it shall not contain an instance of itself (but may contain a pointer to an instance of itself).

ANSI C89则规定了在structunion中不能包含不完全类型(incomplete type)和函数类型。

区分不同标准版本的规范可以写出健壮可移植的代码,便于将那些客观因素排除在外(编译器版本对标准的支持程度)。

通过C99引入的这个特性可以使用Array of length zero来动态扩充一个struct

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct A{
char tag;
char alz[];
}Astruct;
int main(void){
Astruct *x=(struct A*)malloc(sizeof(Astruct)+sizeof(char)*10);
free(x);
return 0;
}

虽然也可以使用一个指针:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef struct A{
char tag;
char *cp;
}AstructPtr;
int main(void){
AstructPtr *x=(AstructPtr*)malloc(sizeof(AstructPtr));
x->cp=(char*)malloc(sizeof(char*)*10);
free(x->cp);
free(x);
return 0;
}

但是使用Array of length zero可以节省一个sizeof(char*)的开销(而且还有内存对齐的开销),而且第一种方法创建的是连续分配的内存,可以减轻内存碎片化的问题。

使用sizeof(Astruct)可以看到,其大小只是一个sizeof(char),而sizeof(AstructPtr)则是一个sizeof(char)+padding+sizeof(char*)的大小:

1
2
3
4
printf("%llu\n",sizeof(Astruct));
// resault: 1
printf("%llu\n",sizeof(AstructPtr));
// resault is sizeof(char)+padding+sizeof(char*) e.g: 16

这样来看确实可以节省很多的空间。关于struct中成员间的padding对齐具体可以看我的这篇文章:结构体成员内存对齐问题

但是,注意上面那段引用C99标准的最后一句,这代表着Array of length zero不能出现在非对象布局的末尾处。同时也意味着这个特性在C++中是不能存在的(因为C++存在继承(inheritance)),派生类中存在基类中的成员,但是C++标准并未规定类的内存布局怎样,所以不能保证继承来的基类的最后一个成员也是派生类的最后一个成员,在C++中也同样不能保证类的最后一个成员在内存布局中位于实例的尾部(不能确定虚函数表的位置,依赖实现)。这里涉及C++对象模型的内容,详情请查看《深度探索C++对象模型》(Inside The C++ Object Model)。

即,在C++中,class的成员(非static)不能是一个不完全类型(incomplete types):

[ISO/IEC 14882:2014]Non-static data members shall not have incomplete types. In particular, a class C shall not contain a non-static member of class C, but it can contain a pointer or reference to an object of class C.

所以就不能在C++中使用上面C中的Array of length zero的技巧(这个还是要依赖于实现),如果你使用了这个技巧,那么从C++标准角度来看是Undefine Behavior行为。

虽然C++中也可以使用{}来对未知大小的数组进行初始化,但是不能使用空的初始化列表为一个未知大小的数组进行初始化。

[ISO/IEC 14882:2014]An array of unknown size initialized with a brace-enclosed initializer-list containing n initializer-clauses, where n shall be greater than zero, is defined as having n elements (8.3.4).

1
int x[] = { 1, 3, 5 };

declares and initializes x as a one-dimensional array that has three elements since no size was specified and there are three initializers. An empty initializer list {} shall not be used as the initializer-clause for an array of unknown bound.

The syntax provides for empty initializer-lists, but nonetheless C++ does not have zero length arrays.

所以在C++中,对A::alz的访问是UB行为(如果你能编译过的话):

1
2
3
4
struct A{
char tag;
char alz[];
};

以上代码在MinGW-W64 G++ 6.2.0以及Clang++ 3.9 x86_64-w64-windows-gnu使用以下参数编译通过(无任何提示):

1
2
g++ -o test test.cc
clang++ -o test test.cc

VS2015中编译则提示了一个警告:

warning C4200: 使用了非标准扩展: 结构/联合中的零大小数组

关于Array of length zero的文章及相关讨论:

全文完,若有不足之处请评论指正。

扫描二维码,分享此文章

本文标题:Array of length zero
文章作者:ZhaLiPeng
发布时间:2017年03月30日 19时00分
更新时间:2017年03月31日 11时48分
本文字数:本文一共有1,386字
原始链接:https://imzlp.me/posts/21095/
许可协议: CC BY-NC-SA 4.0
转载请保留原文链接及作者信息,谢谢!
您的捐赠将鼓励我继续创作!