成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

C++程序運行過程中發(fā)生異常閃退,很有可能是這三個原因?qū)е碌?

Kross / 9077人閱讀

摘要:當出現(xiàn)這種運行一段時間后的異常閃退,很有可能是以下三種原因?qū)е碌?。程序在運行過程中發(fā)生異?;蛘唛W退,可能就是有線程發(fā)生棧溢出導致的。

目錄

1、綜述

2、GDI對象泄露

3、Stack Overflow線程棧溢出

4、內(nèi)存泄露


? ? ? ?Windows應用軟件在交付給客戶使用或者試用后,可能會因為操作系統(tǒng)版本及硬件上的差異,出現(xiàn)這樣那樣的軟件異常問題。特別是項目即將交互等待客戶驗收時,出現(xiàn)多種莫名其妙的異常問題,是比較棘手的,在有限的時間內(nèi)去搞定這些異常問題的壓力也是比較大的。下面以以往遇到的多個項目問題為例,簡單地說一下三種比較典型的軟件異常問題,希望能給大家提供一定的借鑒或參考。

1、綜述

? ? ? ?Windows應用軟件在發(fā)布之前,在公司內(nèi)部進行了詳細的測試,基本已經(jīng)達到穩(wěn)定狀態(tài),但公司內(nèi)部進行的測試是有限的,有限的人力、有限的機器環(huán)境,始終是無法覆蓋所有的問題場景的。當軟件發(fā)布到各式各樣的客戶手中,可能會因為操作系統(tǒng)及硬件上的差異,出現(xiàn)這樣那樣的異常問題。

? ? ? ?從操作系統(tǒng)上看,Windows操作系統(tǒng)就有多個大版本,比如XP、Win7、Win8、Win10,甚至Win11就要出來。除了大版本之外,每個同系列上還有各種子版本,在系統(tǒng)特性上都有著或大或小的差異。作為Windows軟件都要兼容這些常用的、不同版本的操作系統(tǒng),這給軟件的平穩(wěn)運行帶來了挑戰(zhàn)。

? ? ? ?除了操作系統(tǒng)之外,還有各式各樣的硬件,對應著各自的硬件驅(qū)動程序,這給軟件的良好運行帶來了更大的挑戰(zhàn)。所以,當軟件拿到多個客戶的機器上運行,出現(xiàn)這樣那樣的問題是在所難免的,作為軟件的提供方,我們只能盡力將出問題的概率降到最低,在出現(xiàn)問題后要第一時間去響應、去解決。

? ? ? ?和客戶側(cè)的Windows終端應用軟件相比,大多數(shù)服務器側(cè)的軟件則要幸運的多,它們一般不用去面對各式各樣的軟硬件環(huán)境。因為服務器側(cè)的操作系統(tǒng)和硬件設備都是產(chǎn)品提供商定制好了,使用固定的硬件,使用固定版本的Linux操作系統(tǒng)(當然也有服務器使用Windows Server等不同操作系統(tǒng)的),服務器側(cè)產(chǎn)品在發(fā)布之前已經(jīng)能保證在這些固定的軟件環(huán)境中持續(xù)穩(wěn)定的運行。

? ? ? ? 回到本文的主題,本文研究的對象是客戶終端側(cè)的Windows C++軟件,下面就來切入本文的正題。今天我們要講的這幾類異常有個共同的特點就是,軟件剛啟動時運行還算平穩(wěn),CPU和內(nèi)存占用都比較正常,但運行一段時間后或較長一段時間后可能會莫名其妙地異常閃退。當出現(xiàn)這種運行一段時間后的異常閃退,很有可能是以下三種原因?qū)е碌摹R皇前l(fā)生了GDI對象泄露,二是發(fā)生了線程棧溢出,三是發(fā)生了內(nèi)存泄露。這三種異?;旧隙伎赡苁沁\行一段時間才會出現(xiàn)的,甚至有時是很難復現(xiàn)的,因為這些異??赡苁悄承┎僮鞑艜|發(fā)的,如果用戶沒有執(zhí)行這些操作,可能就不會就不會爆出這些問題了。

2、GDI對象泄露

? ? ? ?程序運行一段時間后,當GDI對象達到10000個左右,導致程序崩潰閃退。

Windows系統(tǒng)中,進程中的GDI對象總數(shù)不能超過10000個。當進程的GDI對象總數(shù)接近或超過10000個時就會導致GDI繪圖出現(xiàn)異常,API函數(shù)調(diào)用返回失敗,甚至出現(xiàn)閃退崩潰。

