VS2013關於lambda和區域性類共用產生的問題
阿新 • • 發佈:2018-12-30
注意以下 ConstructFromCommandLine函式中 ProcessInformation 結構和 pi 變數構造時傳遞的 lambda表示式,以及lambda表示式的捕獲列表中的&sResult和return sResult;語句。這些程式碼組合導致VS2013編譯出一段神奇的結果。
若在ResumeThread失敗後(為了測試在 if 中增加了 || 1 )向 sResult 中寫入錯誤描述字串,然後執行 return sResult返回,但在真正返回前會先析構 pi 物件,而析構 pi 物件時又會呼叫一個lambda表示式向 sResult 中追加內容。所以,sResult的內容應該是 ResumeThread失敗後寫入的字串和 pi 的解構函式中呼叫lambda表示式寫入的字串這兩部分。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; }
而實際用VS2013測試的結果是,sResult只包含了ResumeThread失敗後寫入的部分,不包括 pi 解構函式中寫入的。
下圖是VS2013生成的彙編程式碼:
以我的理解,應該對調01556F57(構造用於返回的新String物件)和01556F81(調和pi的解構函式)兩個地址處程式碼才正確。但為何編譯器會編譯出這樣的結果?還望高人解惑。
現在暫只記下不能這樣做……