《再再论指针》修订版(5)

2018-12-17 17:14

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 n = 10; int a[n]; /*非法,VM 类型不能具有文件作用域*/ int (*p)[n]; /*非法,VM 类型不能具有文件作用域*/ struct test {

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 main( void ) {

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 虽然定义时长度可变,但还不是动态数组,在运行期内不能再改变,受 制于其它因素,它只是提供了一个部分解决方案。


《再再论指针》修订版(5).doc 将本文的Word文档下载到电脑 下载失败或者文档不完整,请联系客服人员解决!

下一篇:美国圣地亚哥州立大学研究生申请之生物学

相关阅读
本类排行
× 注册会员免费下载(下载后可以自由复制和排版)

马上注册会员

注:下载文档有可能“只有目录或者内容不全”等情况,请下载之前注意辨别,如果您已付费且无法下载或内容有问题,请联系我们协助你处理。
微信: QQ: