欧美三区_成人在线免费观看视频_欧美极品少妇xxxxⅹ免费视频_a级毛片免费播放_鲁一鲁中文字幕久久_亚洲一级特黄

如何在C#中模擬C++的聯(lián)合(Union)?[C#, C++]

系統(tǒng) 2865 0

如何在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)合:

//Code#01
union TokenValue
{
char _cval;
int _ival;
double _dval;
}
;

2 聯(lián)合的內(nèi)存布局與內(nèi)存使用情況。

下面我們來考察一下TokenValue的內(nèi)存布局。

首先,我們使用sizeof運(yùn)算符來獲取該聯(lián)合各個成員的內(nèi)存占用字節(jié)數(shù):

// Code#02
int _tmain( int argc,_TCHAR * argv[])
{
cout
" sizeof(char): " sizeof ( char ) endl;
cout
" sizeof(int): " sizeof ( int ) endl;
cout
" sizeof(double): " sizeof ( double ) endl;

return 0 ;
}


/**/ /*
*Output:
*sizeof(char):1
*sizeof(int):4
*sizeof(double):8
*
*/

這樣,分配給該聯(lián)合的內(nèi)存就是8個字節(jié)。

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

// Code#03
int _tmain( int argc,_TCHAR * argv[])
{
TokenValuetv;
// [_][_][_][_][_][_][_][_]

tv._cval
= ' K ' ;

// [X][_][_][_][_][_][_][_]

tv._ival
= 1412 ;

// [X][X][X][X][_][_][_][_]

tv._dval
= 3.14159 ;

// [X][X][X][X][X][X][X][X]

return 0 ;
}

3 第一次嘗試:在C#中模擬這種布局方式。

在C#中,要指定成員的內(nèi)存布局情況,我們需要結(jié)合使用StructLayoutAttribute特性、LayoutKind枚舉和FieldOffsetAttribute特性,它們都位于System.Runtime.InteropServices命名空間中。

下面我用struct來試著模擬上面的TokenValue聯(lián)合:

// Code#04
[StructLayout(LayoutKind.Explicit,Size = 8 )]
struct TokenValue
{
[FieldOffset(
0 )]
public char _cval;

[FieldOffset(
0 )]
public int _ival;

[FieldOffset(
0 )]
public double _dval;
}

我們知道,聯(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é)果,重則整個程序中止。請看下面的代碼:

// Code#05
unionTokenValue
{
char _cval;
int _ival;
double _dval;
char * _sval;
}
;

int _tmain( int argc,_TCHAR * argv[])
{
TokenValuetv;
tv._cval
= ' K ' ;
cout
tv._cval endl; // Line#01
cout tv._ival endl; // Line#02
cout tv._dval endl; // Line#03
cout tv._sval endl; // Line#04

return 0 ;
}

這里的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)行一番包裝:

//Code#06
class Program
{
static void Main( string []args)
{
Tokent=
new Token();

Console.WriteLine(t);
Console.WriteLine(t.GetTokenValue());

t.SetTokenValue('K');
Console.WriteLine(t);
Console.WriteLine(t.GetTokenValue());
}

}


public struct Token
{
private TokenValuetv;
private TokenKindtk;

public void SetTokenValue( char c)
{
tk=TokenKind.CharValue;
tv._cval=c;
}


public void SetTokenValue( int i)
{
tk=TokenKind.IntValue;
tv._ival=i;
}


public void SetTokenValue( double d)
{
tk=TokenKind.DoubleValue;
tv._dval=d;
}


public object GetTokenValue()
{
switch (tk)
{
case TokenKind.CharValue:
return tv._cval;
case TokenKind.IntValue:
return tv._ival;
case TokenKind.DoubleValue:
return tv._dval;
default :
return "NoValue";
}

}


public override string ToString()
{
switch (tk)
{
case TokenKind.CharValue:
return tv._cval.ToString();
case TokenKind.IntValue:
return tv._ival.ToString();
case TokenKind.DoubleValue:
return tv._dval.ToString();
default :
return "NoValue";
}

}


[StructLayout(LayoutKind.Explicit,Size=8)]
private struct TokenValue
{
[FieldOffset(0)]
public char _cval;
[FieldOffset(0)]
public int _ival;
[FieldOffset(0)]
public double _dval;
}


private enum TokenKind
{
NoValue,
CharValue,
IntValue,
DoubleValue
}

}


/**/ /*
*Output:
*NoValue
*NoValue
*K
*K
*
*/

由于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)的枚舉類型。例如,你可以:

//Code#07
System.DayOfWeekd=0;
Console.WriteLine(d);

該代碼能正確輸出Sunday——一個星期的第一天(西方習(xí)慣),也是該枚舉的第一個成員。

一般情況下,0對應(yīng)著枚舉的第一個成員(除非你在定義枚舉的時候,把第一個成員指定為別的值,并為別的成員賦予0值)。這樣,我們就不難看出代碼的輸出是合理的,而且代碼本身也是安全的。

6 別在模擬的聯(lián)合中同時使用值類型和引用類型!

到目前為止,我們所模擬的聯(lián)合中,所有的成員都是值類型,如果我們?yōu)樗尤胍粋€引用類型,例如String呢?

//Code#08
[StructLayout(LayoutKind.Explicit,Size=8)]
struct TokenValue
{
[FieldOffset(0)]
public char _cval;

[FieldOffset(0)]
public int _ival;

[FieldOffset(0)]
public double _dval;

[FieldOffset(0)]
public string _sval;
}

這樣,Code #06的代碼運(yùn)行時就會提示出錯:

Could not load type 'TokenValue' from assembly 'UnionLab, Version=1.0.1820.28531, Culture=neutral, PublicKeyToken=null' because it contains an object field at offset 0 that is incorrectly aligned or overlapped by a non-object field.

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!


參考資料:

如何在C#中模擬C++的聯(lián)合(Union)?[C#, C++]


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯(lián)系: 360901061

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

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦?。?!

發(fā)表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 成人国产一区二区三区 | 91福利免费观看 | 午夜影院欧美 | 97中文 | 男女生性毛片免费观看 | 日本精品a在线 | 波多野吉衣一区二区三区四区 | av在线免费观看播放 | 久萆app污版下载网站 | 亚洲精品乱码久久久久久久久久 | 久久久精 | 91免费公开视频 | 久久视频这里只精品99 | 深夜激情视频 | 国产人成午夜免视频网站 | 婷婷亚洲五月琪琪综合 | 热久久国产 | 日本毛片高清免费视频 | 日韩精品视频免费在线观看 | 福利片在线看 | 久操免费在线视频 | 美国免费黄色片 | 日本中文在线观看 | 日本三级不卡 | 国产成人免费视频网站视频社区 | 久久99精品久久久久久 | 又黄又爽的成人免费网站 | 高清视频一区 | 日韩精品视频在线播放 | 亚州精品天堂中文字幕 | 欧美一级黄色片在线观看 | 黄色av网站在线观看 | 国产美女的小嫩bbb图片 | 欧美一区二区精品 | 欧美一级一片 | 一区二区国产在线观看 | 精品特级毛片 | 免费在线一区二区 | 色屁屁www影院免费观看软件 | 欧美操片在线观看 | 天天做天天爱天天爽天天综合 |