首页 > 代码库 > Do you master on array in C ?

Do you master on array in C ?

Do you master on array in C ?


由于新标准C99的支持变长数组, 几乎C的标准特性就是看着gcc来的(Linux 内核严重依赖GCC)


int mani()
{
             const int a = 10;
             int array[a];
             return 0;
}

这段代码能过编译吗?

在2012年是过不了的,2014年就可以了(时间改变一切啊~)


在VC++6.0上做的测试结果

sdev98\bin\hello.c(7) : error C2057: expected constant expressionf:\vc++6.0\microsoft visual studio\common\msdev98\bin\hello.c(7) : error C2466: cannot allocate an array of constant size 0f:\vc++6.0\microsoft visual studio\common\msdev98\bin\hello.c(7) : error C2133: ‘array‘ : unknown sizeError executing cl.exe.

过不了。选VC6.0的原因是因为这家伙的编译器的规范和早期的ANSI是符合的很好的.早期的ANSI标准不支持变长数组的特性


但是!C99明文规定, C语言加入了变长数组的特性



#include <stdio.h>
#include <stdlib.h>

int main()
{
        int a = 10;

	char b[a];

	int *p = &a;

	(*p)++;

	printf("%d %d\n", sizeof(b), a);

	return 0;
}

能过吗?能!


下面的能过吗?不能!

int a = 10;
int array[a] ;
int main()
{
            return 0l
}


上面三个问题能不能答对都不重要了, 下面搞定数组的时候到了,逃避不了新特性——数组变长



          一直说数组变长,这里其实有两种类型的“变长”—— VLA (varible length array) & VM (varible modified,that is, a pointer to a VLA type)


节选自C99标准(数组的就下面这部分)

Array declarators



Constraints

回答,何为数组?

1          In addition to optional type qualifiers and the keyword static, the [ and ] may delimit an expression or *. 

下面if就开始分析情况了...

If they delimit an expression (which specifies the size of an array), the expression shall have an integer type. If the expression is a constant expression, it shall have a value greater than zero. 

如果数组的长度是限定的,那么array[exp]中的exp应该是一个整形数

如果是个常量表达式,这个表达式的值要大于0(知道这里肯定有人会说struct里array[0]的用法,别急后面会分析)


The element type shall not be an incomplete or function type. The optional type qualifiers and the keyword static shall appear only in a declaration of a function parameter with an array type, and then only in the outermost array type derivation.


什么样的数组可以变长?
2           Only an ordinary identifier (as defined in 6.2.3) with both block scope or function prototype scope and no linkage shall have avariably modified type. If an identifier is declared to be an object with static storage duration, it shall not have a variable length array type.

我还是把6.2.3截屏出来,这样好论证,反正比较短


这里的ordinary identifier 最后一项“all other identifiers called ordinary identifiers” ,这里就注意这里的ordinary identifier就是四个选项“—” 的最后一项,而不包括前面三项,那么值得注意的就是struct不属于ordinary identifier

int a = 10;
int array[a];
int main()
{
           return 0;
}
这段代码是过不了gcc的编译的,数组变长发生在block(就是main函数的{} )之外,



Semantics
3 If, in the declaration ‘‘T D1’’, D1 has one of the forms:


D[ type-qualifier-list opt assignment-expression opt ]

D[ static type-qualifier-list opt assignment-expression ]

D[ type-qualifier-list static assignment-expression ]

D[ type-qualifier-list opt * ]


and the type specified for ident in the declaration ‘‘T D’’ is ‘‘derived-declarator-type-list T ’’, then the type specified for ident is ‘‘derived-declarator-type-list array of T ’’. 121)
 

数组两大类型,incomplete type 和complete type
4    

先介绍了什么是incomplete type.

  If the size is not present, the array type is anincomplete type.If the size is * instead of being an expression, the array type is a variable length array type of unspecified size, which can only be used in declarations with function prototype scope; 

注意,这里的*,是省略的意思,不是真的在[ ]写个*


element_type name[size]; 

这里size缺失,形成element_type name[]; 叫varible length array可变长数组,其实我们经常用

char string[] = "hello world!\n";


什么是complete type

                such arrays are nonetheless complete types. If the size is an integer constant expression and the element When several ‘‘array of’’ specifications are adjacent, a multidimensional array is declared. Thus, * can be used only in function declarations that are not definitions (see 6.7.5.3). type has a known constant size, the array type is not a variable length array type; otherwise, the array type is a variable length array type.



5 If the size is an expression that is not an integer constant expression: if it occurs in a declaration at function prototype scope, it is treated as if it were replaced by *; otherwise, each time it is evaluated it shall have a value greater than zero.

如果size是一个值不是整形常量表达式的时候,如果这种情况发生在函数声明的时候,把这个不是常量表达式的size当作*,就是省略掉了,不外乎就是数组传参数是以指针的形式传递的,这里道出了数组传参的本质来源.

如果不是发生在函数声明的参数中,那么这个时候size必须是个大于0的整数.


The size of each instance of a variable length array type does not change during its lifetime. Where a size expression is part of the operand of asizeof operator and changing the value of the size expression would not affect the result of the operator, it is unspecified whether or not the size expression is evaluated.
任何varible length array的大小在其整个生命周期都不会发生变化.


#include <stdio.h>
#include <stdlib.h>

int main()
{
	int a = 10;

	char b[a];

	int *p = &a;

	(*p)++;

	printf("%d %d\n", sizeof(b), a);

	return 0;
}

打印什么?10 11的原因就在于上面的解释 


@凯旋冲锋 发现了一种极赞的做法

#include <stdio.h>
#include <stdlib.h>

