C語言位域詳解和實(shí)例分析

前言

在探究OC底層源碼的時(shí)候,涉及到了聯(lián)合體和位域相關(guān)知識(shí)儡羔。比如objc_objct里面的isa_t就是一個(gè)聯(lián)合體等缀,isa_t里面就有位域類型的數(shù)據(jù)。今天我們就來好好學(xué)習(xí)一下位域祭陷。

目錄.png

簡(jiǎn)介

??位域哑蔫,也稱位段闸迷,在C語言中师溅,位段的聲明和結(jié)構(gòu)(struct)類似膝舅,但它的成員是一個(gè)或多個(gè)位的字段颠毙,這些不同長(zhǎng)度的字段實(shí)際儲(chǔ)存在一個(gè)或多個(gè)整型變量中。在聲明時(shí)蛀蜜,位段成員必須是整形或枚舉類型(通常是無符號(hào)類型)刻两,且在成員名的后面是一個(gè)冒號(hào)和一個(gè)整數(shù),整數(shù)規(guī)定了成員所占用的位數(shù)滴某。
??位域不能是靜態(tài)類型磅摹。不能使用&對(duì)位域做取地址運(yùn)算,因此不存在位域的指針霎奢,編譯器通常不支持位域的引用(reference)户誓。

以下程序則展示了一個(gè)位段的聲明:

struct CHAR 
{
    unsigned int ch   : 8;    //8位
    unsigned int font : 6;    //6位
    unsigned int size : 18;   //18位
};
struct CHAR ch1;

以下程序展示了一個(gè)結(jié)構(gòu)體的聲明:

struct CHAR2 
{
    unsigned char ch;    //8位
    unsigned char font;  //8位
    unsigned int  size;  //32位
};
struct CHAR2 ch2;

??第一個(gè)聲明取自一段文本格式化程序,應(yīng)用了位段聲明幕侠。它可以處理256個(gè)不同的字符(8位)帝美,64種不同字體(6位),以及最多262,144個(gè)單位的長(zhǎng)度(18位)晤硕。這樣悼潭,在ch1這個(gè)字段對(duì)象中,一共才占據(jù)了32位的空間舞箍。而第二個(gè)程序利用結(jié)構(gòu)體進(jìn)行聲明舰褪,可以看出,處理相同的數(shù)據(jù)创译,CHAR2類型占用了48位空間抵知,如果考慮邊界對(duì)齊并把要求最嚴(yán)格的int類型最先聲明進(jìn)行優(yōu)化,那么CHAR2類型則要占據(jù)64位的空間软族。

實(shí)例分析

下面的demo例子輸出什么

struct BitFields {
    unsigned int bit1 : 1;
    unsigned int bit2 : 1;
    unsigned int bit3 : 4;
    unsigned int bit4 : 2;
};

union my_isa_t {
    unsigned int bitfields;
    
    unsigned int bit1 : 1;
    unsigned int bit2 : 1;
    unsigned int bit3 : 4;
    unsigned int bit4 : 2;
};


union my_new_isa_t {
    unsigned int bitfields;
    struct {
        unsigned int bit1 : 1;
        unsigned int bit2 : 1;
        unsigned int bit3 : 4;
        unsigned int bit4 : 2;
    };
};

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
        
        struct BitFields bitFields = {};
        bitFields.bit1 = 0b0;
        bitFields.bit2 = 0b1;
        bitFields.bit3 = 0b1010;
        bitFields.bit4 = 0b11;
        
        union my_isa_t isa = {};
        isa.bitfields = 0b00000011;
        isa.bit1 = 0b0;
        isa.bit2 = 0b1;
        isa.bit3 = 0b1010;
        isa.bit4 = 0b11;
        
        union my_new_isa_t newIsa = {};
        newIsa.bitfields = 0b00000011;
        newIsa.bit1 = 0b0;
        newIsa.bit2 = 0b1;
        newIsa.bit3 = 0b1010;
        newIsa.bit4 = 0b11;
 
        NSLog(@"sizeof(bitfileds):%lu",sizeof(bitFields));
        NSLog(@"bitFields.bit1:%d",bitFields.bit1);
        NSLog(@"bitFields.bit2:%d",bitFields.bit2);
        NSLog(@"bitFields.bit3:%d",bitFields.bit3);
        NSLog(@"bitFields.bit4:%d",bitFields.bit4);
        
        NSLog(@"sizeof(isa):%lu",sizeof(isa));
        NSLog(@"isa.bit1:%d",isa.bit1);
        NSLog(@"isa.bit2:%d",isa.bit2);
        NSLog(@"isa.bit3:%d",isa.bit3);
        NSLog(@"isa.bit4:%d",isa.bit4);
        NSLog(@"isa.bitfileds:%d",isa.bitfields);
        
        NSLog(@"sizeof(newIsa):%lu",sizeof(newIsa));
        NSLog(@"newIsa.bit1:%d",newIsa.bit1);
        NSLog(@"newIsa.bit2:%d",newIsa.bit2);
        NSLog(@"newIsa.bit3:%d",newIsa.bit3);
        NSLog(@"newIsa.bit4:%d",newIsa.bit4);
        NSLog(@"newIsa.bitfields:%d",newIsa.bitfields);
        
        NSLog(@"0b1111:%d",0b1111);
    
    }
    return 0;
}
  • log打印如下


    log.png

??struct BitFileds 很多人都能理解刷喜,但是對(duì)union my_new_isa_t以及union my_isa_t 可能存在疑惑,我們一一進(jìn)行分析:

struct BitFileds

struct BitFileds {
    unsigned int bit1 : 1;
    unsigned int bit2 : 1;
    unsigned int bit3 : 4;
    unsigned int bit4 : 2;
};
  • 結(jié)構(gòu)體的每個(gè)成員都是獨(dú)立的立砸,struct BitFileds有4個(gè)位域類型的成員掖疮,一共占8位。其中bit1占第0位颗祝,bit2占第1位浊闪,bit3占第2~5位,bit4占第6~7位螺戳,結(jié)合內(nèi)存對(duì)齊可以知道struct BitFileds占4個(gè)字節(jié)搁宾。
  • struct BitFileds的內(nèi)存結(jié)構(gòu)圖如下


    結(jié)構(gòu)體&位域.png
  • 根據(jù)最后的整體內(nèi)存布局圖我們可以得到:
    bit1=0b0=0
    bit2=0b1=1
    bit3=0b1010=10
    bit4=0b11=3

union my_isa_t

union my_isa_t {
    unsigned int bitfileds;
    
    unsigned int bit1 : 1;
    unsigned int bit2 : 1;
    unsigned int bit3 : 4;
    unsigned int bit4 : 2;
};
  • 該聯(lián)合體有5個(gè)成員unsigned int類型的bitfields和4個(gè)位域bit1,bit2,bit3,bit4倔幼。

  • 由于4個(gè)位域成員是屬于聯(lián)合體的盖腿,因此4個(gè)位域是參考聯(lián)合體進(jìn)行內(nèi)存布局,所以每個(gè)位域的開始位置都是第0位

  • 因此翩腐,bitfields占用4個(gè)字節(jié)32位(第1~32位)鸟款,bit1占用1個(gè)二進(jìn)制位(第0位),bit2占用1個(gè)二進(jìn)制位(第0位)茂卦,bit3占用4個(gè)二進(jìn)制位(第0~3位)何什,bit4占用2個(gè)二進(jìn)制位(第0~1位)。

  • union my_isa_t 的內(nèi)存結(jié)構(gòu)圖如下


    聯(lián)合體&位域.png
  • 結(jié)合最后的整體布局內(nèi)存可以得到:
    bitfields=0b00001011=11
    bit1=0b1=1
    bit2=0b1=1
    bit3=0b1011=11
    bit4=0b11=3

union my_new_isa_t

union my_new_isa_t {
    unsigned int bitfileds;
    struct {
        unsigned int bit1 : 1;
        unsigned int bit2 : 1;
        unsigned int bit3 : 4;
        unsigned int bit4 : 2;
    };
};
  • 該聯(lián)合體有2個(gè)成員等龙,一個(gè)為unsigned int 類型的bitfileds处渣,一個(gè)為匿名結(jié)構(gòu)體,為了方便而咆,我們暫時(shí)把該匿名結(jié)構(gòu)體稱為s,s包含4個(gè)位域成員霍比。
  • bitfileds大小為4字節(jié),s大小也是4個(gè)字節(jié)暴备,所以聯(lián)合體my_new_isa_t大小為4字節(jié)悠瞬,bitfileds和s共同占用4字節(jié)內(nèi)存空間
  • 4個(gè)位域成員是屬于結(jié)構(gòu)體的涯捻,所以位域成員的內(nèi)存布局是在匿名結(jié)構(gòu)體s下浅妆,參考結(jié)構(gòu)體s。由于結(jié)構(gòu)體的成員占用的內(nèi)存是互相獨(dú)立的障癌,因此bit1占用s的第1位凌外,bit2占用s的第2位,bit3占用s的第3~6位涛浙,bit4占用s的第7~8位康辑。

整體內(nèi)存結(jié)構(gòu)圖如下

聯(lián)合體&結(jié)構(gòu)體&位域.png
  • 結(jié)合最后的整體內(nèi)存布局圖可以得到:
    bitfields=0b11101010=128+64+32+8+2=234
    bit1=0b0=0
    bit2=0b1=1
    bit3=0b1010=10
    bit4=0b11=3

結(jié)合demo中的三個(gè)例子大家應(yīng)該對(duì)位域有了深入的理解,對(duì)于位段成員類型不一致的情況轿亮,這里就不展開了疮薇,大家可以結(jié)合規(guī)則進(jìn)行內(nèi)存分析。

isa_t位域分析

  • 結(jié)合上面的三個(gè)例子我注,我們自己也可以畫出isa_t的內(nèi)存結(jié)構(gòu)圖了
  • isa_t根據(jù)cpu架構(gòu)bitfields也會(huì)有所不同
union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
#   define ISA_BITFIELD                                                      \
      uintptr_t nonpointer        : 1;                                       \
      uintptr_t has_assoc         : 1;                                       \
      uintptr_t has_cxx_dtor      : 1;                                       \
      uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
      uintptr_t magic             : 6;                                       \
      uintptr_t weakly_referenced : 1;                                       \
      uintptr_t deallocating      : 1;                                       \
      uintptr_t has_sidetable_rc  : 1;                                       \
      uintptr_t extra_rc          : 19
#   define RC_ONE   (1ULL<<45)
#   define RC_HALF  (1ULL<<18)

# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
#   define ISA_BITFIELD                                                        \
      uintptr_t nonpointer        : 1;                                         \
      uintptr_t has_assoc         : 1;                                         \
      uintptr_t has_cxx_dtor      : 1;                                         \
      uintptr_t shiftcls          : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
      uintptr_t magic             : 6;                                         \
      uintptr_t weakly_referenced : 1;                                         \
      uintptr_t deallocating      : 1;                                         \
      uintptr_t has_sidetable_rc  : 1;                                         \
      uintptr_t extra_rc          : 8
#   define RC_ONE   (1ULL<<56)
#   define RC_HALF  (1ULL<<7)

# else
#   error unknown architecture for packed isa
# endif
  • isa_t在arm64架構(gòu)下的內(nèi)存結(jié)構(gòu)圖

  • 首先是匿名結(jié)構(gòu)體的內(nèi)存結(jié)構(gòu)圖


    bits.png
  • 整體isa_t的內(nèi)存結(jié)構(gòu)圖


    isa_t.png
  • 具體位域字段含義如下

    • nonpointer:表示是否對(duì) isa指針 開啟指針優(yōu)化按咒。
      • 0:不優(yōu)化,是純isa指針但骨,當(dāng)訪問isa指針時(shí)励七,直接返回其成員變量cls。
      • 1:優(yōu)化奔缠,即isa 指針內(nèi)容不止是類地址掠抬,還包含了類的一些信息、對(duì)象的引用計(jì)數(shù)等校哎。
    • has_assoc:是否有關(guān)聯(lián)對(duì)象两波。
    • has_cxx_dtor:該對(duì)象是否有C++或Objc的析構(gòu)器,如果有析構(gòu)函數(shù),則需要做一些析構(gòu)的邏輯處理;如果沒有雨女,則可以更快的釋放對(duì)象。
    • shiftcls:存儲(chǔ)類指針的值阳准。開啟指針優(yōu)化的情況下氛堕,在 x86_64 架構(gòu)有 44位 用來存儲(chǔ)類指針,arm64 架構(gòu)中有 33位 野蝇。
    • magic:用于調(diào)試器判斷當(dāng)前對(duì)象是真的對(duì)象讼稚,還是一段沒有初始化的空間。
    • weakly_referenced:用于標(biāo)識(shí)對(duì)象是否被指向或者曾經(jīng)被指向一個(gè)ARC的弱變量绕沈,沒有弱引用的對(duì)象釋放的更快锐想。
    • deallocating:標(biāo)識(shí)對(duì)象是否正在釋放內(nèi)存。
    • has_sidetable_rc:對(duì)象的引用計(jì)數(shù)值是否有進(jìn)位乍狐。
    • extra_c:表示該對(duì)象的引用計(jì)數(shù)值赠摇。extra_rc只是存儲(chǔ)了額外的引用計(jì)數(shù),實(shí)際的引用計(jì)數(shù)公式:實(shí)際引用計(jì)數(shù) = extra_rc + 1浅蚪。
  • 通過位域成員藕帜,我們就能夠?qū)ξ挥虼淼亩M(jìn)制進(jìn)行方便的存取了,更重要的是大大節(jié)省了內(nèi)存空間的占用惜傲。

  • isa_t總結(jié)如下:isa_t聯(lián)合體有3個(gè)成員洽故,3個(gè)成員cls\bit\匿名結(jié)構(gòu)體s共同占用8字節(jié)的內(nèi)存空間,通過匿名結(jié)構(gòu)體里面的位域成員盗誊,可以對(duì)8字節(jié)空間的不同二進(jìn)制位進(jìn)行操作时甚,達(dá)到節(jié)省內(nèi)存空間的目的。