? ? ? ?如果代碼中有Pen、Brush、Bitmap、Font、RegionDC等GDI對象泄露時,且這段代碼會頻繁的執(zhí)行,可能指定某一操作后才會頻繁的觸發(fā)泄露代碼的執(zhí)行。在程序運行的過程中GDI對象會快速的增長,當GDI達到10000個左右時,會出現(xiàn)各種GDI函數(shù)繪圖失敗的問題,可以通過GetLastError獲取繪制失敗的原因。

? ? ? 緊接著程序可能就會出現(xiàn)異常崩潰閃退了。多久能達到10000萬個上限,和泄露的程度有關系,也和程序運行的時間長短有關。有時可能半個小時或幾個小時會出現(xiàn),有時需要長時間拷機才會出現(xiàn)。至于拷機,有多種形式,比如下班后的夜間拷機,周末休息期間的長時間拷機運行。

? ? ? ?GDI對象泄露問題該如何感知并排查呢?可以先查看系統(tǒng)的任務管理器,持續(xù)觀察目標進程的GDI總數(shù)的變化,如果GDI對象有明顯增長就說明可能存在GDI對象泄露了。然后再打開GDIView工具,看看具體各類型的GDI對象的數(shù)目:

找出數(shù)值異常的那一種GDI對象。知道發(fā)生泄露的GDI對象類型后,就可以結(jié)合代碼進行逐步排查了。

? ? ? ?這種觀察方式需要人工進行,對于無人值守的情況該怎么處理呢?比如最簡單就是使用按鍵精靈等自動化測試工具去拷機,去執(zhí)行某一些操作,看看長時間運行是否會有問題,如果是監(jiān)測GDI對象是否有泄露,可以每隔若干時間就讓按鍵精靈調(diào)用截圖程序截一張桌面的圖片,并保存到指定的目錄中,第二天來上班后可以看這些圖片去判斷。

? ? ? ?比如如下的代碼,在函數(shù)結(jié)尾的時候不去刪除之前創(chuàng)建的GDI對象,就會導致GDI對象泄露:

