閑聊c/c++: 再談內(nèi)存(c/c++,java,c#,js,objc中的大小端以及裝箱拆箱和統(tǒng)一類(lèi)型系統(tǒng))

2017.08.21-22.02.26-0-11.gif

本篇目的:

  • c/c++中如何判斷大小端的函數(shù)
  • c/c++中通過(guò)指針?lè)ǎ莆环ǐ@取多字節(jié)數(shù)據(jù)類(lèi)型中的各個(gè)字節(jié)
  • java/c#/js中如何獲取多字節(jié)數(shù)據(jù)類(lèi)型中的各個(gè)字節(jié)
  • 什么是裝箱和拆箱
  • 為什么要裝箱拆箱
  • java js c# objc中盡可能的避免裝箱拆箱的方法
  • 順便介紹js一些新的內(nèi)置類(lèi)(有些還處于實(shí)驗(yàn)性質(zhì))
  • 介紹一些simd相關(guān)知識(shí)

用c/c++實(shí)現(xiàn)測(cè)試大小端的函數(shù):

bool isLittleEdian() {
    union {
        unsigned int a;
        unsigned char b[4];
    }U;
    
    U.a = 1;

    if (U.b[0] == 1)
        return true;
    else
        return false;
}
  • 使用union關(guān)鍵來(lái)聲明一個(gè)聯(lián)合數(shù)據(jù)類(lèi)型,它可以實(shí)現(xiàn):以一種數(shù)據(jù)類(lèi)型存儲(chǔ)數(shù)據(jù),以另一種數(shù)據(jù)類(lèi)型來(lái)讀取數(shù)據(jù)。

例如我們用unsigned int a來(lái)進(jìn)行賦值,卻使用unsgined char b[idx]數(shù)組索引來(lái)讀取某個(gè)unsigned char的數(shù)值

  • c/c++中,內(nèi)存是根據(jù)變量的順序來(lái)分配的,從低到高。
    因此unsigned char b[4]在內(nèi)存中從低到高【左->右】的分布是:
    【b[0],b[1],b[2],b[3]】
內(nèi)存地址.png

閑聊c/c++: 談內(nèi)存(大/小端,高/低字節(jié),高/低地址)的結(jié)論:

  • 在小端模式中,數(shù)據(jù)的高字節(jié)保存在內(nèi)存的高地址中,而數(shù)據(jù)的低字節(jié)保存在內(nèi)存的低地址

當(dāng)我們U.a = 1賦值時(shí), 1 < 255,因此屬于最低字節(jié),其值保存在最低地址中(也就是b[0]的地址5831492所指向的byte中)。

因?yàn)? 低字節(jié)保存在內(nèi)存的低地址
所以: b[0] = 1

  • 在大端模式中,數(shù)據(jù)的高字節(jié)保存在內(nèi)存的低地址中,而數(shù)據(jù)的低字節(jié)保存在內(nèi)存的高地址

因?yàn)? 低字節(jié)保存在內(nèi)存的高地址(5831495,指向的是b[3]所在的byte)
所以: b[3] = 1

應(yīng)該很清楚明了的吧!

關(guān)鍵點(diǎn):高低字節(jié)、高低地址?。。?!

除了union外,c/c++中還有多種方式從unsigned int 中獲取某個(gè)unsigned char的數(shù)值:

指針?lè)?

//指針?lè)?void printUInt4ComponentByPointer(unsigned int n) {
    unsigned char* ptr = (unsigned char*)(&n);
    unsigned char char0 = *ptr;   //最低地址
    unsigned char char1 = *(ptr + 1); //指針移動(dòng)方式
    unsigned char char2 = ptr[2]; //使用[]變址操作符
    unsigned char char3 = ptr[3]; //使用[]變址操作符,最高地址

    printf("[%u,%u,%u,%u]\n",char3,char2,char1,char0);
}

移位法:

void printUInt4ComponentByShift(unsigned int n) {

    unsigned char bytes[4];
    bytes[0] = (n >> 24) & 0xFF;
    bytes[1] = (n >> 16) & 0xFF;
    bytes[2] = (n >> 8) & 0xFF;
    bytes[3] = n & 0xFF;

    printf("[%u,%u,%u,%u]\n", bytes[0], bytes[1], bytes[2], bytes[3]);
}
//測(cè)試:
printUInt4ComponentByPointer(257);
printUInt4ComponentByShift(257);
pointer_shift_test.png

在java,c#(事實(shí)上c#在unsafe{}中支持指針操作的),js等這些語(yǔ)言支持移位操作,只能使用移位法進(jìn)行分量的提取操作(或許每個(gè)語(yǔ)言類(lèi)庫(kù)有提供相關(guān)操作,請(qǐng)自己查api文檔)

public class Main {

    static void printUInt4ComponentByShift( int n) {

        byte[] bytes = new byte[4];

        bytes[0] = (byte)((n >> 24) & 0xFF);
        bytes[1] = (byte)((n >> 16) & 0xFF);
        bytes[2] = (byte)((n >> 8) & 0xFF);
        bytes[3] = (byte)(n & 0xFF);
        System.out.printf("[%d,%d,%d,%d]\n",bytes[0],bytes[1],bytes[2],bytes[3]);
    }

    public static void main(String[] args) {
        printUInt4ComponentByShift(257);//[0,0,1,1]
    }
}
  • 用java舉例子,其他語(yǔ)言都一樣(上面代碼稍作修改,用于c#,js,...等語(yǔ)言具有同等效果)
  • java中char是2個(gè)字節(jié),表示一個(gè)utf16字符。byte才是一個(gè)字節(jié),是值類(lèi)型
  • 大寫(xiě)的Byte是引用類(lèi)型,用于容器存儲(chǔ)時(shí)的裝箱使用。自jdk1.5開(kāi)始引入自動(dòng)裝箱拆箱。
  • 事實(shí)上,java中,所有的基本數(shù)據(jù)類(lèi)型都有對(duì)應(yīng)的首字母大寫(xiě)的同名的類(lèi),例如char->Char/byte->Byte.........,就是為了裝箱拆箱使用

裝箱(boxing)與拆箱(unboxing)

1. 什么是裝箱拆箱:

裝箱: 將值類(lèi)型轉(zhuǎn)換為引用類(lèi)型
拆箱: 將引用類(lèi)型轉(zhuǎn)換為值類(lèi)型

Integer i = 100; //裝箱
int j = i; //拆箱

用java代碼來(lái)分析一下:

  • 因?yàn)镮nteger是引用類(lèi)型,而100字面量是一個(gè)數(shù)值類(lèi)型
    將一個(gè)數(shù)值類(lèi)型賦值給引用類(lèi)型時(shí),就發(fā)生了裝箱

  • 這段代碼只能在jdk1.5以后使用,因?yàn)檫@是自動(dòng)裝箱拆箱

  • 如果在jdk1.5之前,只能使用 Integer i = new Integer(100)以及int j = i.intValue()

  • 裝箱的流程如下:

    首先: 因?yàn)?00是數(shù)值類(lèi)型,因此內(nèi)存是分配在內(nèi)

    然后: 使用new 操作符,查詢整個(gè),是否有連續(xù)的,可以分配sizeof(Integer)的內(nèi)存塊,如果有,則返回Integer的首地址,如果沒(méi)有連續(xù)容納的內(nèi)存塊,就需要不停的往下查找,直到找到連續(xù)內(nèi)存塊才返回,否則就報(bào)out of memory錯(cuò)誤

    最后: 將棧上分配的100賦值給堆分配的Integer中的成員變量,完成裝箱工作

  • 拆箱的流程正好和裝箱相反

  • 由此可見(jiàn),裝箱拆箱涉及到堆和棧的內(nèi)存分配,內(nèi)存拷貝以及內(nèi)存析構(gòu),是一個(gè)很耗時(shí)的操作!

  • 一次的裝箱拆箱不可怕,可怕的是成千上萬(wàn)的數(shù)據(jù)需要拆箱裝箱。例如java容器類(lèi),裝填的是Object,因此每次add基本數(shù)據(jù)類(lèi)型時(shí)候,需要裝箱,每次get(i)基本數(shù)據(jù)類(lèi)型時(shí),需要拆箱,當(dāng)每次你循環(huán)成千上萬(wàn)個(gè)數(shù)據(jù)時(shí),進(jìn)行成千上萬(wàn)次的裝箱或拆箱行為。js,objc也是如此,c#比較特別,我們下面來(lái)說(shuō)明!

2. 為什么要裝箱拆箱:

原因: 統(tǒng)一類(lèi)型系統(tǒng)
來(lái)自.net本質(zhì)論的解釋:
裝箱與拆箱是我們能夠統(tǒng)一的來(lái)考察類(lèi)型系統(tǒng),其中任何類(lèi)型的值都可以按對(duì)象處理

  • java中所有的類(lèi)都繼承自O(shè)bject
  • .net中,公共語(yǔ)言運(yùn)行庫(kù)中的類(lèi)都繼承自O(shè)bject
  • objc中,所有的類(lèi)都繼承自NSObject
  • js中,所有一切都是object

所以不可避免的,這些語(yǔ)言都會(huì)產(chǎn)生裝箱和拆箱行為,這是由其本質(zhì)所決定的?。。?!

對(duì)于c#來(lái)說(shuō),比較特別。在c#2.0之前,和java一樣,需要裝箱和拆箱。但是c#2.0引入了泛型機(jī)制,而且c#本身支持struct定義值類(lèi)型,class定義引用類(lèi)型,這些機(jī)制可以避免裝箱拆箱。因此在c#中,現(xiàn)在肯定都是用泛型容器類(lèi)。

c#非泛型容器類(lèi)則只能存儲(chǔ)object類(lèi)型,和java一樣會(huì)自動(dòng)進(jìn)行裝箱拆箱工作。(其實(shí)可以查看java/.net中間語(yǔ)言代碼,就會(huì)非常清晰的了解整個(gè)機(jī)制和流程

隨便說(shuō)一下,java的泛型系統(tǒng)到目前為止還是所謂的"擦除式"泛型系統(tǒng),也就是在編寫(xiě)的時(shí)候,是用泛型的方式寫(xiě)代碼,但實(shí)際在生成字節(jié)碼運(yùn)行時(shí)的時(shí)候,泛型表示全部變?yōu)閛bject類(lèi)型表示,因此也是無(wú)法消除裝箱拆箱的行為。

為什么java會(huì)如此,很簡(jiǎn)單,為了兼容性,Java虛擬機(jī)使用廣泛,而裝箱拆箱需要重寫(xiě)底層機(jī)制,導(dǎo)致不兼容性?。?/p>

.net是看到了java的不良之處,從一開(kāi)始設(shè)計(jì)時(shí)候就考慮好了,在.net 2.0之前雖然沒(méi)支持泛型,但是struct/class可是一開(kāi)始就已經(jīng)擁有了!

3. 如何避免裝箱拆箱?

  • c#中直接使用泛型類(lèi)
  • objc中.m支持c語(yǔ)言,.mm支持c++,所以實(shí)在要追求效率,就使用c語(yǔ)言或c++ stl庫(kù),clang還是非常棒的東西?。?a target="_blank" rel="nofollow">facebook pop即使這么干的,很棒的庫(kù),對(duì)吧!
  • java中,覺(jué)得好像沒(méi)什么可能性了,即使用ndk,除非你全部使用ndk編寫(xiě),否則即使jni導(dǎo)出給java使用,也面臨這c++數(shù)據(jù)類(lèi)型到j(luò)ava數(shù)據(jù)類(lèi)型的包裝。所以并不合算
  • 來(lái)聊一下js吧,js一定會(huì)成為王者之劍。現(xiàn)在的js中增加了很多基本數(shù)據(jù)類(lèi)型的Array內(nèi)置對(duì)象:
js基本數(shù)據(jù)類(lèi)型數(shù)組.png

沒(méi)驗(yàn)證過(guò),但是90%的可能不會(huì)產(chǎn)生裝箱和拆箱行為!

simd_data_type.png

simd-單指令多數(shù)據(jù)流指令集,游戲中的必備技術(shù)。16byte字節(jié)對(duì)齊(續(xù)談內(nèi)存,我們來(lái)聊字節(jié)對(duì)齊和padding),特適合3D數(shù)學(xué)表示。(關(guān)于simd方面的參考資料,最好的就是id soft(發(fā)表在intel的5篇論文,詳細(xì)描述了Doom3中simd數(shù)學(xué)庫(kù)的性能優(yōu)化及測(cè)試,約翰卡馬克就是3D引擎之神,幾年前Doom3已經(jīng)開(kāi)源了,可以在github中下載到,simd數(shù)學(xué)庫(kù)可以參考:Doom3 數(shù)學(xué)庫(kù)以及微軟的XNA Math庫(kù),也是Simd實(shí)現(xiàn)

還有就是增加了紅黑色之類(lèi)的數(shù)據(jù)結(jié)構(gòu)。由此可見(jiàn),js所圖宏大啊!一定要跟蹤js的發(fā)展!

今天到此為止,下一篇我們聊一下nodejs的核心庫(kù)libuv中的幾個(gè)核心宏,你會(huì)看到那是多么的美妙!

一本touch到統(tǒng)一類(lèi)型系統(tǒng)本質(zhì)的書(shū).jpg

非常有深度,信息量巨大的一本書(shū)!!


.Net 本質(zhì)論作者: Don Box: 也是COM本質(zhì)論的作者
"關(guān)于COM,沒(méi)有任何人能闡釋得比Don Box更棒!"
這是微軟"COM guy" Charlie Kindel對(duì)Don Box的評(píng)價(jià)!
.net以前被稱(chēng)為"COM+"


ID soft發(fā)布在intel上的論文,一共7篇,詳細(xì)的測(cè)試,實(shí)現(xiàn)過(guò)程描述,涉及骨骼動(dòng)畫(huà),陰影計(jì)算,骨骼動(dòng)畫(huà)流水線等等,其實(shí)整個(gè)3D都是建立在數(shù)學(xué)基礎(chǔ)上的。因此如果想搞圖形開(kāi)發(fā),這幾篇論文絕對(duì)就是最好的選擇??!
https://software.intel.com/en-us/articles/optimizing-the-rendering-pipeline-of-animated-models-using-the-intel-streaming-simd-extensions

我在unity3d中實(shí)現(xiàn)了Doom3場(chǎng)景地圖的渲染和Doom3 MD5骨骼動(dòng)畫(huà)渲染,其實(shí)最終目的就是為了js webgl demo(我的閑聊js系列文章),兩個(gè)無(wú)頭男人是MD5骨骼動(dòng)畫(huà),一個(gè)靜態(tài),一個(gè)運(yùn)動(dòng),而小美女是著名的她:

unity_girl.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容