青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

為你寫詩

c/c++
隨筆 - 32, 文章 - 0, 評論 - 3, 引用 - 0
數據加載中……

c++內存錯誤

大內高手—常見內存錯誤
 隨著諸如代碼重構和單元測試等方法引入實踐,調試技能漸漸弱化了,甚至有人主張廢除調試器。這是有道理的,原因在于調試的代價往往太大了,特別是調試系統集成之后的BUG,一個BUG花了幾天甚至數周時間并非罕見。 

而這些難以定位的BUG基本上可以歸為兩類:內存錯誤和并發問題。而又以內存錯誤最為普遍,即使是久經沙場的老手,也有時也難免落入陷阱。前事不忘,后世之師,了解這些常見的錯誤,在編程時就加以注意,把出錯的概率降到最低,可以節省不少時間。

這些列舉一些常見的內存錯誤,供新手參考。
1.         內存泄露。
大家都知道,在堆上分配的內存,如果不再使用了,應該把它釋放掉,以便后面其它地方可以重用。在C/C++中,內存管理器不會幫你自動回收不再使用的內存。如果你忘了釋放不再使用的內存,這些內存就不能被重用,就造成了所謂的內存泄露。

把內存泄露列為首位,倒并不是因為它有多么嚴重的后果,而因為它是最為常見的一類錯誤。一兩處內存泄露通常不至于讓程序崩潰,也不會出現邏輯上的錯誤,加上進程退出時,系統會自動釋放該進程所有相關的內存,所以內存泄露的后果相對來說還是比較溫和的。當然了,量變會產生質變,一旦內存泄露過多以致于耗盡內存,后續內存分配將會失敗,程序可能因此而崩潰。
現在的PC機內存夠大了,加上進程有獨立的內存空間,對于一些小程序來說,內存泄露已經不是太大的威脅。但對于大型軟件,特別是長時間運行的軟件,或者嵌入式系統來說,內存泄露仍然是致命的因素之一。

不管在什么情況下,采取比較謹慎的態度,杜絕內存泄露的出現,都是可取的。相反,認為內存有的是,對內存泄露放任自流都不是負責的。盡管一些工具可以幫助我們檢查內存泄露問題,我認為還是應該在編程時就仔細一點,及早排除這類錯誤,工具只是用作驗證的手段。

2.         內存越界訪問。
內存越界訪問有兩種:一種是讀越界,即讀了不屬于自己的數據,如果所讀的內存地址是無效的,程度立刻就崩潰了。如果所讀內存地址是有效的,在讀的時候不會出問題,但由于讀到的數據是隨機的,它會產生不可預料的后果。另外一種是寫越界,又叫緩沖區溢出。所寫入的數據對別人來說是隨機的,它也會產生不可預料的后果。

內存越界訪問造成的后果非常嚴重,是程序穩定性的致命威脅之一。更麻煩的是,它造成的后果是隨機的,表現出來的癥狀和時機也是隨機的,讓BUG的現象和本質看似沒有什么聯系,這給BUG的定位帶來極大的困難。

一些工具可以夠幫助檢查內存越界訪問的問題,但也不能太依賴于工具。內存越界訪問通常是動態出現的,即依賴于測試數據,在極端的情況下才會出現,除非精心設計測試數據,工具也無能為力。工具本身也有一些限制,甚至在一些大型項目中,工具變得完全不可用。比較保險的方法還是在編程是就小心,特別是對于外部傳入的參數要仔細檢查。

3.         野指針。
野指針是指那些你已經釋放掉的內存指針。當你調用free(p)時,你真正清楚這個動作背后的內容嗎?你會說p指向的內存被釋放了。沒錯,p本身有變化嗎?答案是p本身沒有變化。它指向的內存仍然是有效的,你繼續讀寫p指向的內存,沒有人能攔得住你。

釋放掉的內存會被內存管理器重新分配,此時,野指針指向的內存已經被賦予新的意義。對野指針指向內存的訪問,無論是有意還是無意的,都為此會付出巨大代價,因為它造成的后果,如同越界訪問一樣是不可預料的。

釋放內存后立即把對應指針置為空值,這是避免野指針常用的方法。這個方法簡單有效,只是要注意,當然指針是從函數外層傳入的時,在函數內把指針置為空值,對外層的指針沒有影響。比如,你在析構函數里把this指針置為空值,沒有任何效果,這時應該在函數外層把指針置為空值。
 
4.         訪問空指針。
空指針在C/C++中占有特殊的地址,通常用來判斷一個指針的有效性。空指針一般定義為0。現代操作系統都會保留從0開始的一塊內存,至于這塊內存有多大,視不同的操作系統而定。一旦程序試圖訪問這塊內存,系統就會觸發一個異常。
 
操作系統為什么要保留一塊內存,而不是僅僅保留一個字節的內存呢?原因是:一般內存管理都是按頁進行管理的,無法單純保留一個字節,至少要保留一個頁面。保留一塊內存也有額外的好處,可以檢查諸如p=NULL; p[1]之類的內存錯誤。
 
在一些嵌入式系統(如arm7)中,從0開始的一塊內存是用來安裝中斷向量的,沒有MMU的保護,直接訪問這塊內存好像不會引發異常。不過這塊內存是代碼段的,不是程序中有效的變量地址,所以用空指針來判斷指針的有效性仍然可行。
 
在訪問指針指向的內存時,在確保指針不是空指針。訪問空指針指向的內存,通常會導致程度崩潰,或者不可預料的錯誤。
 
5.         引用未初始化的變量。
未初始化變量的內容是隨機的(像VC一類的編譯器會把它們初始化為固定值,如0xcc),使用這些數據會造成不可預料的后果,調試這樣的BUG也是非常困難的。
 
對于態度嚴謹的程度員來說,防止這類BUG非常容易。在聲明變量時就對它進行初始化,是一個編程的好習慣。另外也要重視編譯器的警告信息,發現有引用未初始化的變量,立即修改過來。
 
6.         不清楚指針運算。
對于一些新手來說,指針常常讓他們犯糊涂。
 
比如int *p = …; p+1等于(size_t)p + 1嗎
老手自然清楚,新手可能就搞不清了。事實上, p+n 等于 (size_t)p + n * sizeof(*p)
 
指針是C/C++中最有力的武器,功能非常強大,無論是變量指針還是函數指針,都應該掌握都非常熟練。只要有不確定的地方,馬上寫個小程序驗證一下。對每一個細節都了然于胸,在編程時會省下不少時間。
 
7.         結構的成員順序變化引發的錯誤。
在初始化一個結構時,老手可能很少像新手那樣老老實實的,一個成員一個成員的為結構初始化,而是采用快捷方式,如:
Structs
{
    int   l;
    char* p;
};
 
intmain(intargc, char* argv[])
{
    structss1 = {4, "abcd"};
    return 0;
}
 
以上這種方式是非常危險的,原因在于你對結構的內存布局作了假設。如果這個結構是第三方提供的,他很可能調整結構中成員的相對位置。而這樣的調整往往不會在文檔中說明,你自然很少去關注。如果調整的兩個成員具有相同數據類型,編譯時不會有任何警告,而程序的邏輯上可能相距十萬八千里了。
 
正確的初始化方法應該是(當然,一個成員一個成員的初始化也行):
structs
{
    int   l;
    char* p;
};
 
intmain(intargc, char* argv[])
{
    structss1 = {.l=4, .p = "abcd"};
    structss2 = {l:4, p:"abcd"};
 
    return 0;
}
 
8.         結構的大小變化引發的錯誤。
我們看看下面這個例子:
structbase
{
    intn;
};
 
structs
{
    structbaseb;
    intm;
};
 
在OOP中,我們可以認為第二個結構繼承了第一結構,這有什么問題嗎?當然沒有,這是C語言中實現繼承的基本手法。
 
現在假設第一個結構是第三方提供的,第二個結構是你自己的。第三方提供的庫是以DLL方式分發的,DLL最大好處在于可以獨立替換。但隨著軟件的進化,問題可能就來了。
 
當第三方在第一個結構中增加了一個新的成員int k;,編譯好后把DLL給你,你直接給了客戶了。程序加載時不會有任何問題,在運行邏輯可能完全改變!原因是兩個結構的內存布局重疊了。解決這類錯誤的唯一辦法就是全部重新相關的代碼。
 
解決這類錯誤的唯一辦法就是重新編譯全部代碼。由此看來,DLL并不見得可以動態替換,如果你想了解更多相關內容,建議閱讀《COM本質論》。
      
9.         分配/釋放不配對。
大家都知道malloc要和free配對使用,new要和delete/delete[]配對使用,重載了類new操作,應該同時重載類的delete/delete[]操作。這些都是書上反復強調過的,除非當時暈了頭,一般不會犯這樣的低級錯誤。
 
