構造Crash看C/C++內存分佈

首先看一段必現的Crash程序。

char* do_strcpy(char* pDstStr, const char* pSrcStr)
{
 	char* pRet = pDstStr;
 	while ((*(pDstStr++) = *(pSrcStr++))!='\0'){;}
 	return pRet;
}

bool __strcpy(const char* pSrcStr)
{
	char destStr[8] = {0};
	do_strcpy(&destStr[-24], pSrcStr);
        printf("ok\n");
	printf(destStr);
	return true;
}

int main()
{
	printf("input a string: \n");
	char strInput[256] = {0};
	fgets(strInput, 256, stdin);
	__strcpy(strInput);
	getchar();
	return 0;
}
這段代碼必然crash,下面分析下原因。

輸入一段長字符串,比如50個1,執行到__strcpy中後,在棧上申請一個char[],長度爲8+1.然後調用do_strcpy並傳參,然後執行printf("ok\n").


函數的調用需要壓棧,在調用do_strcpy前,在棧上開闢了9個字節的空間,調用do_strcpy時,首先將返回後的下一個指令地址壓棧,即將printf("ok\n")的函數地址壓棧。然後穿參數,在vc上參數從右到左壓棧,最後傳入do_strcpy的函數地址。待函數執行完後依次出棧,最終得到printf("ok\n")並繼續執行。



do_strcpy傳入的第一個參數是棧上的數組destStr向前偏移24個字節。在do_strcpy中,由於執行了字符串拷貝,且目標字符串的其實地址是destStr[-24],所以拷貝後,數據從destStr[-24]一路覆蓋到destStr[0],再到destStr[7],當然,函數do_strcpy的入參,及返回後下一條指令地址也都被覆蓋,寫入的是什麼取決於Input a String時的輸入。所以,下一條指令地址出錯了,crash也就不奇怪了。


由此擴展到C/C++程序的內存分佈狀況。



從高地址到低地址分別爲棧、堆、未初始化的靜態和全局變量,初始化的靜態和全局變量,以及常量,代碼段。

其中,棧由高地址向低地址生長,棧頂在低地址。

堆由低地址向高地址生長。

抄一段網上的經典代碼:

int a = 0; //全局初始化區 
char *p1; //全局未初始化區 
main() 
{ 
    int b; //棧 
    char s[] = "abc"; //棧 
    char *p2; //棧 
    char *p3 = "123456"; //123456\0在常量區,p3在棧上。 
    static int c = 0; //全局(靜態)初始化區 
        p1 = (char *)malloc(10); 
    p2 = (char *)malloc(20); 
    //分配得來得10和20字節的區域就在堆區。 
    strcpy(p1, "123456"); 
    //123456\0放在常量區,編譯器可能會將它與p3所指向的"123456"優化成一塊。 
} 

堆的使用更復雜一些,如下圖:


程序通過VirtualAlloc和FileMapping可以直接控制虛擬內存進行內存分配,通過HeapAlloc、LocalAlloc和new、malloc可以通過堆管理器動態使用內存,其中,new、malloc是標準庫的函數,在不同平臺下調用的系統api不同,需要經過crt。

發佈了56 篇原創文章 · 獲贊 21 · 訪問量 26萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章