簡單說下越界訪問的調試方法

越界訪問的一般提示是 “access violation reading location 0xXXXXXXXX”,翻譯過來就是“內存越界訪問”。


這個提示和一般提示的不同之處是,程序不會停在越界訪問的錯誤行上,

而是會停在分配或釋放內存的行上,於是就需要我們自己去找到越界行。


那要怎麼找到越界行呢?這裏說一個較爲實際的辦法:


首先根據 函數調用棧 (Call Stack) 這個工具,找到內存越界訪問的那個函數。


例如:

  

f()
{
    #define MAXLIM 100
    #define MAXLEN 10

    int *p = (int *)calloc(MAXLEN, sizeof(int));
    int *q = (int *)calloc(MAXLIM, sizeof(int));

    int i;
    for(i = 0; i < MAXLIM; i++)
        p[i] = 1;

    free(q);
    free(p); // 程序將在執行這一行後提示一個對話框,同時 Output 窗口中會輸出一行 “access violation reading location 0xXXXXXXXX”
}

然後按照下述法則調試代碼,保證可以找到內存越界訪問:


1. 註釋 (Comment) 大量連續源代碼,只保留成對的 *alloc 和 free,運行看是否還提示越界。然後進行下一步。

2. 若越界訪問,那麼註釋更大部分源代碼。重複此步,直至無越界訪問。越界訪問行必然在最後兩次註釋的增加註釋裏。

    若無越界訪問,那麼註釋更小部分源代碼。重複此步,直至越界訪問。越界訪問行必然在最後兩次註釋的減少註釋裏。


下面我們試試這個辦法。


首先註釋大量代碼。我們把整個函數都註釋了,只保留兩個宏定義:


f()
{
    #define MAXLIM 100
    #define MAXLEN 10

    //int *p = (int *)calloc(MAXLEN, sizeof(int));
    //int *q = (int *)calloc(MAXLIM, sizeof(int));

    //int i;
    //for(i = 0; i < MAXLIM; i++)
        //p[i] = 1;

    //free(q);
    //free(p);
}

這樣子顯然沒有任何問題。於是我們註釋更小部分代碼:


f()
{
    #define MAXLIM 100
    #define MAXLEN 10

    int *p = (int *)calloc(MAXLEN, sizeof(int));
    int *q = (int *)calloc(MAXLIM, sizeof(int));

    //int i;
    //for(i = 0; i < MAXLIM; i++)
        //p[i] = 1;

    free(q);
    free(p);
}

調試發現也沒有問題。那麼再註釋更小部分的代碼。for 循環,雖然不容易用“直到換行符的註釋(//)”,註釋,但是還是可以用“從/*符到*/符的註釋”,註釋。


f()
{
    #define MAXLIM 100
    #define MAXLEN 10

    int *p = (int *)calloc(MAXLEN, sizeof(int));
    int *q = (int *)calloc(MAXLIM, sizeof(int));

    int i;
    for(i = 0; i < MAXLIM; i++)
        /*p[i] = 1*/;

    free(q);
    free(p);
}

調試後依然沒有問題。那麼繼續減小注釋範圍。這時發現已經沒有可以註釋的了,那麼就不註釋,也就是原來代碼了。


原來代碼運行必然會提示越界訪問的。於是可以肯定地說,越界訪問出現在 "p[i] = 1;" 上。正是這個索引 i 超過了允許訪問的界限。


於是我們要比較一下 i 的訪問範圍和 p 的允許範圍。


i 的訪問範圍是 0 ... MAXLIM - 1

p 的允許範圍是 0 ... MAXLEN - 1


而 MAXLEN < MAXLIM ,於是越界。這時原因查出,只需根據需要,減小賦值範圍或增大分配內存即可。


f()
{
    #define MAXLIM 100
    #define MAXLEN 10

    int *p = (int *)calloc(MAXLEN, sizeof(int));
    int *q = (int *)calloc(MAXLIM, sizeof(int));

    int i;
    for(i = 0; i < MAXLIM; i++)
        p[i] = 1;

    free(q);
    free(p);
}


其實從 free 能夠釋放內存這裏可以知道,對已分配內存其實像字符串一樣已經做過標記,所以釋放時才能夠知道需要釋放到哪兒。


賦值時候不會自動檢測是否越界訪問,也是爲了效率提升。如果一定需要檢測,我們可以自己用 assert 宏進行實現:


f()
{
    #define MAXLIM 100
    #define MAXLEN 10

    int *p = (int *)calloc(MAXLEN, sizeof(int));
    int *q = (int *)calloc(MAXLIM, sizeof(int));

    int i;
    for(i = 0; i < MAXLIM; i++)
    {
        assert(i < MAXLEN);
        p[i] = 1;
    }

    free(q);
    free(p);
}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章