而有時候我們卻被蒙在鼓里,兩個代碼看起來都是調用的free函數,實際上卻調用了不同的實現。比如在Win32下,調試版與發布版,單線程與多線程是不同的運行時庫,不同的運行時庫使用的是不同的內存管理器。一不小心鏈接錯了庫,那你就麻煩了。程序可能動則崩潰,原因在于在一個內存管理器中分配的內存,在另外一個內存管理器中釋放時出現了問題。
 
10.     返回指向臨時變量的指針
大家都知道,棧里面的變量都是臨時的。當前函數執行完成時,相關的臨時變量和參數都被清除了。不能把指向這些臨時變量的指針返回給調用者,這樣的指針指向的數據是隨機的,會給程序造成不可預料的后果。
 
下面是個錯誤的例子:
char* get_str(void)
{
    charstr[] = {"abcd"};
 
    returnstr;
}
 
int main(int argc, char* argv[])
{
    char* p = get_str();
 
    printf("%s\n", p);
 
    return 0;
}
 
 
下面這個例子沒有問題,大家知道為什么嗎?
char* get_str(void)
{
    char* str = {"abcd"};
 
    returnstr;
}
 
intmain(intargc, char* argv[])
{
    char* p = get_str();
 
   printf("%s\n", p);
 
    return 0;
}
 
11.     試圖修改常量
在函數參數前加上const修飾符,只是給編譯器做類型檢查用的,編譯器禁止修改這樣的變量。但這并不是強制的,你完全可以用強制類型轉換繞過去,一般也不會出什么錯。
 
而全局常量和字符串,用強制類型轉換繞過去,運行時仍然會出錯。原因在于它們是是放在.rodata里面的,而.rodata內存頁面是不能修改的。試圖對它們修改,會引發內存錯誤。
 
下面這個程序在運行時會出錯:
intmain(intargc, char* argv[])
{
    char* p = "abcd";
 
    *p = '1';
 
    return 0;
}
 
 
12.     誤解傳值與傳引用
在C/C++中,參數默認傳遞方式是傳值的,即在參數入棧時被拷貝一份。在函數里修改這些參數,不會影響外面的調用者。如:
 
#include <stdlib.h>
#include <stdio.h>
 
void get_str(char* p)
{
    p = malloc(sizeof("abcd"));
    strcpy(p, "abcd");
 
    return;
}
 
int main(int argc, char* argv[])
{
    char* p = NULL;
 
    get_str(p);
 
    printf("p=%p\n", p);
 
    return 0;
}
 
在main函數里,p的值仍然是空值。
 
13.     重名符號。
無論是函數名還是變量名,如果在不同的作用范圍內重名,自然沒有問題。但如果兩個符號的作用域有交集,如全局變量和局部變量,全局變量與全局變量之間,重名的現象一定要堅決避免。gcc有一些隱式規則來決定處理同名變量的方式,編譯時可能沒有任何警告和錯誤,但結果通常并非你所期望的。
 
下面例子編譯時就沒有警告:
t.c
#include <stdlib.h>
#include <stdio.h>
 
intcount = 0;
 
intget_count(void)
{
    returncount;
}
 
 
main.c
#include <stdio.h>
 
extern int get_count(void);
 
int count;
 
int main(int argc, char* argv[])
{
    count = 10;
 
    printf("get_count=%d\n", get_count());
 
    return 0;
}
 
