繁体   English   中英

为什么64位VC ++编译器在函数调用后添加nop指令?

[英]Why does 64-bit VC++ compiler add nop instruction after function calls?

我使用Visual Studio C ++ 2008 SP1, x64 C++编译器编译了以下内容:

在此输入图像描述

我很好奇,为什么编译器会在那些call之后添加那些nop指令?

PS1。 我会理解第二和第三个nop将是4字节边距上的代码对齐,但第一个nop打破了这个假设。

PS2。 编译的C ++代码中没有循环或特殊的优化内容:

CTestDlg::CTestDlg(CWnd* pParent /*=NULL*/)
    : CDialog(CTestDlg::IDD, pParent)
{
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);

    //This makes no sense. I used it to set a debugger breakpoint
    ::GdiFlush();
    srand(::GetTickCount());
}

PS3。 附加信息: 首先,谢谢大家的意见。

以下是其他观察结果:

  1. 我的第一个猜测是增量链接可能与它有关。 但是,项目的Visual StudioRelease构建设置具有incremental linking

  2. 这似乎只影响x64版本。 构建为x86 (或Win32 )的相同代码没有那些nop ,即使使用的指令非常相似:

在此输入图像描述

  1. 我尝试使用更新的链接器构建它,即使VS 2013生成的x64代码看起来有些不同,它仍会在一些call之后添加那些nop

在此输入图像描述

  1. dynamicstatic链接到MFC也没有区别存在那些nop 这个与VS 2013动态链接到MFC dll:

在此输入图像描述

  1. 还要注意的是那些nop S能后出现nearfar call S作为很好,他们什么都没有做比对。 以下是我从IDA获得的代码的一部分,如果我再进一步说明:

在此输入图像描述

如您所见,在far call之后插入nop ,恰好“对齐” B地址上的下一个lea指令! 如果仅为了对齐而添加这些内容毫无意义。

  1. 我本来倾向于认为,因为near relative call (即那些以E8开头的call )比far call s(或以FF开头的那些,在这种情况下为15更快一些

在此输入图像描述

链接器可能首先尝试near call s,并且因为它们比far call s短一个字节,如果成功,它可以在末尾用nop s填充剩余空间。 但是上面的例子(5)有点打败了这个假设。

所以我仍然没有明确的答案。

这纯粹是猜测,但它可能是某种SEH优化。 我说优化是因为SEH似乎在没有NOP的情况下工作正常。 NOP可能有助于加速平仓。

在下面的示例中( 使用VC2017进行实时演示 ),在test1调用basic_string::assign后插入了NOP ,但在test2没有(相同但声明为非抛出1 )。

#include <stdio.h>
#include <string>

int test1() {
  std::string s = "a";  // NOP insterted here
  s += getchar();
  return (int)s.length();
}

int test2() throw() {
  std::string s = "a";
  s += getchar();
  return (int)s.length();
}

int main()
{
  return test1() + test2();
}

部件:

test1:
    . . .
    call     std::basic_string<char,std::char_traits<char>,std::allocator<char> >::assign
    npad     1         ; nop
    call     getchar
    . . .
test2:
    . . .
    call     std::basic_string<char,std::char_traits<char>,std::allocator<char> >::assign
    call     getchar

请注意,MSVS默认使用/EHsc标志进行编译(同步异常处理)。 如果没有那个标志, NOP消失,并且使用/EHa (同步异步异常处理), throw()不再/EHa ,因为SEH始终打开。


1由于某些原因,只有throw()似乎减少了代码大小,使用noexcept使生成的代码更大并且召唤更多的NOP MSVC ...

这是一个特殊的填充程序,让异常处理程序/展开函数正确检测它是否是函数的序言/结尾/正文。

这是由于x64中的调用约定要求堆栈在任何调用指令之前对齐16字节。 这不是(我的知识)硬件要求,而是软件要求。 这提供了一种方法来确保在进入函数时(即,在调用指令之后),堆栈指针的值总是8模16。因此允许从堆栈中的对齐位置进行简单的数据对齐和存储/读取。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM