本文不看其他,只專注于weak的內(nèi)部結(jié)構(gòu)實(shí)現(xiàn)細(xì)節(jié)和源碼解讀,看了網(wǎng)上很多的文章都是貼上一篇open source里面的代碼,并沒有對(duì)實(shí)現(xiàn)細(xì)節(jié)進(jìn)行解釋。所以在這篇文章中,主要分為
weak_entry_t、weak_table_t的源碼解析,weak_entry_t和weak_table_t的相互關(guān)系,以及對(duì)應(yīng)的操作函數(shù)。
下文的主要是基于兩個(gè)對(duì)象來說的,一個(gè)是被引用的對(duì)象,一個(gè)是弱引用變量(也就是源代碼中大量出現(xiàn)的指向指針的指針)。
我說一下我源碼閱讀的習(xí)慣,先把目光放在頭文件中,因?yàn)轭^文件能夠給我們一個(gè)整體基礎(chǔ)結(jié)構(gòu)。弄清楚具體的結(jié)構(gòu)之后,然后再跳到實(shí)現(xiàn)文件中去看具體的實(shí)現(xiàn)細(xì)節(jié)。
先交代一下我的編譯環(huán)境和源代碼版本:
編譯環(huán)境:
Apple LLVM version 9.1.0 (clang-902.0.39.1)
Target: x86_64-apple-darwin17.5.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
源代碼版本:
objc4-723
頭文件類關(guān)系和結(jié)構(gòu)分析
我先根據(jù)頭文件畫一個(gè)基本的UML類圖:
DisguisedPtr模板類
先將視線放在weak_entry_t上面,結(jié)構(gòu)weak_entry_t的第一個(gè)成員變量是referent,它是一個(gè)DisguisedPtr類模板實(shí)例化之后的變量(點(diǎn)開前面的鏈接吧,不然我講不清楚,不然你會(huì)罵我的),這個(gè)成員其實(shí)就是保存被引用的對(duì)象。
DisguisedPtr類里面看起來這個(gè)類并不復(fù)雜,有一個(gè)uintptr_t類型的成員變量,由此DisguisedPtr類的對(duì)象所占用的內(nèi)存空間大小也應(yīng)該為8字節(jié)。
public下面主要是構(gòu)造函數(shù)加三大函數(shù)中的兩個(gè):重載復(fù)制運(yùn)算符,賦值構(gòu)造函數(shù);由于該類里面并沒有涉及到動(dòng)態(tài)new指針變量,所以其析構(gòu)函數(shù)便使用了默認(rèn)析構(gòu)函數(shù)。除此之外還重載一些其他的操作符。主要看一下私有的兩個(gè)成員函數(shù):
static uintptr_t disguise(T* ptr) {
return -(uintptr_t)ptr;
}
static T* undisguise(uintptr_t val) {
return (T*)-val;
}
其中disguise
函數(shù)是將指針變量強(qiáng)轉(zhuǎn)為uintptr_t的整形變量,具體怎么偽裝呢?就是把該指針指向的內(nèi)存地址(16進(jìn)制數(shù)據(jù)比如:0x7ffeefbff4e8)強(qiáng)制轉(zhuǎn)換為無符號(hào)長(zhǎng)整型的十進(jìn)制數(shù)據(jù)。由于其類型是無符號(hào)長(zhǎng)整型,因此取負(fù)數(shù)是數(shù)據(jù)溢出之后取該類型取值范圍內(nèi)較大的長(zhǎng)整型值達(dá)到偽裝的效果(也就是不好去找到原內(nèi)存地址)。
unsigned long ul_val = 2;
unsigned long*bitl = &ul_val;
cout<<"ul_val address: "<<bitl<<endl;///0x7ffeefbff4e8
///140732920755432 取負(fù)數(shù) -> 18446744069408184208
cout<<"disguise: "<<disguise(bitl)<<endl;
cout<<"undisguise: "<<undisguise(*bitl)<<endl;/// 0xfffffffffffffffe 1111...1110
其作用在源文件的注釋中也說了,我通俗總結(jié)是:對(duì)那些比如leak這種內(nèi)存檢測(cè)工具進(jìn)行偽裝,然后這些檢測(cè)工具可能就不好去跟蹤被引用的對(duì)象。
weak_entry_t
現(xiàn)在來看一下union的具體內(nèi)存分布細(xì)節(jié),怎么來解釋這個(gè)問題呢?奉上objc-weak.h的源碼,打開源碼配合文章來看。
union {
struct {/// 為了方便說明問題,我將該結(jié)構(gòu)取名為:struct1
weak_referrer_t *referrers;
uintptr_t out_of_line : 2;
uintptr_t num_refs : PTR_MINUS_1;/// num_refs記錄的是實(shí)際引用數(shù)量
uintptr_t mask;/// 記錄當(dāng)前referrers數(shù)組容器的大小
uintptr_t max_hash_displacement;/// 根據(jù)hash-key尋找index的最大移動(dòng)數(shù),這個(gè)在后面的append_referrer會(huì)講
};
struct {/// 為了方便說明問題,我將該結(jié)構(gòu)取名為:struct2
// out_of_line=0 is LSB of one of these (don't care which)
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
};
首先要有一個(gè)概念,union里面的多個(gè)成員是共享同一且相同大小的內(nèi)存空間,在strcut1結(jié)構(gòu)成員中算出其總共所占內(nèi)存大小為64*4,也就是32個(gè)字節(jié)。其中我的機(jī)器是64位機(jī),我的編譯器對(duì)于指針類型所占內(nèi)存大小的ABI實(shí)現(xiàn)為64位,而無符號(hào)長(zhǎng)整型占用的內(nèi)存大小也為64位。多說一句,在C++中結(jié)構(gòu)和類的內(nèi)存存儲(chǔ)區(qū)域好像都是在堆上面,由低地址向高地址生長(zhǎng)。
基于此來畫出inline_referrers和上面第一個(gè)結(jié)構(gòu)大致的內(nèi)存分布樣式(關(guān)于inline_referrers的元素類型模板類DisguisedPtr所占內(nèi)存大小在上面講DisguisedPtr類時(shí)提到了):
在源碼中注釋也說了:
// out_of_line_ness field overlaps with the low two bits of inline_referrers[1].
// inline_referrers[1] is a DisguisedPtr of a pointer-aligned address.
// The low two bits of a pointer-aligned DisguisedPtr will always be 0b00
// (disguised nil or 0x80..00) or 0b11 (any other address).
// Therefore out_of_line_ness == 0b10 is used to mark the out-of-line state.
out_of_line_ness是和inline_referrers[1]的低2位是等同的,out_of_line_ness
和num_refs
使用了位段,一共占用64位(2位和62位)。由于此時(shí)已經(jīng)是結(jié)構(gòu)內(nèi)存對(duì)齊了,所以下一個(gè)結(jié)構(gòu)成員mask的內(nèi)存地址就剛好換行。
上面還提到的0x0b10,它應(yīng)該是經(jīng)過DisguisedPtr偽裝之后得到的值,并不是實(shí)際的等于0b10,一個(gè)只占兩位內(nèi)存空間的,怎么也存儲(chǔ)不了16位的數(shù)據(jù)。out_of_line_ness == 0b10是標(biāo)記使用out-of-line的狀態(tài)。關(guān)于這個(gè)0b10我沒有想清楚它的由來,有知道的同學(xué)麻煩告知于我!!!
繼續(xù)來看該結(jié)構(gòu)的構(gòu)造函數(shù):
weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
: referent(newReferent)
{
inline_referrers[0] = newReferrer;
for (int i = 1; i < WEAK_INLINE_COUNT; i++) {
inline_referrers[i] = nil;
}
}
在創(chuàng)建weak_entry_t實(shí)例的時(shí)候,默認(rèn)是使用inline_referrers的方式來管理對(duì)象引用的,并把其余的位上的數(shù)據(jù)清空。
out_of_line_ness
用來判斷使用out_of_line的方式來進(jìn)行引用管理,這個(gè)out_of_line_ness的值主要是依據(jù)于被引用的對(duì)象,其引用變量的個(gè)數(shù)決定的,具體的邏輯在下文會(huì)講到。
再看看struct1的referrers成員,看起來是一個(gè)指針變量,更具體的說是存儲(chǔ)的引用變量數(shù)組的起始地址,而這些引用變量指針指向的地址被DisguisedPtr進(jìn)行了偽裝。
到這里我把weak_entry_t的內(nèi)存分布講了一遍(具體的含義在上面代碼塊中的注釋里),然后下面來看一下weak_table_t
。
weak_table_t
weak_table_t
在頭文件中看不出什么特別的內(nèi)容,但是從源碼中可以看出,應(yīng)該是一個(gè)基于C的結(jié)構(gòu),沒有使用C++中結(jié)構(gòu)獨(dú)有的特性。
struct weak_table_t {
weak_entry_t *weak_entries;
size_t num_entries;/// 和weak_entry_t的num_refs概念類似
uintptr_t mask;///和 weak_entry_t的mask概念類似
uintptr_t max_hash_displacement;/// 和weak_entry_t的max_hash_displacement概念類似
};
同樣的,其weak_entries成員也應(yīng)該是一個(gè)數(shù)組,存儲(chǔ)著weak_entry_t變量的指針。針對(duì)該結(jié)構(gòu)頭文件中公開的操作函數(shù)有:
id weak_register_no_lock(weak_table_t *weak_table, id referent,
id *referrer, bool crashIfDeallocating);
void weak_unregister_no_lock(weak_table_t *weak_table, id referent, id *referrer);
#if DEBUG
bool weak_is_registered_no_lock(weak_table_t *weak_table, id referent);
#endif
void weak_clear_no_lock(weak_table_t *weak_table, id referent);
這看不了什么具體的內(nèi)容,所以針對(duì)頭文件的解讀就到這里。下面去實(shí)現(xiàn)文件中看看具體的實(shí)現(xiàn),看看網(wǎng)上為什么都在說的基于Hash表的一個(gè)存儲(chǔ)結(jié)構(gòu)。源碼地址,老規(guī)矩,打開這個(gè)網(wǎng)頁(yè)對(duì)照著源碼來看。
objc-weak具體實(shí)現(xiàn)細(xì)節(jié)
在實(shí)現(xiàn)文件中,首先看兩個(gè)hash函數(shù):
static inline uintptr_t hash_pointer(objc_object *key);
static inline uintptr_t w_hash_pointer(objc_object **key);
它們會(huì)根據(jù)對(duì)象的指針(不管是指針還是指向指針的指針)調(diào)用一個(gè)fast-hash函數(shù)來生成一個(gè)key,其原理是基于fast_hash,而這個(gè)key的作用目前我們無從得知,但是在下面中,你會(huì)看到這個(gè)key作為散列表查找散列槽的重要依據(jù)。
第一個(gè)函數(shù)hash_pointer是對(duì)應(yīng)weak_table_t中weak_entries散列表的,也就是根據(jù)被引用對(duì)象生成的key;
第二個(gè)函數(shù)w_hash_pointer則是對(duì)應(yīng)weak_entry_t中out_of_line情況下referrers散列表的,也就是根據(jù)弱引用變量生成的key。
grow_refs_and_insert函數(shù)
繼續(xù)看源碼,下面主要來看看一個(gè)很重要的函數(shù):
__attribute__((noinline, used))
static void grow_refs_and_insert(weak_entry_t *entry,
objc_object **new_referrer)
{
assert(entry->out_of_line);
/**
* #define TABLE_SIZE(entry) (entry->mask ? entry->mask + 1 : 0)
* entry->mask用來記錄referrers的數(shù)量
*/
size_t old_size = TABLE_SIZE(entry);
size_t new_size = old_size ? old_size * 2 : 8;/// 增長(zhǎng)一倍的大小
size_t num_refs = entry->num_refs;
weak_referrer_t *old_refs = entry->referrers;
entry->mask = new_size - 1;
entry->referrers = (weak_referrer_t *)
_calloc_internal(TABLE_SIZE(entry), sizeof(weak_referrer_t));
entry->num_refs = 0;
entry->max_hash_displacement = 0;
/// 開始處理數(shù)據(jù)
for (size_t i = 0; i < old_size && num_refs > 0; i++) {
if (old_refs[i] != nil) {
append_referrer(entry, old_refs[i]);/// 把老數(shù)據(jù)復(fù)制進(jìn)新的entry里面
num_refs--;
}
}
// Insert
append_referrer(entry, new_referrer);/// 給entry插入新的數(shù)據(jù)
if (old_refs) _free_internal(old_refs);
}
由于基于C的數(shù)組其實(shí)都是定長(zhǎng)的,為了能夠動(dòng)態(tài)地增加新元素就需要不斷地去申請(qǐng)新的內(nèi)存空間,并且還要是連續(xù)的內(nèi)存地址(要是不連續(xù)的地址就去使用鏈表的方式,但是鏈表的索引明顯弱于數(shù)組的)。正是因?yàn)樾聞?dòng)態(tài)申請(qǐng)的連續(xù)內(nèi)存空間,這就需要把老數(shù)據(jù)復(fù)制過來,并把需要新增的數(shù)據(jù)也追加進(jìn)去,最后釋放掉原內(nèi)存空間:
它其實(shí)和C++里面的動(dòng)態(tài)數(shù)組的原理一樣,為了不頻繁地去申請(qǐng)(
calloc
)新的空間和頻繁地?cái)?shù)據(jù)移動(dòng)。所以每次2倍增長(zhǎng)來增加weak_entry_t的長(zhǎng)度。為什么說是C++里面動(dòng)態(tài)數(shù)組的做法,在《數(shù)據(jù)結(jié)構(gòu)與算法實(shí)現(xiàn)-C++描述》里有提及這些內(nèi)容。
append_referrer和remove_referrer
在grow_refs_and_insert函數(shù)中調(diào)用了append_referrer
函數(shù),這個(gè)函數(shù)很明顯是做插入操作的,默認(rèn)使用inline的方式來增加新增的weak引用,如果使用inline的方式失敗了,則是以outline的方式,并申請(qǐng)對(duì)應(yīng)的存儲(chǔ)空間,把entry->referrers指向新申請(qǐng)的內(nèi)存地址,把inline_referrers數(shù)組里的數(shù)據(jù)拷貝到new_referrers中,其源碼如下:
if (! entry->out_of_line) {
// Try to insert inline.
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i] == nil) {
entry->inline_referrers[i] = new_referrer;
return;
}
}
// Couldn't insert inline. Allocate out of line.
weak_referrer_t *new_referrers = (weak_referrer_t *)
_calloc_internal(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
// This constructed table is invalid, but grow_refs_and_insert
// will fix it and rehash it.
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
new_referrers[i] = entry->inline_referrers[I];
}
entry->referrers = new_referrers;
entry->num_refs = WEAK_INLINE_COUNT;
entry->out_of_line = REFERRERS_OUT_OF_LINE;
entry->mask = WEAK_INLINE_COUNT-1;
entry->max_hash_displacement = 0;
}
從這里就可以看出,當(dāng)被引用對(duì)象的弱引用referrers個(gè)數(shù)小于WEAK_INLINE_COUNT時(shí),其entry里面是以inline小數(shù)組方式來存儲(chǔ)這些弱引用變量的,只有當(dāng)inline_referrers全部裝滿之后,該entry out_of_line被設(shè)置為REFERRERS_OUT_OF_LINE,后續(xù)如若有變量繼續(xù)引用該對(duì)象則是以outline的方式存儲(chǔ)的。
union是在被引用變量的referrers個(gè)數(shù)小于等于WEAK_INLINE_COUNT時(shí),使用inline數(shù)組的內(nèi)存表現(xiàn)形式;當(dāng)referrers個(gè)數(shù)超過了WEAK_INLINE_COUNT則以struct1的內(nèi)存表現(xiàn)形式!
由于使用inline的方式是使用小數(shù)組的方式,但是針對(duì)弱引用對(duì)象過多,那么它的存取性能就是考慮的一個(gè)重點(diǎn)。而散列是一種用于以常數(shù)平均時(shí)間執(zhí)行插入、刪除和查找的技術(shù)。
下面這個(gè)過程我不是很確定,如有不同的建議希望指出。
size_t index = w_hash_pointer(new_referrer) & (entry->mask);
size_t hash_displacement = 0;
while (entry->referrers[index] != NULL) {
index = (index+1) & entry->mask;
hash_displacement++;
}
if (hash_displacement > entry->max_hash_displacement) {
entry->max_hash_displacement = hash_displacement;
}
weak_referrer_t &ref = entry->referrers[index];
ref = new_referrer;
entry->num_refs++;
begin是通過引用new_referrer調(diào)用散列函數(shù)獲取一個(gè)散列值,這個(gè)散列值就是散列表中的元素查找自己所在散列槽的key。
從源碼可以看出,通過散列值查找元素對(duì)應(yīng)散列槽的方式好像是使用了線性探測(cè)法。簡(jiǎn)化上面的代碼,配合下方這圖來看一下把new_referrer指針查找正確index的過程:
如上圖,假設(shè)使用
w_hash_pointer
獲取到的key為2,obj1成功插入到散列槽2中,obj2使用w_hash_pointer
獲取到的key也為2,此時(shí)散列槽2已經(jīng)放入了obj1,那么只有正向地去尋找下一個(gè)散列槽,如果為空則放入obj2。回到源碼中,在求begin值的時(shí)候。把hash值和entry->mask做了按位與的操作,但是這里為什么要對(duì)entry->mask做一次按位與操作呢?
entry->mask存儲(chǔ)著weak_entry_t的referrers數(shù)組大小,這樣做能保證所得的散列值是小于當(dāng)前數(shù)組的出界的,因?yàn)榇笥趓eferrers數(shù)組大小對(duì)應(yīng)的二進(jìn)制位的高位全部被置為0,從而避免出現(xiàn)數(shù)組越界帶來的問題。
關(guān)于出界和入界的概念,可以在《C陷阱與缺陷》中關(guān)于一個(gè)介紹for循環(huán)越界導(dǎo)致的死循環(huán)一節(jié),具體的記得不是很清楚了。針對(duì)出界這個(gè)概念還是蠻重要的,老板們可以去看一看。
remove_referrer
和append_referrer在源碼上來看基本沒有什么區(qū)別,區(qū)別只不過是一個(gè)賦值,一個(gè)置空而已。
weak_table_t的擴(kuò)容和減容
針對(duì)weak_table_t的擴(kuò)容和減容源碼相對(duì)來說比較簡(jiǎn)單,限于篇幅我沒有提供對(duì)應(yīng)的代碼,所以在看的時(shí)候還麻煩自己打開上面提到的源碼地址對(duì)照來看。
在源碼中主要提供了如下函數(shù):
- weak_entry_insert;
函數(shù)weak_entry_insert
和上一節(jié)提到的append_referrer是類似的,weak_table_t的內(nèi)部實(shí)現(xiàn)同樣也是使用散列表的方式來管理所有的entry變量的。只是weak_entry_insert沒有去嘗試inline的那一步。 - weak_resize;
函數(shù)weak_resize
和上面提到的grow_refs_and_insert函數(shù)類似,在調(diào)整大小時(shí),都是創(chuàng)建一個(gè)新尺寸大小的內(nèi)存空間,然后將原內(nèi)存空間的數(shù)據(jù)移動(dòng)到新的內(nèi)存空間。weak_resize只有移動(dòng)老數(shù)據(jù),沒有新數(shù)據(jù)的添加!最后釋放掉原內(nèi)存。 - weak_grow_maybe;
函數(shù)weak_grow_maybe
是在原weak_table的entry數(shù)量大于了weak_table數(shù)組容量的3/4時(shí),便調(diào)用weak_resize去擴(kuò)充容量到原數(shù)組容量的2倍。 - weak_compact_maybe;
函數(shù)weak_compact_maybe
是用來收縮容量的,當(dāng)數(shù)組的容量?jī)?nèi)大部分都為空的話,則減容。
if (old_size >= 1024 && old_size / 16 >= weak_table->num_entries) {
weak_resize(weak_table, old_size / 8);
// leaves new table no more than 1/2 full
}
- weak_entry_remove;
函數(shù)weak_entry_remove
用來weak_table_t的entries里對(duì)應(yīng)的entry
if (entry->out_of_line()) free(entry->referrers);
bzero(entry, sizeof(*entry));
weak_table->num_entries--;
weak_compact_maybe(weak_table);
sizeof(*entry)獲取到了weak_entry_t所占用的內(nèi)存大小,使用bzero是將該內(nèi)存段全部置為0,使用bzero而不使用memset影響并不大,使用memset需要多傳入一個(gè)參數(shù)來確定需要重置的值。在《Unix網(wǎng)絡(luò)編程》里創(chuàng)建sockaddr_in結(jié)構(gòu)變量時(shí),把對(duì)應(yīng)內(nèi)存空間數(shù)據(jù)清空用到了bzero,并講了一下和memset的區(qū)別,具體內(nèi)容可以去看看這本書。
頭文件暴露的四個(gè)函數(shù)
在頭文件中暴露了四個(gè)外部可用的函數(shù),分別是:weak_register_no_lock、weak_unregister_no_lock、weak_is_registered_no_lock和weak_clear_no_lock,根據(jù)注釋來看主要是針對(duì)weak_table_t的添加、刪除和清空數(shù)據(jù)等操作。在這里以下面的代碼為基礎(chǔ)來講解:
__weak id refer = obj;
下面再來具體看看這幾個(gè)函數(shù)在干什么?
weak_register_no_lock
源代碼中提出,注冊(cè)一個(gè)新的鍵值對(duì),如果新的弱對(duì)象不存在則去新創(chuàng)建一個(gè)對(duì)應(yīng)的entry。
if (!referent || referent->isTaggedPointer()) return referent_id;
如果被弱引用指向的對(duì)象(obj)是isTaggedPointer,這里便不做相關(guān)操作,直接返回弱引用指向的對(duì)象(obj)。
關(guān)于什么是Tagged Pointer,后面我再去細(xì)看一下里面的源碼。從這里的源碼可以看出,如果是TaggedPointer就不做后續(xù)操作,因?yàn)橹羔槻]有指向真正的內(nèi)存地址,返回的值則是被引用對(duì)象自身。
weak_entry_t *entry;
if ((entry = weak_entry_for_referent(weak_table, referent))) {
append_referrer(entry, referrer);
}else {
weak_entry_t new_entry(referent, referrer);
weak_grow_maybe(weak_table);
weak_entry_insert(weak_table, &new_entry);
}
如果存在對(duì)應(yīng)的entry則直接調(diào)用append_referrer進(jìn)行插入。如果不存在,則調(diào)用weak_entry_t的構(gòu)造函數(shù)創(chuàng)建一個(gè)新的對(duì)象,并查看是否需要針對(duì)weak_table進(jìn)行擴(kuò)容,將新的entry插入到weak_table中。下圖是一個(gè)為對(duì)象增加弱引用,并將引用添加到weak_table中的簡(jiǎn)易流程:
現(xiàn)在來看一下weak_unregister_no_lock
函數(shù),針對(duì)weak_table的移除,必須確保entry已經(jīng)存在于weak_table中,才會(huì)去進(jìn)行后續(xù)的操作,同樣把對(duì)應(yīng)的流程圖畫出來:
最后兩個(gè)函數(shù)是一個(gè)是debug狀態(tài)下用于判斷某一entry是否存在于weak_table中,另一個(gè)函數(shù)則是對(duì)特定的被弱引用的對(duì)象(obj)的所有引用做清楚操作。
結(jié)語
到這里objc-weak應(yīng)該算是講清楚了(天知道我的表達(dá)能力怎么樣。。。),最后我從外層結(jié)構(gòu)到內(nèi)層結(jié)構(gòu)來一一總結(jié)下:
1、weak_table可以存儲(chǔ)多個(gè)entry,而且它會(huì)根據(jù)其散列表中entry的個(gè)數(shù)來伸縮其所占內(nèi)存大小;
2、一個(gè)entry表示的是一個(gè)被弱引用的對(duì)象(上文提到的obj),該變量可以被多個(gè)變量弱引用(refer)。所以entry也存在一個(gè)散列表,其用來存儲(chǔ)對(duì)應(yīng)的弱引用變量的引用。也就是前面源碼里面提到的指向指針的指針。
3、entry的out_of_line_ness只有在弱引用變量大于WEAK_INLINE_COUNT時(shí)才會(huì)置為REFERRERS_OUT_OF_LINE。也就是只有在這時(shí)候union才會(huì)使用struct1結(jié)構(gòu)內(nèi)存布局。
4、還有就是out_of_line_ness == 0b10沒有看懂。。。