int main()
{
	const int a = 10;

	char b[a];

	printf("%d %d\n", sizeof(b), a);

	int *p = &a;

	(*p)++;

	printf("%d %d\n", sizeof(b), a);

	return 0;
}
看!输出结果是

10 10
11 11

为什么?怎么就变了不是说好整个生命周期数组长度不变的么,不是说好sizeof都不受影响么?wait,看看那个const,不能再赞的技巧,这里涉及到了一次指针的强制类型转换

看到int *p = &a; 这里&a 是const int a的地址,极其对于这个地址怎么解释呢?看它的数据类型——const int

类似的,char c;&c 就会把这个地址解释为储存的是一个char类型的变量.

Just think about it.

int*p = &a; p 指向的是一个int类型的变量,&a 指向的是一个const int的变量,上帝,如果你对p 解引用,然后你是可以改变p指向的值的,为什么?因为p告诉你它指向的类型是int类型 你想怎么改怎么改,而const int不行!

说白了就是同一个内存地址,你使用了不同的方法去解释它,一种解释方法是p,一种解释方法是&a


把这两者结合起来会发生很奇妙的作用,hold住!

当你通过a去访问这块内存的时候是read only的(const),当你去通过p去访问这块内存的时候是read&write的,int类型嘛.

a++;是不允许的,你尝试通过read only的方式去改变a标记的内存的数据

(*p)++;是允许的,p的访问方式是 r w


你会很淡定的发现只有一个error!warning和我推测的一样,是有强制类型转换.



但是不确定这个做法的后果,没有研究它对stack其他储存位置的影响,这几乎是一个“魔法师的魔术",但是你要知道它发生在stack上面,而且之前array的大小其实定下来了,我怀疑这种做法是一种”欺骗性的“,并没有实质上的去改变数组的大小,可以说是一种“合法的数组越界” (关于这点我们可以继续讨论,这种做法对于stack内存布局的影响)



6      For two array types to be compatible, both shall have compatible element types, and if both size specifiers are present, and are integer constant expressions, then both size specifiers shall have the same constant value. If the two array types are used in a context which requires them to be compatible, it is undefined behavior if the two size specifiers evaluate to unequal values.

这里多维数组的compatible问题我还不是很明白,以至于后面图片的的那个demo我也不是很明白,这里还要请

Essential On C & linux 的teammate多多指教



下面开始放例子了,介绍数组的用法,以及违法情况
7 EXAMPLE 1
float fa[11], *afp[17];
declares an array of float numbers and an array of pointers to float numbers.


8 EXAMPLE 2
Note the distinction between the declarations
extern int *x;
extern int y[];
The first declares x to be a pointer to int; the second declares y to be an array of int of unspecified size (an incomplete type), the storage for which is defined elsewhere.

9EXAMPLE 3
The following declarations demonstrate the compatibility rules for variably modified types.

extern int n;
extern int m;
void fcompat(void)
{
int a[n][6][m];
int (*p)[4][n+1];
int c[n][n][6][m];
int (*r)[n][n][n+1];
p = a; // invalid: not compatible because 4 != 6
r = c; // compatible, but defined behavior only if  n == 6 and m == n+1 这里不是很明白,感觉r 和c 和不来啊,总觉得维数都不一样
}


10 EXAMPLE 4 

              All declarations of variably modified (VM) types have to be at either block scope or function prototype scope. 

这句话就死死的把指向变长数组的指针——VM 的使用范围仅限于block 内部

             Array objects declared with the static or extern storage-class specifier cannot have a variable length array (VLA) type. 

任何具有静态或者外部链接特性的objects,都不能VLA(这几乎可以理解为,VLA只发生在栈上,如果我的观点有误,希望能够交流讨论)

             However, an object declared with the static storage-class specifier can have a VM type(that is, a pointer to a VLA type). Finally, all identifiers declared with a VM type have to be ordinary identifiers and cannot, therefore, be members of structures or unions.

VM最后不能是结构体member

 

图中的 int(*s)[m] 就是 VM 


最后不忘记讨论数组size == 0的情况,这里只能用在结构体里面

这是一种很经典的做法,以至于C99标准里面有特别声明,不是所谓的“奇迹淫巧”,这里时有正规说明的!哈哈

对于使用方法,下面的link我有总结

http://blog.csdn.net/cinmyheart/article/details/28985843

             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 aflexible array member. With two exceptions, the flexible array member is ignored. First, the size of the structure shall be equal to the offset of the last element of an otherwise identical structure that replaces the flexible array member with an array of unspecified length. 106) Second, when a . (or ->)
operator has a left operand that is (a pointer to) a structure with a flexible array member and the right operand names that member, it behaves as if that member were replaced with the longest array (with the same element type) that would not make the structure larger than the object being accessed; the offset of the array shall remain that of the flexible array member, even if this would differ from that of the replacement array. If this array would have no elements, it behaves as if it had one element but the behavior is undefined if any attempt is made to access that element or to generate a pointer one past it.

这种是flexible array member,注意区分 VLA和 VM










林荫道 霍贝玛 荷兰 1689年 140 × 103cm 油画 英国伦敦国家美术馆
    
此图展现了乡野的美丽风光,画面上宁静的乡间景致看似平淡,却耐人寻味,使观者心旷神怡。对称的小树于平稳中见动感,随小道向前远望还能看到左旁的教堂尖顶,右旁两幢高顶茅屋,车辙印在泥泞的村道上,表现出一种正在延续着的平静而艰难的生活,占有大部分画面的天空则云蒸霞蔚,美得令人陶醉。这幅画中还带着一种忧伤的讽刺意味,长长的林荫大道在灰暗的地平线处消失到一点,画中这种忧伤的讽喻也是霍贝玛现实生活的真实反映。




Do you master on array in C ?