線上訂房服務-台灣趴趴狗聯合訂房中心
發文 回覆 瀏覽次數:3219
推到 Plurk!
推到 Facebook!

關於BCB Alignment 觀念

答題得分者是:aftcast
ddy
站務副站長


發表:262
回覆:2105
積分:1169
註冊:2002-07-13

發送簡訊給我
#1 引用回覆 回覆 發表時間:2009-07-24 18:04:05 IP:122.147.xxx.xxx 訂閱
小弟有一個問題苦思好久,希望提出來與各位大大討論
關於BCB 在struct 對齊的問題

程式如下:
[code cpp]
AnsiString s,t;
#pragma pack(push,1)
struct system_conf {
char name[16];
char id;
char mode;
};
struct aa{
char a;
int b;
char c;
} ;
#pragma pack(pop)
t.sprintf("pack(8): struct system_conf size=%d,struct aa size=%d",sizeof(struct system_conf),sizeof(struct aa));
Memo1->Lines->Add(t);

struct system_conf sys;
struct aa test;

sprintf(sys.name,"%s","ABCDEFGHIJKLMNO");
sys.id = 0x99;
sys.mode = 0xaa;
test.a ='a';
test.b =0x12345678;
test.c ='c';
t.sprintf("struct system_conf:");
Memo1->Lines->Add(t);

t="";
for(int i=0;i {
char *p = (char*) &sys;
t = s.sprintf("x ",(unsigned char)*(p i));
}
Memo1->Lines->Add(t);
s="";t="";
t =s.sprintf("struct aa:");
Memo1->Lines->Add(t);
for(int i=0;i {
char *p = (char*) &test;
t = s.sprintf("x ",(unsigned char)*(p i));
}
Memo1->Lines->Add(t);
[/code]
我將#pragma pack(push,n) n 設為 1,2,4,8 每次執行記錄其結果如下:
pack(1): struct system_conf size=18,struct aa size=6
struct system_conf:
41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 00 99 aa
struct aa:
61 78 56 34 12 63

pack(2): struct system_conf size=18,struct aa size=8
struct system_conf:
41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 00 99 aa
struct aa:
61 88 78 56 34 12 63 00

pack(4): struct system_conf size=18,struct aa size=12
struct system_conf:
41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 00 99 aa
struct aa:
61 00 00 00 78 56 34 12 63 f5 12 00

pack(8): struct system_conf size=18,struct aa size=12
struct system_conf:
41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 00 99 aa
struct aa:
61 00 00 00 78 56 34 12 63 f5 12 00

(紅色是編譯器填充的資料)
問題....
1. struct system_conf 的size 為何不會隨 #pragma pack 的設定而變化…
是否是因為 sys.id 與sys.mode 為 char ,鄰接於char[]之後無法識別?
在小弟的認知裡,struct system_conf若為 pack(4) 其長度應為 20
其內容應為:
41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 00 99 aa 00 00
難道是我的觀念有誤?
2. struct aa 在 #pragma pack(4) 與(8) 時結果一樣,是否是因為在32位元系統裡(8)與(4)一樣?



附帶一提,好玩是的在gcc 下結果還不一樣...
我把程式改寫後以MinGW 的gcc 來編譯執行
[code cpp]
struct system_conf {
char name[16];
char id;
char mode;
} __attribute__ ((aligned (4)));
[/code]
其長度竟然是 20
資料為:41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 00 99 aa 00 00
應該是正確的
__attribute__ ((aligned (4))) 和 #pragma pack(4) 的結果還不同…
到底是那裡的問題呢??
編輯記錄
ddy 重新編輯於 2009-07-24 18:27:21, 註解 無‧
ddy 重新編輯於 2009-07-24 18:35:02, 註解 無‧
ddy 重新編輯於 2009-07-24 18:46:27, 註解 無‧
ddy 重新編輯於 2009-07-24 18:52:41, 註解 無‧
aftcast
站務副站長


發表:81
回覆:1485
積分:1763
註冊:2002-11-21

發送簡訊給我
#2 引用回覆 回覆 發表時間:2009-07-27 12:15:22 IP:210.64.xxx.xxx 訂閱
題目出的真好~!  難得有這麼高水準的問題 : )

我把自己研究已久的感言發表一下囉,若有錯誤,尚請指教!

前言:

1/ 所謂的alignment指的是對齊記憶體的位址線,會這樣做的原因和cpu暫存器很有關係,比如16位元時(286時代)通常為了加速而對齊2byte,即偶數位址,因為暫存器是16位元的。
2/ compiler額外填入的值叫padding,又可分為internal padding和trailing padding。internal是指成員間所加入。trailing是最後一成員後面額外加入的,這個觀念很重要,也是本題解答的關鍵。

規則: (部份是個人研究的結果,不敢完全保證正確)

1/ 若成員型別的size大於等於指定的alignment,那就要對齊alignment倍數的位址上。
2/ 若成員型別的size小於alignment,也要考慮次alignment(個人研究心得)
3/ 成員若是一陣列型別,那要考慮的是陣列所指向的型別,而非本身陣列的大小。這點用說的可能不好理解。舉例來說,short[16]這個成員,你要判別的不是32bytes是否有大於等於或小於alignment,而是short(2Bytes)這個型別的大小是否大於等於或小於alignment。

重要說明:
針對規則2的部份:

以修改ddy下面的例子來說,若
struct aa{
char a;
//int b;
short b;
char c;
} ;
以4bytes來做aligment,struct的內容為
61 d4 78 56 63 00 (size=6,並非8) <<---對最後只補一個00更疑惑? 先別急著想,最後有答案…
為何會加入d4這一個byte? 因為short(2byte)比指定的alignment4小,但它還是要對齊自己的2byte(對暫存器來說,有加速到),即偶數位址,即我所謂的次對齊。從此觀點可以解答為何pack8與pack4的答案會是一樣的。因為例子中所有的型別都小於8bytes,所以就各自align (次對齊)
可以把ddy的例子改成如下觀查之:
struct aa{
char a;
__int64 b;
char c;
} ;
這時候pack8就會看出來有不同。因為__int64等於8bytes。alignment就產生了變化,即一般所了解的情形。
61 50 fe 73 01 00 00 00 78 56 34 12 00 00 00 00 63 00 00 00 01 00 00 00


針對trailing padding的進一步解說: (解決為何ddy例子中所有的system_conf 的size都一樣)

一般來說,成員間的padding大家都可以理解,但最後一個成員的後面到底要不要padding?? 有想過嗎?
個人研究的結果,trailing padding要考慮的是當自定型別struct若為應用在陣列上,會發生什麼事?!
比如說
system_conf foo[10];
為了能正確的指出每個陣列的元素,那麼元素的大小就要正確。在此,元素是一個自定的struct型別。想像一下,有trailing padding與否,會有什麼差別?
以本例來說,因為struct裡通通都是char型別(請額外對照一下規則3),所以沒有trailing padding是ok的,一切都不會出問題。(再往下看可能更了解)
若把system_conf修改為
struct system_conf {
char name[16];
int id;
char mode;
}; //然後alignment4
內容為:41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 00 99 00 00 00 aa f5 12 00 (size=24)
為了讓int id 能夠在下個struct元素時(比如說foo[2])有對齊到4的倍數位址,trailing padding是必要的!!!
但若所有的型別都是char,即1個byte,那整個陣列從1至10,其實全都有對齊到,或者說全都無需對齊。這就是為何system_conf 的size都一樣!

最後,進一步的,請思考最上面的問題 : 61 d4 78 56 63 00 <<---為何只補一個byte(00)
明明是alignment4,你期待的提補3個byte是吧?
因為short是2byte,它只要對齊偶數位址就可以,故僅把trailing padding加入1個byte就足夠了! 想想,是這樣沒錯吧!? : ) 至於,若是加入你要的3個bytes,那是否也可以呢? 當然也是正確的,只是浪費一些空間而已。這或許也說明了
MinGW 的gcc 的結果是符合ddy的預期,且gcc編的程式也不會出錯!!

borland compiler 似乎會精簡記憶體的浪費,但依舊保持程式的正確性! (個人心得)

希望這篇文章對想深入了解高深一點的技術的同好有些幫助~! Good Luck
------


蕭沖
--All ideas are worthless unless implemented--

C++ Builder Delphi Taiwan G+ 社群
http://bit.ly/cbtaiwan
編輯記錄
aftcast 重新編輯於 2009-07-27 12:24:49, 註解 無‧
ddy
站務副站長


發表:262
回覆:2105
積分:1169
註冊:2002-07-13

發送簡訊給我
#3 引用回覆 回覆 發表時間:2009-07-28 10:56:25 IP:122.147.xxx.xxx 訂閱
蕭兄回答的才真是高水準的回答! 小弟拜讀後受益了
經蕭兄指點後,再進行一些實驗,觀念更穩固了 ^__^
系統時間:2024-11-22 11:22:56
聯絡我們 | Delphi K.Top討論版
本站聲明
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。
2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。
3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇!