C語言難點(diǎn)揭秘[3]

字號:

懸空指針
    懸空指針比較棘手。當(dāng)程序員在內(nèi)存資源釋放后使用資源時(shí)會發(fā)生懸空指針(請參見清單 5):
    清單 5. 懸空指針
     void f8()
     {
     struct x *xp;
     xp = (struct x *) malloc(sizeof (struct x));
     xp.q = 13;
     ...
     free(xp);
     ...
     /* Problem! There's no guarantee that
     the memory block to which xp points
     hasn't been overwritten. */
     return xp.q;
     }
    傳統(tǒng)的“調(diào)試”難以隔離懸空指針。由于下面兩個(gè)明顯原因,它們很難再現(xiàn):
    即使影響提前釋放內(nèi)存范圍的代碼已本地化,內(nèi)存的使用仍然可能取決于應(yīng)用程序甚至(在極端情況下)不同進(jìn)程中的其他執(zhí)行位置。
    懸空指針可能發(fā)生在以微妙方式使用內(nèi)存的代碼中。結(jié)果是,即使內(nèi)存在釋放后立即被覆蓋,并且新指向的值不同于預(yù)期值,也很難識別出新值是錯(cuò)誤值。
    懸空指針不斷威脅著 C 或 C++ 程序的運(yùn)行狀態(tài)。
    數(shù)組邊界違規(guī)
    數(shù)組邊界違規(guī)十分危險(xiǎn),它是內(nèi)存錯(cuò)誤管理的最后一個(gè)主要類別?;仡^看一下清單 1;如果 explanation 的長度超過 80,則會發(fā)生什么情況?回答:難以預(yù)料,但是它可能與良好情形相差甚遠(yuǎn)。特別是,C 復(fù)制一個(gè)字符串,該字符串不適于為它分配的 100 個(gè)字符。在任何常規(guī)實(shí)現(xiàn)中,“超過的”字符會覆蓋內(nèi)存中的其他數(shù)據(jù)。內(nèi)存中數(shù)據(jù)分配的布局非常復(fù)雜并且難以再現(xiàn),所以任何癥狀都不可能追溯到源代碼級別的具體錯(cuò)誤。這些錯(cuò)誤通常會導(dǎo)致數(shù)百萬美元的損失。
    內(nèi)存編程的策略
    勤奮和自律可以讓這些錯(cuò)誤造成的影響降至最低限度。下面我們介紹一下您可以采用的幾個(gè)特定步驟;我在各種組織中處理它們的經(jīng)驗(yàn)是,至少可以按一定的數(shù)量級持續(xù)減少內(nèi)存錯(cuò)誤。
    編碼風(fēng)格
    編碼風(fēng)格是最重要的,我還從沒有看到過其他任何作者對此加以強(qiáng)調(diào)。影響資源(特別是內(nèi)存)的函數(shù)和方法需要顯式地解釋本身。下面是有關(guān)標(biāo)頭、注釋或名稱的一些示例(請參見清單 6)。
    清單 6. 識別資源的源代碼示例
     /********
     * ...
     *
     * Note that any function invoking protected_file_read()
     * assumes responsibility eventually to fclose() its
     * return value, UNLESS that value is NULL.
     *
     ********/
     FILE *protected_file_read(char *filename)
     {
     FILE *fp;
     fp = fopen(filename, "r");
     if (fp) {
     ...
     } else {
     ...
     }
     return fp;
     }
     /*******
     * ...
     *
     * Note that the return value of get_message points to a
     * fixed memory location. Do NOT free() it; remember to
     * make a copy if it must be retained ...
     *
     ********/
     char *get_message()
     {
     static char this_buffer[400];
     ...
     (void) sprintf(this_buffer, ...);
     return this_buffer;
     }
     /********
     * ...
     * While this function uses heap memory, and so
     * temporarily might expand the over-all memory
     * footprint, it properly cleans up after itself.
     *
     ********/
     int f6(char *item1)
     {
     my_class c1;
     int result;
     ...
     c1 = new my_class(item1);
     ...
     result = c1.x;
     delete c1;
     return result;
     }
     /********
     * ...
     * Note that f8() is documented to return a value