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

标签 c++ visual-studio assembly 64-bit disassembly

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

enter image description here

我很好奇,为什么编译器要添加那些 nop之后的说明call s?

PS1。我会理解第二个和第三个 nop s 将在 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. 我的第一个猜测是 incremental linking可能与它有关。但是,Release Visual Studio 中的build设置项目有incremental linking关闭。

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

enter image description here

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

enter image description here

  1. 还有 dynamicstatic链接到 MFC 对这些 nop 的存在没有影响s。这个是通过动态链接到 MFC dll 的 VS 2013 构建的。 :

enter image description here

  1. 还要注意那些 nop s 可以出现在 near 之后和 far call s 也是如此,它们与对齐无关。这是我从 IDA 获得的部分代码如果我再进一步:

enter image description here

如您所见,nopfar 之后插入call这恰好“对齐”下一个 lea B 上的说明地址!如果这些只是为了对齐而添加的,那是没有意义的。

  1. 我原本倾向于相信,自从 near relative call s(即以 E8 开头的那些)是 somewhat fasterfar call s(或以 FF 开头的,在这种情况下为 15)

enter image description here

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

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

最佳答案

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

在以下示例 (live demo with 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() 不再有任何影响,因为 SEH 始终处于开启状态。


1 出于某种原因,只有 throw() 似乎减少了代码大小,使用 noexcept 使生成的代码更大,甚至召唤更多NOP。 MSVC...

关于c++ - 为什么 64 位 VC++ 编译器在函数调用后添加 nop 指令?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44854497/

相关文章:

.net - 从 IntelliSense 中隐藏用户控件属性

c - 链接 c 和程序集

assembly - 当通过 qemu 运行时,简单的引导加载程序位于 RAM 之外

windows - 您如何访问 Windows 命令行参数?

visual-studio - SSIS 添加一个存在的包实际上添加了一个相同的副本

visual-studio - VS PostBuild 事件 - 复制文件(如果存在)

c++ - Qt 不发射信号

c++ - C++中变量和方法可以同名吗?

c++ - 创建一个指针的指针并在不修改原始指针的情况下对其进行修改?

c++ - 有限状态机解析器