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

时间:2017-06-30 20:37:48

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

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

enter image description here

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

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. 我的第一个猜测是incremental linking可能与它有关。但是,项目Release中的Visual Studio版本设置已关闭incremental linking

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

  3. enter image description here

    1. 我尝试使用更新的链接器构建它,即使x64生成的VS 2013代码看起来有些不同,它仍会在nop之后添加calldynamic } S:
    2. enter image description here

      1. 同时staticnop链接到MFC对这些VS 2013的存在没有任何影响。这个是使用nop
      2. 与MFC dll动态链接构建的

        enter image description here

        1. 另请注意,这些near也可以在farcall IDA之后显示,而且它们与对齐无关。这是我从nop获得的代码的一部分,如果我再继续一步:
        2. enter image description here

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

          1. 我原本倾向于相信,因为relative call E8 s(即以far开头的那些)somewhat faster而不是call } FF s(或以15开头的那些,在这种情况下为near
          2. enter image description here

            链接器可能会先尝试使用call far,因为它们比call nop短一个字节,如果成功,它可能会填充结尾有 dt <- factor(c("No", "Yes", "Residue"), levels = c("No", "Yes", "Residue")) s的剩余空格。但是上面的例子(5)有点打败了这个假设。

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

3 个答案:

答案 0 :(得分:3)

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

在以下示例(live demo with VC2017)中,在NOP中调用basic_string::assigntest1中没有test2后插入了#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(); } 声明为非投掷 1 )。

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

大会:

/EHsc

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

1 由于某种原因,只有noexcept似乎减少了代码大小,使用NOP会使生成的代码更大,并且召唤更多{{1}} s。 MSVC ...

答案 1 :(得分:0)

这是特殊的填充符,用于让异常处理程序/展开函数正确地检测它是否是函数的序言/结尾/主体。

答案 2 :(得分:-2)

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