1.前言 数组和位移基本上没啥关系,但可以通过位移的方式来操控数组的内存运作方式。比较细微的容易被疏忽的处理,然后引申下CLR的card_table这个表,本篇来看下。
2.概述 先上示例代码:
typedef unsigned int uint32_t;
int main(int argc,char** argv) {
uint32_t card_table[] = { 1,3,5,7,9,11,13,15,17 };
uint32_t* g_card_table = card_table;
uint32_t bone = g_card_table[2];
uint32_t btwo = *(g_card_table + 8);
}
示例通过索引取数组的值,比如索引2:
uint32_t bone = g_card_table[2];
可以写成如下:
uint32_t boneone = *(g_card_table+2);
前者数组后者指针,我们可以通过指针的偏移量来操控数组。
3.card_table card_table有两个功能 其一:当2代对象引用了0代对象,此时如果0代对象进行了GC。可能这个0代对象被GC压缩或者移动了地址。2代对象此时指向的就是空地址或者错误地址,这就需要card_table位来标记0代移动对象的地址。修正2代对象指向正确的地址。
其二:是当GC堆的2代引用了0代对象,而此时GC回收了0代,如果这个0代对象除了2代引用了它,没有其它引用,那么它可能被面临回收,为了防止回收,card_table里面做了位标记,GC的时候除了扫描GC堆,还扫描card_table的位标记,防止对象误回收。
可见card_table是比较重要的一个CLR功能。
示例当中
uint32_t btwo = *(g_card_table + 8);
当我们在*(g_card_table + 8)此处进行了赋值0xFF,表示此处有0代对象移动或者是面临被回收。我们取值的时候可以往前扩大下范围,防止card_table漏扫。
bone = g_card_table[2];
我们看到这里的索引是2,而赋值的时候索引是8。这样的话取值bone的范围是大于btwo的范围。可以正确扫描处引用0代的2代对象大致的范围。
实际的操作如下:
赋值:
g_card_table + n1.selfName(2代对象) >> 0x0B = 0xFF
取值:
card_table [card_word (card)];
可以看到2代对象右移了0x0B位,card和card_word总共位移了0x0D位。多出了两位,也就是把数组取值向左边扩展了。以便正确扫描二代。
另外一个比较奇特功能就是通过余数来获取正确的card_table值
last_card_word = &card_table [card_word (card)];
bit_position = card_bit (card);
card_word_value = (*last_card_word) >> bit_position;
card_bit里面是%,然后把得到的*last_card_word按照余数右移,获取真正的card_table数值。
以上极为细致的操作是CLR骚操的一部分,有一些东西依然需要更进一步