¿Cuál es la diferencia entre código nativo, código máquina y código ensamblador?

106

Estoy confundido sobre el código máquina y el código nativo en el contexto de los lenguajes .NET.

¿Cuál es la diferencia entre ellos? ¿Son lo mismo?

samaladeepak
fuente
3
Tengo una pregunta con respecto a esta pregunta. ¿Esta pregunta entra dentro del requisito de StackOverflow? afaik no lo es, pero al mismo tiempo este tipo de pregunta es muy útil / informativa. Suponiendo que este tipo de preguntas no está permitido, ¿dónde deberíamos hacer este tipo de preguntas si no es aquí?
Yousuf Azad

Respuestas:

150

De hecho, los términos son un poco confusos, porque a veces se usan de manera inconsistente.

Código de máquina: este es el mejor definido. Es un código que usa las instrucciones de código de bytes que su procesador (la pieza física de metal que hace el trabajo real) comprende y ejecuta directamente. El resto del código debe traducirse o transformarse en código de máquina antes de que su máquina pueda ejecutarlo.

Código nativo: este término se usa a veces en lugares donde se hace referencia al código de máquina (ver arriba). Sin embargo, a veces también se usa para referirse a código no administrado (ver más abajo).

Código no administrado y código administrado: el código no administrado se refiere al código escrito en un lenguaje de programación como C o C ++, que se compila directamente en código de máquina . Contrasta con el código administrado , que está escrito en C #, VB.NET, Java o similar, y se ejecuta en un entorno virtual (como .NET o JavaVM), que “simula” un procesador en software. La principal diferencia es que el código administrado "administra" los recursos (principalmente la asignación de memoria) por usted empleando la recolección de basura y manteniendo opacas las referencias a los objetos. Código no administradoes el tipo de código que requiere que asigne y desasigne memoria manualmente, lo que a veces provoca pérdidas de memoria (cuando se olvida de desasignar) y, a veces, fallas de segmentación (cuando desasigna demasiado pronto). No administrado también suele implicar que no hay comprobaciones en tiempo de ejecución de errores comunes, como desreferenciación de puntero nulo o desbordamiento de límites de matriz.

Estrictamente hablando, la mayoría de los lenguajes de tipado dinámico, como Perl, Python, PHP y Ruby, también son código administrado . Sin embargo, no se describen comúnmente como tales, lo que muestra que el código administrado es en realidad un término de marketing para los entornos de programación comercial realmente grandes y serios (.NET y Java).

Código ensamblador: este término generalmente se refiere al tipo de código fuente que la gente escribe cuando realmente quiere escribir código de bytes. Un ensamblador es un programa que convierte este código fuente en código de bytes real. No es un compilador porque la transformación es 1 a 1. Sin embargo, el término es ambiguo en cuanto a qué tipo de código de bytes se usa: podría ser administrado o no administrado. Si no está administrado, el código de bytes resultante es código de máquina . Si se administra, el código de bytes se usa detrás de escena por un entorno virtual como .NET. El código administrado (por ejemplo, C #, Java) se compila en este lenguaje de código de bytes especial, que en el caso de .NET se denomina Lenguaje Intermedio Común (CIL) y en Java se denomina código de bytes de Java.. Por lo general, el programador común tiene poca necesidad de acceder a este código o escribir en este lenguaje directamente, pero cuando la gente lo hace, a menudo se refiere a él como código ensamblador porque usa un ensamblador para convertirlo en código de bytes.

Timwi
fuente
C ++ puede compilarse en código de máquina, pero muy a menudo se compila en otros formatos como exe que se ejecutarán con un sistema operativo.
Gordon Gustafson
Hay lenguajes que admiten la recolección de basura y referencias opacas que normalmente se compilan en código de máquina. Las implementaciones más serias de Common Lisp hacen eso. Lo que diga puede ser cierto para los lenguajes compatibles con Microsoft, pero hay más lenguajes compilados que los que admite Visual Studio.
David Thornley
3
@CrazyJugglerDrummer: El código contenido en los archivos EXE generados por los compiladores de C ++ sigue siendo código de máquina. @David Thornley: mencioné muchos más idiomas que solo esos, pero no quería complicar las cosas al mencionar todas las extrañas rarezas.
Timwi
Algunos compiladores, muchos, en realidad compilan desde C / C ++ u otros lenguajes en lenguaje ensamblador, luego llaman al ensamblador y el ensamblador lo convierte en archivos de objeto que son en su mayoría código de máquina pero necesitan algunos toques antes de que puedan ingresar a la memoria del procesador. el enlazador enlaza todo a la versión en código máquina del programa. El punto es C / C ++, etc. a menudo no se compila directamente en el código de máquina, es invisible para el usuario y hace dos o tres pasos en el camino. TCC, por ejemplo, es una excepción a esto, va directamente al código de máquina.
old_timer
Esto se siente como un fastidio, pero no todos los ensambladores traducen 1-1 en códigos de operación. De hecho, muchos ensambladores modernos admiten construcciones de abstracción como clases. Ejemplo: TASM, ensamblador de Borland. en.wikipedia.org/wiki/TASM
Primer
45

Lo que ve cuando usa Depurar + Windows + Desmontaje al depurar un programa C # es una buena guía para estos términos. Aquí hay una versión anotada de la misma cuando compilo un programa 'hola mundo' escrito en C # en la configuración de lanzamiento con la optimización JIT habilitada:

        static void Main(string[] args) {
            Console.WriteLine("Hello world");
00000000 55                push        ebp                           ; save stack frame pointer
00000001 8B EC             mov         ebp,esp                       ; setup current frame
00000003 E8 30 BE 03 6F    call        6F03BE38                      ; Console.Out property getter
00000008 8B C8             mov         ecx,eax                       ; setup "this"
0000000a 8B 15 88 20 BD 02 mov         edx,dword ptr ds:[02BD2088h]  ; arg = "Hello world"
00000010 8B 01             mov         eax,dword ptr [ecx]           ; TextWriter reference
00000012 FF 90 D8 00 00 00 call        dword ptr [eax+000000D8h]     ; TextWriter.WriteLine()
00000018 5D                pop         ebp                           ; restore stack frame pointer
        }
00000019 C3                ret                                       ; done, return

Haga clic con el botón derecho en la ventana y marque "Mostrar bytes de código" para obtener una pantalla similar.

La columna de la izquierda es la dirección del código de la máquina. Su valor es falsificado por el depurador, el código está ubicado en otro lugar. Pero eso podría ser en cualquier lugar, dependiendo de la ubicación seleccionada por el compilador JIT, por lo que el depurador simplemente comienza a numerar direcciones desde 0 al comienzo del método.

La segunda columna es el código de la máquina . Los 1 y 0 reales que ejecuta la CPU. El código de máquina, como aquí, se muestra comúnmente en hexadecimal. Quizás ilustrativo es que 0x8B selecciona la instrucción MOV, los bytes adicionales están ahí para decirle a la CPU exactamente lo que necesita moverse. También tenga en cuenta los dos tipos de la instrucción CALL, 0xE8 es la llamada directa, 0xFF es la instrucción de llamada indirecta.

La tercera columna es el código ensamblador . El ensamblaje es un lenguaje simple, diseñado para facilitar la escritura de código de máquina. Se compara con la compilación de C # en IL. El compilador utilizado para traducir el código ensamblador se llama "ensamblador". Probablemente tenga el ensamblador de Microsoft en su máquina, su nombre ejecutable es ml.exe, ml64.exe para la versión de 64 bits. Hay dos versiones comunes de lenguajes ensambladores en uso. El que ves es el que usan Intel y AMD. En el mundo del código abierto, el ensamblaje en la notación de AT&T es común. La sintaxis del lenguaje depende en gran medida del tipo de CPU para el que se escribió, el lenguaje ensamblador de un PowerPC es muy diferente.

Bien, eso aborda dos de los términos de su pregunta. El "código nativo" es un término confuso, no es infrecuente que se utilice para describir código en un lenguaje no administrado. Quizás sea instructivo ver qué tipo de código de máquina genera un compilador de C. Esta es la versión 'hola mundo' en C:

int _tmain(int argc, _TCHAR* argv[])
{
00401010 55               push        ebp  
00401011 8B EC            mov         ebp,esp 
    printf("Hello world");
00401013 68 6C 6C 45 00   push        offset ___xt_z+128h (456C6Ch) 
00401018 E8 13 00 00 00   call        printf (401030h) 
0040101D 83 C4 04         add         esp,4 
    return 0;
00401020 33 C0            xor         eax,eax 
}
00401022 5D               pop         ebp  
00401023 C3               ret   

No lo anoté, principalmente porque es muy similar al código de máquina generado por el programa C #. La llamada a la función printf () es bastante diferente de la llamada Console.WriteLine () pero todo lo demás es casi igual. También tenga en cuenta que el depurador ahora está generando la dirección del código de máquina real y que es un poco más inteligente con los símbolos. Un efecto secundario de generar información de depuración después de generar código de máquina, como suelen hacer los compiladores no administrados. También debo mencionar que desactivé algunas opciones de optimización de código de máquina para que el código de máquina se vea similar. Los compiladores de C / C ++ tienen mucho más tiempo disponible para optimizar el código, el resultado es a menudo difícil de interpretar. Y muy difícil de depurar.

El punto clave aquí es que existen muy pocas diferencias entre el código máquina generado a partir de un lenguaje administrado por el compilador JIT y el código máquina generado por un compilador de código nativo. Esta es la razón principal por la que el lenguaje C # puede competir con un compilador de código nativo. La única diferencia real entre ellos son las llamadas a la función de soporte. Muchos de los cuales se implementan en CLR. Y eso gira principalmente en torno al recolector de basura.

Hans Passant
fuente
6

El código nativo y el código de máquina son lo mismo: los bytes reales que ejecuta la CPU.

El código ensamblador tiene dos significados: uno es el código de máquina traducido a una forma más legible por humanos (con los bytes de las instrucciones traducidos a mnemónicos cortos parecidos a palabras como "JMP" (que "salta" a otro lugar en el código). El otro es el código de bytes IL (bytes de instrucción que generan compiladores como C # o VB, que eventualmente terminarán traducidos al código de máquina, pero que aún no lo están) que vive en una DLL o EXE.

cHao
fuente
2

En .NET, los ensamblados contienen código de lenguaje intermedio de MS (MSIL, a veces CIL).
Es como un código de máquina de "alto nivel".

Cuando se carga, MSIL es compilado por el compilador JIT en código nativo (código de máquina Intel x86 o x64).

Henk Holterman
fuente