思考

  • 如果我們把匿名結(jié)構(gòu)體中的位域換成基本數(shù)據(jù)類型來表示哈踱,結(jié)合內(nèi)存對(duì)齊原則荒适,得額外增加多少內(nèi)存空間的消耗?
    我們可以算一下
// 在arm64下將位域換成基本數(shù)據(jù)類型
struct isa_t_notBitFields {
    unsigned char nonpointer;  // 1字節(jié)
    unsigned char has_assoc; // 1字節(jié)
    unsigned char has_cxx_dtor; // 1字節(jié)
    unsigned long shiftcls; // 8字節(jié)
    unsigned char magic; // 1字節(jié)
    unsigned char weakly_referenced; // 1字節(jié)
    unsigned char deallocating; // 1字節(jié)
    unsigned char has_sidetable_rc; // 1字節(jié)
    unsigned int extra_rc; // 4字節(jié)
};
  • 答案是24個(gè)字節(jié)嚣鄙。你答對(duì)了么吻贿?
    對(duì)結(jié)構(gòu)體所占空間大小、 內(nèi)存對(duì)齊這方面還存在疑惑的哑子,可以參考帶你深入理解iOS內(nèi)存對(duì)齊舅列。
  • 通過位域,可以做每一個(gè)繼承自NSObject的對(duì)象都至少減少了16字節(jié)的內(nèi)存空間卧蜓,要知道1M內(nèi)存也就1024個(gè)字節(jié)帐要,這可是相當(dāng)可觀的!

總結(jié)

  • 本文介紹了位域的概念和作用弥奸,結(jié)合實(shí)例讓讀者能夠掌握分析有關(guān)位域的內(nèi)存結(jié)構(gòu)圖榨惠,同時(shí)結(jié)合Apple的源碼isa_t的內(nèi)容,直觀感受位域的帶來的巨大好處,apple在優(yōu)化方面的可謂是盡可能做到了極致赠橙。

BitFieldDemo-源碼

參考
百度百科-位域
C語言位域(位段)詳解

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末耽装,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子期揪,更是在濱河造成了極大的恐慌掉奄,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件凤薛,死亡現(xiàn)場(chǎng)離奇詭異姓建,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)缤苫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門速兔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人活玲,你說我怎么就攤上這事涣狗。” “怎么了舒憾?”我有些...
    開封第一講書人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵屑柔,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我珍剑,道長(zhǎng)掸宛,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任招拙,我火速辦了婚禮唧瘾,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘别凤。我一直安慰自己饰序,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開白布规哪。 她就那樣靜靜地躺著求豫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪诉稍。 梳的紋絲不亂的頭發(fā)上蝠嘉,一...
    開封第一講書人閱讀 49,764評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音杯巨,去河邊找鬼蚤告。 笑死,一個(gè)胖子當(dāng)著我的面吹牛服爷,可吹牛的內(nèi)容都是我干的杜恰。 我是一名探鬼主播获诈,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼心褐!你這毒婦竟也來了舔涎?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤逗爹,失蹤者是張志新(化名)和其女友劉穎终抽,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體桶至,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年匾旭,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了镣屹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡价涝,死狀恐怖女蜈,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情色瘩,我是刑警寧澤伪窖,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站居兆,受9級(jí)特大地震影響覆山,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜泥栖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一簇宽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧吧享,春花似錦魏割、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至殊鞭,卻和暖如春遭垛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背操灿。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來泰國(guó)打工耻卡, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人牲尺。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓卵酪,卻偏偏與公主長(zhǎng)得像幌蚊,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子溃卡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348