天天看點

My insight C++——C++中的隐式計數

<!--[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,如需轉載請自行聯系原作者

繼續閱讀