int a; double b; char c[0];
};
c 就叫柔性数组成员,如果把 PntTest 指向的动态分配内存看作一个整体,c 就是一个长 度可以动态变化的结构体成员,柔性一词来源于此。c 的长度为 0,因此它不占用 test 的空 间,同时 PntTest->c 就是“hello world”的首地址,不需要再使用( char* )( PntTest + 1 )这么丑 陋的语法了。
鉴于这种代码结构所产生的重要作用,C99 甚至把它收入了标准中: 6.7.2.1 Structure and union specifiers
As a special case, the last element of a structure with more than one named member may have an incomplete array type; this is called a flexible array member. C99 使用不完整类型实现柔性数组成员,标准形式是这样的: struct test {
int a; double b; char c[];
};
c 同样不占用 test 的空间,只作为一个符号地址存在,而且必须是结构体的最后一个成 员。柔性数组成员不仅可以用于字符数组,还可以是元素为其它类型的数组,例如: struct test {
int a; double b; float c[];
};
应当尽量使用标准形式,在非 C99 的场合,可以使用指针方法。有些人使用 char a[1], 这是非常不可取的,把这样的 a 用作柔性数组成员会发生越界行为,虽然 C/C++标准并没有 规定编译器应当检查越界,但也没有规定不能检查越界,为了一个小小的指针空间而牺牲移 植性,是不值得的。
第九章 C99 可变长数组 VLA 详解
C90 及 C++的数组对象定义是静态联编的,在编译期就必须给定对象的完整信息。但在 程序设计过程中,我们常常遇到需要根据上下文环境来定义数组的情况,在运行期才能确知 数组的长度。对于这种情况,C90 及 C++没有什么很好的办法去解决(STL 的方法除外),只 能在堆中创建一个内存映像与需求数组一样的替代品,这种替代品不具有数组类型,这是一 个遗憾。C99 的可变长数组为这个问题提供了一个部分解决方案。
可变长数组(variable length array,简称 VLA)中的可变长指的是编译期可变,数组定 义时其长度可为整数类型的表达式,不再象 C90/C++那样必须是整数常量表达式。在 C99 中 可如下定义数组: int n = 10, m = 20; char a[n]; int b[m][n];
a 的类型为 char[n],等效指针类型是 char*,b 的类型为 int[m][n],等效指针类型是 int(*)[n]。int(*)[n]是一个指向 VLA 的指针,是由 int[n]派生而来的指针类型。
由此,C99 引入了一个新概念:可变改类型(variably modified type,简称 VM)。一个含 有源自 VLA 派生的完整声明器被称为可变改的。VM 包含了 VLA 和指向 VLA 的指针,注意 VM 类型并没有创建新的类型种类,VLA 和指向 VLA 的指针仍然属于数组类型和指针类型, 是数组类型和指针类型的扩展。
一个 VM 实体的声明或定义,必须符合如下三个条件:
1。代表该对象的标识符属于普通标识符(ordinary identifier);
2。具有代码块作用域或函数原型作用域; 3。无链接性。
Ordinary identifier 指的是除下列三种情况之外的标识符:
1。标签(label);
2。结构、联合和枚举标记(struct tag、uion tag、enum tag);
3。结构、联合成员标识符。
这意味着 VM 类型的实体不能作为结构、联合的成员。第二个条件限制了 VM 不能具有 文件作用域,存储连续性只能为 auto,这是因为编译器通常把全局对象存放于数据段,对 象的完整信息必须在编译期内确定。
VLA 不能具有静态存储周期,但指向 VLA 的指针可以。
两个 VLA 数组的相容性,除了满足要具有相容的元素类型外,决定两个数组大小的表 达式的值也要相等,否则行为是未定义的。
下面举些实例来对数种 VM 类型的合法性进行说明: #include
int k; int a[n]; /*非法,a 不是普通标识符*/ int (*p)[n]; /*非法,p 不是普通标识符*/
};
int main( void ) {
int m = 20; struct test1 {
int k;
int a[n]; int (*p)[n];
};
extern int a[n]; static int b[n]; int c[n]; int d[m][n];
static int (*p1)[n] = d; n = 20;
/*非法,VLA 不能具有链接性*/
/*非法,VLA 不能具有静态存储周期*/ /*合法,自动 VLA*/ /*合法,自动 VLA*/
/*合法,静态 VM 指针*/
/*非法,a 不是普通标识符*/ /*非法,a 不是普通标识符*/
static int (*p2)[n] = d; /*未定义行为*/ return 0;
}
一个 VLA 对象的大小在其生存期内不可改变,即使决定其大小的表达式的值在对象定 义之后发生了改变。有些人看见可变长几个字就联想到 VLA 数组在生存期内可自由改变大 小,这是误解。VLA 只是编译期可变,一旦定义就不能改变,不是运行期可变,运行期可变 的数组叫动态数组,动态数组在理论上是可以实现的,但付出的代价可能太大,得不偿失。 考虑如下代码: #include
int n = 10, m = 20; char a[m][n]; char (*p)[n] = a;
printf( “%u %u”, sizeof( a ), sizeof( *p ) ); n = 20; m = 30;
printf( “\\n” );
printf( “%u %u”, sizeof( a ), sizeof( *p ) ); return 0;
}
虽然 n 和 m 的值在随后的代码中被改变,但 a 和 p 所指向对象的大小不会发生变化。 上述代码使用了运算符 sizeof,在 C90/C++中,sizeof 从操作数的类型去推演结果,不对 操作数进行实际的计算,运算符的结果为整数常量。当 sizeof 的操作数是 VLA 时,情形就不 同了。sizeof 必须对 VLA 进行计算才能得出 VLA 的大小,运算结果为整数,不是整数常量。
VM 除了可以作为自动对象外,还可以作为函数的形参。作为形参的 VLA,与非 VLA 数 组一样,会调整为与之等效的指针,例如:
void func( int a[m][n] ); 等效于 void func( int (*a)[n] );
在函数原型声明中,VLA 形参可以使用*标记,*用于[]中,表示此处声明的是一个 VLA
对象。如果函数原型声明中的 VLA 使用的是长度表达式,该表达式会被忽略,就像使用了* 标记一样,下面几个函数原型声明是一样的: void func( int a[m][n] ); void func( int a[*][n] ); void func( int a[ ][n] ); void func( int a[*][*] ); void func( int a[ ][*] ); void func( int (*a)[*] );
*标记只能用在函数原型声明中。再举个例: #include
void func( int, int, int a[*][*] ); int main(void) {
int m = 10, n = 20; int a[m][n]; int b[m][m*n]; func( m, n, a ); /*未定义行为*/ func( m, n, b ); return 0; }
void func( int m, int n, int a[m][m*n] ) {
printf( \
}
除了*标记外,形参中的数组还可以使用类型限定词 const、volatile、restrict 和 static 关 键字。由于形参中的 VLA 被自动调整为等效的指针,因此这些类型限定词实际上限定的是 一个指针,例如:
void func( int, int, int a[const][*] );
等效于
void func( int, int, int ( *const a )[*] );
它指出 a 是一个 const 对象,不能在 func 内部直接通过 a 修改其代表的对象。例如: void func( int, int, int a[const][*] ); ??..
void func( int m, int n, int a[const m][n] ) {
int b[m][n]; a = b; /*错误,不能通过 a 修改其代表的对象*/
}
static 表示传入的实参的值至少要跟其所修饰的长度表达式的值一样大。例如: void func( int, int, int a[const static 20][*] ); ??
int m = 20, n = 10; int a[m][n]; int b[n][m];
func( m, n, a );
func( m, n, b ); /*错误,b 的第一维长度小于 static 20*/
类型限定词和 static 关键字只能用于具有数组类型的函数形参的第一维中。这里的用词 是数组类型,意味着它们不仅能用于 VLA,也能用于一般数组形参。
总的来说,VLA 虽然定义时长度可变,但还不是动态数组,在运行期内不能再改变,受 制于其它因素,它只是提供了一个部分解决方案。
百度搜索“77cn”或“免费范文网”即可找到本站免费阅读全部范文。收藏本站方便下次阅读,免费范文网,提供经典小说综合文库《再再论指针》修订版(5)在线全文阅读。
相关推荐: