关于C99中的Flexible array member个人理解

关于C99中的Flexible array member个人理解
(原文见 C99 section §6.7.2.1, item 16, page 103)
下方是我个人的理解

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.
作为一个特列,具有大于一个成员的结构体最后一个成员可以是一个不完整的数组类型,这叫叫做可变数组成员
解析:从这里定义可以看出两点:
1 包含可变数组成员的结构体必须有两个以上的成员
struct st { double d[];};
编译器就会提示
error: flexible array member in a struct with no named members
2可变数组成员必须是最后一个
所以当我定义如下结构体的时候,
struct sa { int n; double d[]; int k; };
编译器就会提示
error: flexible array member not at end of struct

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.
第一种情况,结构体的大小等于 另外一个用变长数组替换了可变数组成员,其它部分都相同的结构体 的 最后一个成员的偏移量

这句话看上去不好理解,看个例子就明白了(就是C99中接下来的17节的例子)
EXAMPLE Assuming that all array members are aligned the same, after the declarations:
struct s { int n; double d[]; }; 这个就是原结构体
struct ss { int n; double d[1]; }; 这个就是用变长数组替换了可变数组成员,其它部分都相同的结构体
我们也可以定义
struct ss2 { int n; double d[2]; };
struct ss3 { int n; double d[100]; };
那么如下的表达式就具有相同的大小(在我的测试环境中是 8):
sizeof (struct s)
offsetof(struct s, d)
offsetof(struct ss2, d)
offsetof(struct ss3, d)
后面三个offsetof表达式,就是最后一个成员(相对于结构体)的偏移量

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,
第二种情况,操作符(.或者->)的左操作数是一个指向具有可变数组成员的结构体的指针,并且右操作数是这个可变数组成员
struct s *s1;
s1 = malloc(sizeof (struct s) + 16);
s1->d //左操作数是指向具有可变数组成员的结构体的指针 右操作数是这个可变数组成员

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;
这种表现就像这个成员被同种类型的最长数组取代一样,但是这个不会使当前使用的结构体变得更大;
解析:
struct s *s1;
s1 = malloc(sizeof (struct s) + 16);
s1->d[0] //左操作数是指向具有可变数组成员的结构体的指针 右操作数是这个可变数组成员
s1->d[1] //在所分配的对象之内,合法区域
s1->d[2] //不在所分配的对象之内, 属于非法区域,即不可定义

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
如果这个数组没有元素,它的表现就像有一个元素一样,但是如果试图去获取这个元素或者传递指针给这个元素,那么这种行为是不可定义的。

解析:为什么不可定义
因为使用了不属于自己的内存空间,不受控制,所以行为不可定义,如下这个例子:
struct s { int n; double d[];}; //定义结构体
struct s *s2;
s2 = malloc(sizeof (struct s)); //分配结构体空间,只分配了 int 的空间,数组空间为0
s2->n = 22;

s2->d[0] = 42; // undefined behavior
printf("%d \n", s2->n); //22
printf("%lf \n", s2->d[0]); //行为不可定义 因为这里使用了后面连续的内存空间,但是这些空间不属于这个对象

unsigned char *uc ;
printf("s2->d[0] value %f \n", s2->d[0]); // 42.000000 ,第一次打印,貌似没有问题
uc = (unsigned char *)&(s2->d[0])+6;
这里我们对double类型的第7个字节的内容做了改变 (实际情况中,这个内存地址可能被其它对象使用)
*uc = 'a'; //97
printf("s2->d[0] value %.15lf \n", s2->d[0]); // 136.000000000000000
重新打印之后,就发现值变了,是不是很危险,明明是42,怎么变成136了?
这里的解释会麻烦一些,涉及到double类型的存储
double是64位的,第一位是符号位(0为正,1为负), 其后11位是指数位(初始值1023),后面是52位的尾数
因为我们的初始值是赋为42,变为二进制为 0010 1010, 所以按照指数计数法为 1.0101 * 10^5, 尾数为 0101
故第一位为0, 后面的11位位 1023 + 5 = 1028 转化为二进制 100 0000 0100,所以整体为
0 100 0000 0100 0101 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
因为我的机器是Little-Endian(低地址放低位数据),所以第7个字节的数据为 0100 0101
现在经过我们的赋值操作*uc = 'a'; 这个字节的值变为97(0110 0001),所以整体变为
0 100 0000 0110 0001 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
那现在整体的double的值就变为
100 0000 0110 转为10进制 1030 - 1023 = 7, 尾数为 0001,
故整体的值为 1.0001 * 10^7 = 10001000 转化为10进制为 128 + 8 = 136, 完毕。

//再来修改一个值
s2->d[0] = 42;
uc = (unsigned char *)&(s2->d[0])+5;
*uc = 'a'; //97
printf("s2->d[0] value %.15lf \n", s2->d[0]); // 42.757812500000000
0 100 0000 0100 0101 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
因为是第6个字节,所以得到
0 100 0000 0100 0101 0110 0001 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
故整体为
1.010101100001 * 10^5 = 101010.1100001 转化为10进制为:
1*32 + 1*8 + 1*2 + 1 * 2^(-1) + 1*2^(-2) + 1*2^(-7)
=32+8+1+ 0.5 + 0.25 + 0.0078125
=42.7578125
因为printf默认lf只打印小数点后6位,所以输出完整的值需要制定位数

对于C99中给的例子
struct { int n; double d[1]; } *s1, *s2;
s2 = malloc(sizeof (struct s) + 6);
*dp = 42; // undefined behavior 这个情况就是使用了不属于自己的内存空间,容易出现莫名其妙的错误,请一开始就申请需要的内存

posted on 2020-07-23 15:28  子虚乌有  阅读(2818)  评论(0编辑  收藏  举报