// 拷貝桌面,lpRect 代表選定區(qū)域,bSave 標記是否將圖片內(nèi)容保存到剪切板中HBITMAP CCatchScreenDlg::CopyScreenToBitmap( LPRECT lpRect ) {                           	// 確保選定區(qū)域不為空矩形	if ( IsRectEmpty( lpRect ) )	{		return NULL;	}	CUIString strLog;	HWND hWndDeskTop = ::GetDesktopWindow();	//HDC hScrDC = ::CreateDC( _T("DISPLAY"), NULL, NULL, NULL );	HDC hScrDC = ::GetDC( hWndDeskTop ); // 為屏幕創(chuàng)建設備描述表	if ( hScrDC == NULL )	{		strLog.Format( _T("[CCatchScreenDlg::CopyScreenToBitmap] 創(chuàng)建DISPLAY失敗, GetLastError: %d"), 			GetLastError() );		WriteScreenCatchLog( strLog );		return NULL;	}	HDC hMemDC = ::CreateCompatibleDC( hScrDC ); // 為屏幕設備描述表創(chuàng)建兼容的內(nèi)存設備描述表	if ( hMemDC == NULL )	{		strLog.Format( _T("[CCatchScreenDlg::CopyScreenToBitmap]創(chuàng)建與hScrDC兼容的hMemDC失敗, GetLastError: %d"), 			GetLastError() );		WriteScreenCatchLog( strLog );		//::DeleteDC( hScrDC );		::ReleaseDC( hWndDeskTop, hScrDC );		return NULL;	}	int nX = 0;	int nY = 0;	int nX2 = 0;	int nY2 = 0;   	int nWidth = 0; 	int nHeight = 0;	// 保證left小于right,top小于bottom	CDirectRect rc = *lpRect;	rc.Normalize();	// 獲得選定區(qū)域坐標	nX = rc.left;	nY = rc.top;	nX2 = rc.right;	nY2 = rc.bottom;	// 確保選定區(qū)域是可見的	if ( nX < 0 )	{		nX = 0;	}	if ( nY < 0 )	{		nY = 0;	}	if ( nX2 > m_xScreen )	{		nX2 = m_xScreen;	}	if ( nY2 > m_yScreen )	{		nY2 = m_yScreen;	}	nWidth = nX2 - nX;	nHeight = nY2 - nY;	//HBITMAP hBitmap = ::CreateCompatibleBitmap( hScrDC, nWidth, nHeight ); // 創(chuàng)建一個與屏幕設備描述表兼容的位圖	HBITMAP hBitmap = CreateDIBBitmap( nWidth, nHeight );	if ( hBitmap == NULL )	{		strLog.Format( _T("[CCatchScreenDlg::CopyScreenToBitmap]創(chuàng)建與hScrDC兼容的Bitmap失敗, GetLastError: %d"), 			GetLastError() );		WriteScreenCatchLog( strLog );		//::DeleteDC( hScrDC );		::ReleaseDC( hWndDeskTop, hScrDC );		::DeleteDC( hMemDC );		return NULL;	}	::SelectObject( hMemDC, hBitmap ); 	// 把新位圖選到內(nèi)存設備描述表中	BOOL bRet = FALSE;	BOOL bProcessed = FALSE;	if ( IsOSWin7OrAbove() )	{		DEVMODE curDevMode;		memset( &curDevMode, 0, sizeof(curDevMode) );		curDevMode.dmSize = sizeof(DEVMODE);		BOOL bEnumRet = ::EnumDisplaySettings( NULL, ENUM_CURRENT_SETTINGS, &curDevMode );		//strLog.Format( _T("[CCatchScreenDlg::CopyScreenToBitmap]m_xScreen: %d, curDevMode.dmPelsWidth: %d"), 		//	m_xScreen, curDevMode.dmPelsWidth );		//WriteScreenCatchLog( strLog );		if ( bEnumRet && m_xScreen < curDevMode.dmPelsWidth )		{			bProcessed = TRUE;			::SetStretchBltMode( hMemDC, STRETCH_HALFTONE );			bRet = ::StretchBlt( hMemDC, 0, 0, nWidth, nHeight, hScrDC, 0, 0, curDevMode.dmPelsWidth, 				curDevMode.dmPelsHeight, SRCCOPY|CAPTUREBLT );		}	}	if ( !bProcessed )	{		bRet = ::BitBlt( hMemDC, 0, 0, nWidth, nHeight, hScrDC, nX, nY, SRCCOPY | CAPTUREBLT );  // CAPTUREBLT - 該參數(shù)保證能夠截到透明窗口	}    	if ( !bRet )	{		strLog.Format( _T("[CCatchScreenDlg::CopyScreenToBitmap]將hScrDC拷貝到hMemDC失敗, GetLastError: %d"), 			GetLastError() );		WriteScreenCatchLog( strLog );		//::DeleteDC( hScrDC );		::ReleaseDC( hWndDeskTop, hScrDC );		::DeleteDC( hMemDC );		::DeleteObject( hBitmap );		return NULL;	}	if ( hScrDC != NULL )	{		//::DeleteDC( hScrDC );		::ReleaseDC( hWndDeskTop, hScrDC );	}	if ( hMemDC != NULL )	{		::DeleteDC( hMemDC );	}	return hBitmap; // hBitmap資源不能釋放,因為函數(shù)外部要使用}

3、Stack Overflow線程棧溢出

? ? ? ?如果某個時刻線程的函數(shù)調(diào)用堆棧中所有函數(shù)占用??臻g總數(shù)超過當前線程的棧空間上限,就會產(chǎn)生Stack Overflow的線程棧溢出的異常,程序就會閃退崩潰。

每個線程的??臻g是有上限的,在Windows中,每個線程的??臻g默認是1MB,在創(chuàng)建線程時可以自定義線程的??臻g上限值。

? ? ? ?線程棧溢出的異常,一般發(fā)生在剛進入到被調(diào)用的函數(shù)時,函數(shù)的??臻g是在函數(shù)入口的地方分配的。程序在運行過程中發(fā)生異?;蛘唛W退,可能就是有線程發(fā)生棧溢出導致的。對應棧溢出的異常,在VS中調(diào)試是看不到異常時的函數(shù)調(diào)用堆棧的,因為發(fā)生異常時進程直接退出調(diào)試了,只能看到發(fā)生了棧溢出的提示文字,但看不到發(fā)生棧溢出時的函數(shù)調(diào)用堆棧。

? ? ? ?可以使用windbg查看到異常時的函數(shù)調(diào)用堆棧。將windbg附加到目標進程上,當目標進程發(fā)生棧溢出時windbg就能捕獲到異常中斷下來,此時輸入kn等命令就可以查看到此刻的函數(shù)調(diào)用堆棧,就能找到產(chǎn)生異常的線索了。

線程發(fā)生棧溢出一般有一下幾個原因:

1)函數(shù)遞歸調(diào)用的深度過深,函數(shù)一直沒返回,??臻g一直沒有釋放;

2)消息上觸發(fā)函數(shù)的死循環(huán)調(diào)用,函數(shù)始終退不出來,函數(shù)的棧空間釋放;

3)定義了一個占用內(nèi)存很大的局部變量

4)函數(shù)中使用switch...case語句,包含了大量的case分支,每個case分支中都定義了局部變量,導致當前函數(shù)占用了大量的??臻g。

? ? ? ?對于switch...case...語句中有多個case分支的情況,case分支中的局部變量的聲明周期是在case分支中的,即代碼運行到對應的case分支中時該分支中的局部變量才有“生命”,但其實這個局部變量的??臻g已經(jīng)在函數(shù)入口處分配好??臻g了,并不是代碼執(zhí)行到case子句中才分配??臻g的。這點可以通過編寫測試代碼,查看函數(shù)入口處給當前函數(shù)分配??臻g的匯編代碼就能看出來了,可以先頂一個變量查看匯編代碼看看分配了多少??臻g,然后再增加一個變量,看看分配的??臻g是否變大。

4、內(nèi)存泄露

? ? ? ?當代碼中有內(nèi)存泄露,當將所在進程的內(nèi)存耗盡時,就會出現(xiàn)“run out of memory”的崩潰:

? ? ? ?和GDI泄露類似的,有可能是某一塊的代碼有內(nèi)存泄露,在執(zhí)行某些操作時才會執(zhí)行有內(nèi)存泄露的代碼,才會觸發(fā)內(nèi)存泄漏。同樣,何時會導致“run out of memory”的崩潰,與泄露的程度、運行的時長有直接的關系。

? ? ? ?對于32位程序,系統(tǒng)會給該進程分配4GB的虛擬地址空間,一般是2GB的用戶態(tài)內(nèi)存和2GB內(nèi)核態(tài)的內(nèi)存。隨著泄露的內(nèi)存越來越多,將4GB的虛擬地址空間耗完了,就會出現(xiàn)“run out of memory”的崩潰了。

? ? ? ?發(fā)現(xiàn)和排查內(nèi)存泄露,也GDI泄露的處理方式也是類似的。先通過查看資源管理器中的目標進程的內(nèi)存占用情況,一般情況下,程序正常運行時只會占用幾百MB的內(nèi)存,如果在資源管理器中發(fā)現(xiàn)目標進程的內(nèi)存都漲到1GB以上,并且長時間處于1GB以上的占用,且不會回落,那大概率是有內(nèi)存泄露了。

? ? ? ?現(xiàn)在有很多內(nèi)存泄露檢測工具,比如BoundsChecker,但是很多已經(jīng)過時了,不能使用了。Windows下主要用Windbg調(diào)試器去排查,Linux下主要使用Valgrind內(nèi)存檢測工程。關于windbg如何排查內(nèi)存泄露,參看我的這篇文章:

使用Windbg定位Windows C++程序中的內(nèi)存泄露https://blog.csdn.net/chenlycly/article/details/121295720https://blog.csdn.net/chenlycly/article/details/121295720

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/123735.html

相關文章

  • 是學Java好呢?還是學C++更有前途?

    摘要:最近有不少初學編程的朋友問他們比較傾向于和作為他們首選學習語言,但是學好呢還是學更有前途到底哪一門語言更有錢途呢這個問題問的好,很多初學者都會有類似的疑問,今天我就來給大家簡單的解答一下。 ? ? ? ? 最近有不少初學編程的朋友問:他們比較傾向于Java和C++作為他們首選學習語言,但是...

    jiekechoo 評論0 收藏0
  • 內(nèi)存 問題- 收藏集 - 掘金

    摘要:然而,中依然有可能發(fā)生內(nèi)存泄漏。所以你的安卓快速定位解決內(nèi)存泄漏掘金昨天是個好日子,程序員的節(jié)日,在這里給所有的程序員送上一份遲到的祝福。應用內(nèi)存泄漏的定位分析與解決策略掘金,大家好,我是。 Android 性能優(yōu)化之巧用軟引用與弱引用優(yōu)化內(nèi)存使用 - Android - 掘金前言: 從事Android開發(fā)的同學都知道移動設備的內(nèi)存使用是非常敏感的話題,今天我們來看下如何使用軟引用與弱...

    TIGERB 評論0 收藏0
  • Emscripten教程之優(yōu)化你代碼

    摘要:優(yōu)化項也會引發(fā)一些問題。檢查你的代碼是否工作并修復問題。從起,及以上的優(yōu)化級別默認啟動了這項設置。目前正在進行改進。代碼移植系列文章代碼移植主題系列文章是中文站點的一部分內(nèi)容。 作者:云荒杯傾歡迎加入Wasm和emscripten技術(shù)交流群,群聊號碼:939206522。 這是關于Emscripten的系列文章,更多文章請看下面鏈接。 Emscripten代碼移植系列文章 Emscr...

    Jokcy 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<