關於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 這個情況就是使用了不屬於自己的內存空間,容易出現莫名其妙的錯誤,請一開始就申請需要的內存


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM