una tipos tener puede parámetros parametros funciones funcion formales ejemplos cuantos con argumentos c++ visual-studio assembly 64bit disassembly

tipos - ¿Por qué el compilador de VC++ de 64 bits agrega instrucción nop luego de las llamadas a funciones?



parametros formales en c++ (2)

He compilado lo siguiente con Visual Studio C ++ 2008 SP1, compilador x64 C++ :

Tengo curiosidad, ¿por qué el compilador agregó esas instrucciones de nop luego de esas call ?

PS1. Yo entiendo que el 2do y 3er nop s serían alinear el código en un margen de 4 bytes, pero el 1er nop rompe esa suposición.

PS2. El código de C ++ que se compiló no tenía bucles ni elementos especiales de optimización:

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. Información adicional: En primer lugar, gracias a todos por su aporte.

Aquí hay observaciones adicionales:

  1. Mi primera suposición fue que el enlace incremental podría haber tenido algo que ver con eso. Sin embargo, la configuración de compilación Release en Visual Studio para el proyecto tiene un incremental linking desactivado.

  2. Esto parece afectar únicamente a las compilaciones x64 . El mismo código construido como x86 (o Win32 ) no tiene esos nop s, aunque las instrucciones utilizadas son muy similares:

  1. Traté de compilarlo con un enlazador más nuevo, y aunque el código x64 producido por VS 2013 ve algo diferente, todavía agrega esos nop s después de algunas call s:

  1. También static vinculación dynamic vs static a MFC no hizo ninguna diferencia en la presencia de esos nop s. Este está creado con enlaces dinámicos a dlls de MFC con VS 2013 :

  1. También tenga en cuenta que esos nop s pueden aparecer después de las call near y de far también, y no tienen nada que ver con la alineación. Aquí hay una parte del código que obtuve de IDA si avanzo un poco más:

Como puede ver, el nop se inserta después de una call far que sucede para "alinear" la siguiente instrucción lea en la dirección B Eso no tiene sentido si se agregaron solo para la alineación.

  1. Originalmente me inclinaba a creer que, dado que las call relative near (es decir, aquellas que comienzan con E8 ) son algo más rápidas que las call far (o las que comienzan con FF , 15 en este caso)

el enlazador puede tratar de acercarse primero a las call near , y dado que son call un byte más cortas que far , si tiene éxito, puede rellenar el espacio restante con nop s al final. Pero luego el ejemplo (5) de arriba derrota esta hipótesis.

Entonces todavía no tengo una respuesta clara a esto.


Esto es solo una suposición, pero podría ser algún tipo de optimización SEH. Digo optimización porque SEH parece funcionar bien sin los NOP también. NOP podría ayudar a acelerar el desenrollamiento.

En el siguiente ejemplo ( demostración en vivo con VC2017 ), hay un NOP insertado después de una llamada a basic_string::assign en test1 pero no en test2 (idéntico pero declarado como non-throwing 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(); }

Montaje:

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

Tenga en cuenta que MSVS compila de forma predeterminada con el /EHsc (manejo de excepciones sincrónicas). Sin esa bandera, los NOP desaparecerán, y con /EHa (manejo de excepciones sincrónico y asíncrono), throw() ya no hace la diferencia porque SEH siempre está activo.

1 Por alguna razón, solo throw() parece reducir el tamaño del código, utilizando noexcept hace que el código generado sea aún más grande y convoca aún más NOP s. MSVC ...


Esto se debe a una convención de llamadas en x64 que requiere que la pila esté alineada con 16 bytes antes de cualquier instrucción de llamada. Esto no es (para mi conocimiento) un requisito de hardware sino uno de software. Esto proporciona una manera de asegurarse de que al ingresar una función (es decir, después de una instrucción de llamada), el valor del puntero de la pila sea siempre 8 módulo 16. De este modo, permite la alineación simple de datos y el almacenamiento / lectura de la ubicación alineada en la pila.