如果把main.c中的int count;修改為int count = 0;,gcc就會編輯出錯,說multiple definition of `count'。它的隱式規則比較奇妙吧,所以還是不要依賴它為好。
 
14.     棧溢出。
我們在前面關于堆棧的一節講過,在PC上,普通線程的棧空間也有十幾M,通常夠用了,定義大一點的臨時變量不會有什么問題。
 
而在一些嵌入式中,線程的棧空間可能只5K大小,甚至小到只有256個字節。在這樣的平臺中,棧溢出是最常用的錯誤之一。在編程時應該清楚自己平臺的限制,避免棧溢出的可能。
 
15.     誤用sizeof。
盡管C/C++通常是按值傳遞參數,而數組則是例外,在傳遞數組參數時,數組退化為指針(即按引用傳遞),用sizeof是無法取得數組的大小的。
 
從下面這個例子可以看出:
voidtest(charstr[20])
{
    printf("%s:size=%d\n", __func__, sizeof(str));
}  
 
intmain(intargc, char* argv[])
{
    charstr[20] = {0};
 
    test(str);
 
    printf("%s:size=%d\n", __func__, sizeof(str));
   
    return 0;
}
[root@localhost mm]# ./t.exe
test:size=4
main:size=20
 
16.     字節對齊。
字節對齊主要目的是提高內存訪問的效率。但在有的平臺(如arm7)上,就不光是效率問題了,如果不對齊,得到的數據是錯誤的。
 
所幸的是,大多數情況下,編譯會保證全局變量和臨時變量按正確的方式對齊。內存管理器會保證動態內存按正確的方式對齊。要注意的是,在不同類型的變量之間轉換時要小心,如把char*強制轉換為int*時,要格外小心。
 
另外,字節對齊也會造成結構大小的變化,在程序內部用sizeof來取得結構的大小,這就足夠了。若數據要在不同的機器間傳遞時,在通信協議中要規定對齊的方式,避免對齊方式不一致引發的問題。
 
17.     字節順序。
字節順序歷來是設計跨平臺軟件時頭疼的問題。字節順序是關于數據在物理內存中的布局的問題,最常見的字節順序有兩種:大端模式與小端模式。
 
大端模式是高位字節數據存放在低地址處,低位字節數據存放在高地址處。
小端模式指低位字節數據存放在內存低地址處,高位字節數據存放在內存高地址處;
 
       比如long n = 0x11223344。
      
模式
第1個字節
第2個字節
第3個字節
第4個字節
大端模式
0x11
0x22
0x33
0x44
小端模式
0x44
0x33
0x22
0x11
 
在普通軟件中,字節順序問題并不引人注目。而在開發與網絡通信和數據交換有關的軟件時,字節順序問題就要特殊注意了。
 
18.     多線程共享變量沒有用valotile修飾。
在關于全局內存的一節中,我們講了valotile的作用,它告訴編譯器,不要把變量優化到寄存器中。在開發多線程并發的軟件時,如果這些線程共享一些全局變量,這些全局變量最好用valotile修飾。這樣可以避免因為編譯器優化而引起的錯誤,這樣的錯誤非常難查。
 
可能還有其它一些內存相關錯誤,一時想不全面,這里算是拋磚引玉吧,希望各位高手補充。
~~end~~

posted on 2011-05-15 23:30 pp_zhang 閱讀(896) 評論(2)  編輯 收藏 引用 所屬分類: c/c++

評論

# re: c++內存錯誤  回復  更多評論   

很好
很全面
2011-05-16 15:26 | 雙龍

# re: c++內存錯誤  回復  更多評論   

高手 我有個程序就會報不能寫的錯誤 看了您的博文 我還是不知道怎么改 能不能幫我看一下
2011-11-30 16:27 | 默默小蚊子
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            亚洲成人在线视频播放| 亚洲欧美精品在线观看| 亚洲大胆在线| 在线欧美日韩国产| 91久久精品一区二区别| 亚洲精品国产精品乱码不99| 亚洲精品日韩欧美| 一区二区三区高清不卡| 午夜日韩福利| 久久精品久久99精品久久| 久久精品国产亚洲aⅴ| 你懂的亚洲视频| 亚洲激情六月丁香| 亚洲天堂久久| 久久一区二区三区国产精品 | 亚洲激情在线观看视频免费| 亚洲国产成人91精品| 日韩亚洲欧美在线观看| 亚洲一区二区久久| 另类综合日韩欧美亚洲| 欧美精品 国产精品| 国产精品视频网| 亚洲国产婷婷| 欧美一区二区视频网站| 欧美激情视频一区二区三区在线播放| 欧美好吊妞视频| 亚洲国产精品999| 国产精品99久久久久久有的能看| 久久国产色av| 欧美日韩国产三级| 亚洲国产一二三| 在线亚洲成人| 久久午夜电影网| 亚洲人成网站精品片在线观看| 亚洲深夜影院| 免播放器亚洲一区| 国产伦精品一区二区三区在线观看 | 久久国产精品一区二区三区四区| 久久av老司机精品网站导航| 欧美精品偷拍| 亚洲成在人线av| 久久精品国语| 亚洲一级黄色| 欧美日韩三级视频| 亚洲黄色性网站| 久久久噜噜噜久久中文字幕色伊伊| 99视频+国产日韩欧美| 免费久久99精品国产自| 国产一区二区三区在线免费观看| 亚洲视频一区二区免费在线观看| 亚洲成人在线网| 久久日韩精品| 亚洲第一区中文99精品| 毛片av中文字幕一区二区| 欧美尤物巨大精品爽| 国产拍揄自揄精品视频麻豆| 香蕉成人久久| 香蕉久久久久久久av网站| 国产精品亚洲а∨天堂免在线| 国产精品99久久久久久久久| 亚洲免费观看视频| 欧美午夜宅男影院在线观看| 一区二区三区蜜桃网| 亚洲人被黑人高潮完整版| 亚洲一区二区三区激情| 亚洲国产国产亚洲一二三| 亚洲欧美综合| 国产一区二区三区高清| 久久蜜桃av一区精品变态类天堂| 香蕉久久夜色精品国产| 国产综合第一页| 噜噜噜久久亚洲精品国产品小说| 久久久蜜桃一区二区人| 亚洲国产精品福利| 亚洲国产日韩欧美在线图片 | 亚洲一区二区三区欧美 | 亚洲小视频在线| 欧美日韩色婷婷| 麻豆精品网站| 韩日成人av| 久久婷婷国产综合尤物精品| 亚洲影视在线| 国内激情久久| 欧美福利视频| 欧美激情第1页| 亚洲深夜影院| 亚洲欧美成人精品| 亚洲国产成人91精品 | 免费成人av| 亚洲欧洲美洲综合色网| 亚洲黄色成人| 欧美日韩成人在线播放| 性伦欧美刺激片在线观看| 久久gogo国模裸体人体| 亚洲人成在线观看网站高清| 欧美视频在线观看免费| 一区二区欧美激情| 国产精品久久久久久久久久久久久久| 亚洲深夜福利在线| 亚洲欧美视频在线观看| 在线日韩视频| 日韩香蕉视频| 国语自产精品视频在线看一大j8 | 美女91精品| 欧美日韩成人一区二区三区| 久久精品国产第一区二区三区| 看片网站欧美日韩| 香蕉久久夜色| 欧美国产日韩视频| 久久人体大胆视频| 国产精品九九| 亚洲电影观看| 国产精品久久久久国产精品日日| 久久野战av| 国产精品久久久久婷婷| 亚洲国产精品国自产拍av秋霞 | 欧美夫妇交换俱乐部在线观看| 亚洲欧美日韩国产成人| 欧美第十八页| 暖暖成人免费视频| 韩国av一区二区三区| 亚洲婷婷综合久久一本伊一区| 亚洲欧洲在线看| 久久久综合激的五月天| 欧美在线视频全部完| 欧美手机在线视频| 最新精品在线| 亚洲人成高清| 奶水喷射视频一区| 你懂的视频一区二区| 激情一区二区| 久久久久久久一区二区三区| 久久久精彩视频| 韩国精品在线观看| 欧美一区二区三区精品电影| 午夜视频一区在线观看| 欧美激情一区二区三区在线视频| 欧美va亚洲va国产综合| 樱花yy私人影院亚洲| 久久国产精品一区二区| 久久精品二区三区| 国产日韩欧美一区二区三区在线观看| 99re成人精品视频| 亚洲午夜电影在线观看| 欧美日韩在线视频首页| 一区二区欧美精品| 午夜精品一区二区三区在线视| 国产精品久久久亚洲一区| 亚洲一区二区三区三| 久久精品国产v日韩v亚洲 | 欧美日韩xxxxx| 日韩一级大片在线| 亚洲一区二区黄| 欧美日韩一区二区在线观看视频| 亚洲美女色禁图| 亚洲欧美在线磁力| 国产欧美日韩一级| 欧美亚洲一区二区三区| 蜜臀久久99精品久久久画质超高清| 一区二区亚洲精品| 欧美电影免费| 9l视频自拍蝌蚪9l视频成人| 亚洲欧美另类国产| 国产午夜精品麻豆| 欧美成人a视频| 中日韩高清电影网| 久久人人九九| 99热精品在线观看| 国产伦精品一区二区三区照片91 | 欧美日韩国产精品一区| 亚洲视频在线观看视频| 久久久久久日产精品| 亚洲美女免费视频| 国产一区二区三区高清| 国产一区二区高清视频| 久久久欧美一区二区| 亚洲精品视频在线观看网站 | 麻豆精品在线观看| 一区二区免费在线播放| 国产亚洲一区二区三区在线观看| 蜜臀久久99精品久久久久久9 | 日韩视频一区二区在线观看| 国产精品国产一区二区| 鲁鲁狠狠狠7777一区二区| 在线一区亚洲| 亚洲电影在线观看| 久久国产精品一区二区三区| 亚洲最新视频在线| 激情成人在线视频| 国产精品亚洲片夜色在线| 欧美国产日韩一区| 久久精品人人做人人爽| 亚洲视频欧洲视频| 亚洲激情在线观看视频免费| 久久久久久久一区| 性做久久久久久免费观看欧美| 亚洲免费av网站| 亚洲国产精品久久| 伊人久久大香线蕉综合热线| 国产精品一区免费观看|