如何在C#中模擬C++的聯(lián)合(Union)?[C#, C++]
How To Simulate C++ Union In C#?
Updated on Sunday, December 26, 2004
Written by Allen Lee
0 如何閱讀本文?
如果你...
- ...希望了解聯(lián)合的概念,請閱讀“什么是聯(lián)合?”。
- ...希望了解聯(lián)合的內(nèi)存使用情況,請閱讀“聯(lián)合的內(nèi)存布局與內(nèi)存使用情況?!?。
- ...希望了解如何在C#中模擬聯(lián)合,請閱讀“第一次嘗試:在C#中模擬這種布局方式。”。
- ...希望了解在C++中使用聯(lián)合有哪些要注意的地方,請閱讀“在實(shí)際的C++代碼中,我們是如何使用聯(lián)合的?”。
- ...希望了解如何在C#中更好的使用模擬的聯(lián)合,請閱讀“第二次嘗試:改進(jìn)型的聯(lián)合模擬?!薄?
- ...希望了解在C#中使用模擬的聯(lián)合有些什么注意事項(xiàng),請閱讀“別在模擬的聯(lián)合中同時使用值類型和引用類型!”。
- ...希望了解為何我要寫這篇文章,請閱讀“為什么要在C#里面模擬這個用處不大的東西?”。
否則...
- ...你應(yīng)該從頭到尾閱讀全文。
1 什么是聯(lián)合?
聯(lián)合(Union)是一種特殊的類,一個聯(lián)合中的數(shù)據(jù)成員在內(nèi)存中的存儲是互相重疊的。每個數(shù)據(jù)成員都在相同的內(nèi)存地址開始。分配給聯(lián)合的存儲區(qū)數(shù)量是“要包含它最大的數(shù)據(jù)成員”所需的內(nèi)存數(shù)。同一時刻只有一個成員可以被賦給一個值。
下面我們來看看C++中如何表達(dá)聯(lián)合:







2 聯(lián)合的內(nèi)存布局與內(nèi)存使用情況。
下面我們來考察一下TokenValue的內(nèi)存布局。
首先,我們使用sizeof運(yùn)算符來獲取該聯(lián)合各個成員的內(nèi)存占用字節(jié)數(shù):

















這樣,分配給該聯(lián)合的內(nèi)存就是8個字節(jié)。
接著,我們來看看具體使用該聯(lián)合時,所分配的內(nèi)存的字節(jié)占用情況如何:




















3 第一次嘗試:在C#中模擬這種布局方式。
在C#中,要指定成員的內(nèi)存布局情況,我們需要結(jié)合使用StructLayoutAttribute特性、LayoutKind枚舉和FieldOffsetAttribute特性,它們都位于System.Runtime.InteropServices命名空間中。
下面我用struct來試著模擬上面的TokenValue聯(lián)合:













我們知道,聯(lián)合的每個數(shù)據(jù)成員都在相同的內(nèi)存地址開始,通過把[FieldOffset(0)]應(yīng)用到TokenValue的每一個成員,我們就指定了這些成員都處于同一起始位置。當(dāng)然,我們得事先告訴.NET這些成員的內(nèi)存布局由我們來作主,把LayoutKind.Explicit枚舉傳遞給StructLayoutAttribute特性的構(gòu)造函數(shù),并應(yīng)用到TokenValue,.NET就不會再干涉該struct的成員在內(nèi)存中的布局了。另外,我顯式的把TokenValue的大小設(shè)置為8字節(jié),當(dāng)然,這樣做是可選的。
4 在實(shí)際的C++代碼中,我們是如何使用聯(lián)合的?
在實(shí)際的C++代碼中,我們應(yīng)盡量避免讓客戶端直接使用聯(lián)合,Code #03就是一個很好的反面例子了。為什么呢?熟悉C/C++的開發(fā)人員都知道,聯(lián)合提供我們這樣一個節(jié)省空間的儲存方式,是要我們付出一定的代價的。這個代價就是代碼的安全性,不恰當(dāng)?shù)厥褂寐?lián)合可能會導(dǎo)致程序崩潰的。
由于每一次只有一個聯(lián)合成員處于激活狀態(tài),如果我們不小心或者因?yàn)槠渌蚴褂锰幱谛菝郀顟B(tài)的成員,輕則得到錯誤的結(jié)果,重則整個程序中止。請看下面的代碼:




















這里的TokenValue比起Code #01的僅僅多了一個_sval,它是C風(fēng)格的字符串,實(shí)質(zhì)上,它是指向字符串的第一個字符的指針,它占用4字節(jié)的內(nèi)存空間。
當(dāng)程序運(yùn)行到Line #04時,就會出現(xiàn)Unhandled Exception,程序中止,并指出_sval的值非法(即所謂的“野指針”)。程序無法把它的值輸出控制臺,然而,Line #01 ~ Line #03都能輸出,只是Line #02和Line #03所輸出的值是錯誤的而已。
實(shí)際的應(yīng)用中,我們一般不會看到如此低級且顯而易見的錯誤,但復(fù)雜的實(shí)際應(yīng)用中,不恰當(dāng)?shù)厥褂寐?lián)合的確會為我們帶來不少的麻煩。
5 第二次嘗試:改進(jìn)型的聯(lián)合模擬。
一般情況下,聯(lián)合作為一種內(nèi)部數(shù)據(jù)的儲存手段,沒有必要讓客戶端對其有所了解,更沒必要讓客戶端直接使用它。為了使我們的聯(lián)合模擬用起來更安全,我們需要對它進(jìn)行一番包裝:






























































































由于Token是值類型,實(shí)例化時,對應(yīng)的成員(tv和tk)會自動被賦予與之對應(yīng)的零值。此時,tv._cval為'\0'、tv._ival和tv._dval均為0(實(shí)質(zhì)上它們是同一個值在不同的類型中的表現(xiàn))。而tk也被自動賦予0:
tk = 0;
這里,你無需進(jìn)行強(qiáng)類型轉(zhuǎn)換,0是任何枚舉的默認(rèn)初始值,.NET會負(fù)責(zé)把0轉(zhuǎn)換成對應(yīng)的枚舉類型。例如,你可以:



該代碼能正確輸出Sunday——一個星期的第一天(西方習(xí)慣),也是該枚舉的第一個成員。
一般情況下,0對應(yīng)著枚舉的第一個成員(除非你在定義枚舉的時候,把第一個成員指定為別的值,并為別的成員賦予0值)。這樣,我們就不難看出代碼的輸出是合理的,而且代碼本身也是安全的。
6 別在模擬的聯(lián)合中同時使用值類型和引用類型!
到目前為止,我們所模擬的聯(lián)合中,所有的成員都是值類型,如果我們?yōu)樗尤胍粋€引用類型,例如String呢?
















這樣,Code #06的代碼運(yùn)行時就會提示出錯:
TokenValue初始化的時候,_cval、_ival和_dval都能正確的被賦予對應(yīng)的零值,而這些零值也能被統(tǒng)一起來(別的值就不行了)。但_sval不同,它是引用類型,如果沒有顯示初始化為某個有意義的值,它將被賦予null值!這個null值跟之前的有意義的零值是不能被統(tǒng)一起來的!所以,要么你就去掉這個_sval,要么就重新定義它的起始位置(當(dāng)然,你也得去掉Size=8!),但這樣一來,TokenValue就不再稱得上聯(lián)合的模擬了。
在C++中,我們可以直接使用指針來解決這個問題,如Code #05,但C#中,問題就會變得有點(diǎn)辣手。如果你有興趣的話,可以使用不安全代碼(Unsafe code)來試著解決,但這樣一來,你的代碼又會引入一些新的問題。
7 為什么要在C#里面模擬這個用處不大的東西? [NEW]
相信很多人都有這樣一個疑問:為什么要在C#里面模擬這個用處不大的東西?就我個人來說,我始終堅(jiān)信事物的存在必定有它的理由,否則就不會存在。其實(shí),聯(lián)合在我們平時的編碼中的確很少用到,但在某些情況下,我們必須使用它!.NET為我們提供巨大的便利的同時,也不忘讓我們能夠與非托管代碼交互。你知道,早期的Win32 API使用C來完成的,這里面就有很多函數(shù)的參數(shù)是以聯(lián)合的形式表達(dá)的,要在C#中跟這些API交互,我們就得“尊重”原函數(shù)的用法約束。
8 終點(diǎn)與起點(diǎn)的交界處。
回顧整個探索旅程,我們?yōu)榱耸褂寐?lián)合節(jié)省空間的優(yōu)勢,開始了這個模擬的探索,然而,為了彌補(bǔ)聯(lián)合的不足,我們對這個模擬進(jìn)行了一番包裝,增加了不少額外的代碼,直到后來,又發(fā)現(xiàn)了在這個模擬中同時使用值類型的成員和引用類型的成員所引發(fā)的問題,我們一直都沒有停止過探索和思考。正如馬斯洛的需要層次理論所描述的,人只要低層次的需要被滿足,馬上就會轉(zhuǎn)向更高的需要層次,一級一級的,直到攀上最高峰為止。
關(guān)于在C#中模擬C++的聯(lián)合這個話題,我并沒有在本文中給予你一個完整的展示,相反,我為你展示的僅僅是一個探索的起點(diǎn),希望為你帶來一絲靈感,讓你根據(jù)自己的實(shí)際情況來定制你的探索旅程。Have a good trip!
參考資料:
- Stanley B.Lippman,Josee Lajoie; 《C++ Primer中文版(第三版)》 ;潘愛民,張麗譯;中國電力出版社 2002
- Microsoft .NET Framework SDK Documentation,Microsoft Corp. 2004
- Allen Lee; 《關(guān)于枚舉的種種 [C#, IL, BCL] 》
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
