簡體   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