1. 程式人生 > >VS2013關於lambda和區域性類共用產生的問題

VS2013關於lambda和區域性類共用產生的問題

注意以下 ConstructFromCommandLine函式中 ProcessInformation 結構和 pi 變數構造時傳遞的 lambda表示式,以及lambda表示式的捕獲列表中的&sResult和return sResult;語句。這些程式碼組合導致VS2013編譯出一段神奇的結果。

String CDebugger::ConstructFromCommandLine( __in LPCTSTR lpCommandLine )
{
	/*	析構時會呼叫 TerminateProcess終止 hProcess的類。*/
	struct ProcessInformation : public PROCESS_INFORMATION
	{
		ProcessInformation( function< VOID( PROCESS_INFORMATION & )> pfnErrorNotify )
			: pfnTerminateFailedNotify( pfnErrorNotify )
		{ ZeroMemory( static_cast<LPPROCESS_INFORMATION>(this), sizeof( PROCESS_INFORMATION ) ); }

		~ProcessInformation()
		{
			if( hThread != NULL )
				ENSURE( CloseHandle( hThread ) );

			if( hProcess != NULL )
			{
				if( !TerminateProcess( hProcess, ERROR_SUCCESS ) &&
					WaitForSingleObject( hProcess, IGNORE ) != WAIT_OBJECT_0 || 1 )
					pfnTerminateFailedNotify( *this );

				ENSURE( CloseHandle( hProcess ) );
			}
		}
		function< VOID( PROCESS_INFORMATION &) > pfnTerminateFailedNotify;
	};

	String sResult;
	ProcessInformation pi( [&sResult, this, lpCommandLine]( PROCESS_INFORMATION &pi )->VOID
	{
		CONST DWORD dwErrno( GetLastError() );
		if( dwErrno != ERROR_SUCCESS )
		{
			sResult.AppendFormat( _T( "\r\n錯誤程式碼:0x%08x\r\n描述:%s\r\n" ), 
				dwErrno, (LPCTSTR)String::FormatMessage( dwErrno ) );
		}
		sResult.AppendFormat( _T( "\r\n使用 “%s” 命令列啟動 %s(%d) 程序成功," ) \
			_T( "但偵錯程式附加失敗後未能終止建立的程序,請嘗試手動終止該程序;" ),
			lpCommandLine, PathFindFileName( (LPCTSTR)m_sDebugTargetImagePath ), pi.dwProcessId );
	} );

	String sCmdLine( lpCommandLine );
	STARTUPINFO si{ sizeof( STARTUPINFO ) };
	// 啟動目標程序
	if( !CreateProcess( NULL, 
		sCmdLine.GetBufferReserveLength( sCmdLine.GetLength() + DEFAULT_BUFFER_SIZE ),
		NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi ) )
	{
		sResult.Format( _T( "使用 “%s” 命令啟動程序失敗。" ), lpCommandLine );
		return move( sResult );
	}

	// 注入抓取器。
	sResult = InjectRobberModule( pi.hProcess, (LPCTSTR)m_sRobberModulePathName, TRUE );
	if( !sResult.IsEmpty() ){
		return sResult;
	}

	// 載入除錯符號。
	if( !LoadSymbol( m_sSymbolsDirectory.IsEmpty() ? NULL : (LPCTSTR)m_sSymbolsDirectory, pi.hProcess ) )
	{
		// 允許載入除錯符號失敗。但要向stdout輸出錯誤訊息。
		// VS程序內可通過重寫STARTUPINFO中的hStdOutput把輸出重定向的VS的輸出視窗。
		DbgPrint( _T( "從 %s 目錄載入除錯符號失敗。Errno:0x%08x,訊息:%s\n" ),
			m_sSymbolsDirectory.IsEmpty() ? _T( "預設" ) : (LPCTSTR)m_sSymbolsDirectory,
			GetLastError(), (LPCTSTR)String::FormatMessage(GetLastError() ) );
	}

	if( ResumeThread( pi.hThread ) != 1 || 1)
	{
		UnloadRobberModule( pi.hProcess, (LPCTSTR)m_sRobberModulePathName );
		sResult.AppendFormat( _T( "使用 “%s” 命令列啟動 %s(%d) 程序成功," ) \
			_T( "但附加除錯模組後未能將其恢復執行,不能繼續除錯;" ),
			lpCommandLine, PathFindFileName( (LPCTSTR)m_sDebugTargetImagePath ), pi.dwProcessId );
		return sResult;
	}

	m_hDebugTargetProcess = pi.hProcess;
	m_dwDebugTargetProcessId = pi.dwProcessId;
	m_bHasAttach = TRUE;
	pi.hProcess = NULL;
	
	return NULL_STRING;
}
若在ResumeThread失敗後(為了測試在 if 中增加了 || 1 )向 sResult 中寫入錯誤描述字串,然後執行 return sResult返回,但在真正返回前會先析構 pi 物件,而析構 pi 物件時又會呼叫一個lambda表示式向 sResult 中追加內容。所以,sResult的內容應該是 ResumeThread失敗後寫入的字串和 pi 的解構函式中呼叫lambda表示式寫入的字串這兩部分。

而實際用VS2013測試的結果是,sResult只包含了ResumeThread失敗後寫入的部分,不包括 pi 解構函式中寫入的。

下圖是VS2013生成的彙編程式碼:


以我的理解,應該對調01556F57(構造用於返回的新String物件)和01556F81(調和pi的解構函式)兩個地址處程式碼才正確。但為何編譯器會編譯出這樣的結果?還望高人解惑。

現在暫只記下不能這樣做……