我整理了以下使用Visual Studio C ++ 2008 SP1,x64
C++
编译:
我很好奇,为什么编译器添加这些nop
s后那些call
说明?
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。附加信息:首先,谢谢大家对你的输入。
这里的补充意见:
Release
在Visual Studio
构建设置的项目有incremental linking
关闭。x64
只依据。建成x86
(或Win32
)相同的代码没有这些nop
s,即使使用的指令非常相似:x64
产生的VS 2013
代码看起来有些不同,但仍然有一些nop
s之后将那些call
s:dynamic
VS static
链接到MFC做这些nop
s的存在没有什么区别。这是一个与动态链接构建与VS 2013
到MFC DLL文件:nop
和near
far
s以及那些call
s可以出现,他们什么都没有做比对。下面是我从IDA
了,如果我再上一步一点点的代码的一部分:正如你看到的,nop
的是,发生在“对齐”下一个far
指令在call
地址lea
B
之后插入!这是没有意义的,如果这些都仅用于比对增加。
near
relative
call
s(即那些与E8
开始)比somewhat faster far
s call
(或与FF
开始的,15
在这种情况下)接头可以尝试先用near
call
s去了,因为这些是一个字节比far
call
s短,如果成功,它可能垫nop
s的剩余空间在最后。但随后的示例(5)上述有点儿失败这一假说。
所以,我仍然没有一个明确的答案。
这纯粹是一种猜测,但它可能是某种SEH的优化。我说的优化,因为SEH似乎没有NOP指令来工作也没关系。 NOP可能有助于加速展开。
在下面的例子(live demo with VC2017),有一个呼叫之后插入一个NOP
在basic_string::assign
但不是在test1
test2
(相同的,但声明为不可throwing1)。
#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
s消失,并与/EHa
(同步和异步异常处理),throw()
不再有差别,因为SEH始终打开。
1出于某种原因,只throw()
似乎减少了代码大小,使用noexcept
使得生成的代码更大,召唤更NOP
s。 MSVC ...
这是特殊的填料,让异常处理程序/平仓功能正确检测无论是在功能的序言/结尾/体。
这是由于在64位调用约定要求堆栈是任何呼叫指令之前对准的16个字节。这不是(我knwoledge)硬件要求,但软件之一。这提供了一种以确保进入的函数(即,呼叫指令之后)时,堆栈指针的值始终是8模16因此允许简单的数据alignement和存储/从对齐位置在堆读取。