您的位置:首页 > 其它

VS2013关于lambda和局部类共用产生的问题

2017-06-07 16:57 225 查看
注意以下 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的析构函数)两个地址处代码才正确。但为何编译器会编译出这样的结果?还望高人解惑。

现在暂只记下不能这样做……
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: