<!--[if gte mso 9]> Normal 0 7.8 pt 0 2 false false false MicrosoftInternetExplorer4 <![endif]--><!--[if gte mso 9]> <![endif]-->
C++是一種廣泛使用的語言,也曾有興趣略作研究。因為最近一段時間估計不會用它進行開發了,靜下心來,談談我對它的了解或是發現。
(1) 引子
本文談一談C++中的隐式計數。隐式計數是一個計數器,因為他的儲存空間沒有顯示的展現在程式代碼中,故稱之為“隐式”,而“計數”是說該存儲空間的功能。這麼一說,你首先想到的可能是C++中的new []和delete []操作符,不錯,用new配置設定一個數組時,正是使用了“隐式計數”,才使得delete該數組指針時,能夠擷取到數組元素的個數,請看下面的代碼,或許你并不陌生:
struct Test
{
Test() { cout << "Test()" << endl; }
~Test() { cout << "~Test()" << endl; }
};
int main()
Test *p = new Test[2];
*((int *)p - 1) = 1;
delete[] p;
return 0;
}
看看輸出吧,構造函數被調用兩次,析構函數被調用一次(在VC6.0和gcc中均是如此),毫無疑問,(int *)p - 1的位置就是一個計數器,它記錄了數組數組元素的個數。
(2) 示例1——string中的隐式計數
我們再來看看下面關于std::string的例子:
string s1("Hello");
string s2 = s1;
char *p = const_cast<char *>(s2.c_str());
p[0] = 'M';
cout << s1 << endl;
上面的程式輸出什麼?是“Hello”嗎?答案是不一定(我在gcc 3.4.6中是Mello)。這實際上取決于string類的實作,如果string采用了copy on write的機制,那麼s1和s2實際上共享同一段記憶體,是以上面的代碼也修改了s1的内容。當然,這都是const_cast惹的禍,正常代碼對s1和s2都是能正常工作的。
上面提到了copy on write的共享機制,那麼,程式又是如何決定什麼時候删除該共享記憶體呢?答案是引用計數,如果你有興趣,你可以檢視p指針的前4個位元組,發現其值為1,而再添加一個“string s3 = s1;”後,其值變為2,毫無疑問,這正是一個引用計數。
(3) 示例2——static中的隐式計數
再看一個關于static局部變量的例子。我們知道,如果函數中的static變量在定義時賦初值,那麼隻有在第一次調用該函數時,初始化才被執行,以後的調用都不再執行。那麼,編譯器又是怎麼實作的呢?答案很簡單,就是一個标志位,程式初始化時該标志位為0,函數中初始化相關的代碼則演變為:檢查标志位,如果是0,則對變量進行初始化,然後将标志位置1,否則跳過初始化步驟。這個标志位實際上就是一個隐式的計數器,雖然它隻是一個0-1計數。
知道了編譯器的這個“内幕”,你可以用下面的代碼輕易的繞過初始化檢查,讓函數中的static變量在每次被調用時都被初始化(代碼有些BT,不适者勿看)。
void Test(int initVal)
static int i = initVal;
cout << i << endl;
++i;
int FindAddress()
unsigned char *addr = (unsigned char *)&Test;
// There is only one instruction in Test: jmp realAddr
if (*addr == 0xe9)
addr = addr + *(int *)(addr + 1) + 5;
// Look forward at most 100 bytes for instruction "and eax 1"
for (int i = 0; i < 64; i++)
#ifdef WIN32
if (memcmp(addr + i, "\x83\xe0\x01", 3) == 0)
return *(int *)(addr + i - 4);
#else
if (addr[i+0] == 0x80 && addr[i+1] == 0x3d && addr[i+6] == 0x00)
return *(int *)(addr + i + 2);
#endif
cout << "before modify: " << endl;
Test(0);
Test(100);
try
int flagAddress = FindAddress();
if (flagAddress)
cout << "After modify: " << endl;
*reinterpret_cast<int *>(flagAddress) = 0;
Test(1000);
else
cout << "Can not find the flag address" << endl;
catch (...)
cout << "There is some bug in program" << endl;
代碼的講解我就不說了,注意的是FindAddress函數,其中嘗試查找某種特征的指令。
(4) 總結
上面舉了幾個“隐式計數”的例子,它們可能是編譯器為了在程式中實作代碼面上的功能,而在其中添加的額外資料結構,也可能是庫源碼中為你所不熟悉的資料結構(如string中的隐式計數)。了解這些深層次的實作,對查排錯誤或是提高效率都不無裨益。
很少寫文章,寫這篇文章的目的,隻是希望下次你在用到C++的某個比較“怪異”的特性時,也能有自己的發現, :)。
本文轉自Intel_ISN 51CTO部落格,原文連結:http://blog.51cto.com/intelisn/130713,如需轉載請自行聯系原作者