本机代码、机器代码和汇编代码之间有什么区别?

我对.NET 语言中的机器代码和本机代码感到困惑。

他们之间有什么区别? 他们是一样的吗?

61979 次浏览

在.NET 中,程序集包含 中间语言代码(MSIL,有时是 CIL)。
它就像一个“高级”的机器代码。

加载后,MSIL 由 JIT 编译器编译成本机代码(Intel x86或 x64机器代码)。

本机代码和机器代码是一回事—— CPU 执行的实际字节。

汇编代码有两种含义: 一种是将机器代码翻译成更易于阅读的形式(将指令的字节翻译成类似于“ JMP”的简短助记符(“跳转”到代码中的另一个位置)。另一种是存在于 DLL 或 EXE 中的 IL 字节码(编译器如 C # 或 VB 生成的指令字节,最终将转换成机器码,但目前还没有)。

这些术语确实有点令人困惑,因为它们有时使用不一致。

Machine code: This is the most well-defined one. It is code that uses the byte-code instructions which your processor (the physical piece of metal that does the actual work) understands and executes directly. All other code must be translated or transformed into 机器代码 before your machine can execute it.

本机代码: 这个术语有时用于表示 机器代码(见上文)的地方。然而,它有时也被用来表示 非托管代码(见下文)。

非托管代码 managed code: Unmanaged代码是指用 C 或 C + + 等编程语言编写的代码,这些代码直接编译成 机器代码。它与用 C # 、 VB.NET、 Java 或类似语言编写并在虚拟环境(如。NET 或 JavaVM) ,它在软件中“模拟”处理器。主要区别在于,托管代码通过使用垃圾收集和保持对对象的引用不透明来为您“管理”资源(主要是内存分配)。非托管代码是一种需要您手动分配和取消分配内存的代码,有时会导致内存泄漏(当您忘记取消分配时) ,有时会导致分段错误(当您过早取消分配时)。Unmanaged还通常意味着不会对常见错误(如空指针解引用或数组边界溢出)进行运行时检查。

严格地说,大多数动态类型语言ーー如 Perl、 Python、 PHP 和 Ruby ーー也是 托管代码。然而,它们通常不被这样描述,这表明 托管代码实际上是一个营销术语,用于真正大型、严肃的商业编程环境(。NET 及 Java)。

汇编代码: 这个术语通常指人们在真正想要编写字节码时编写的那种源代码。汇编程序是一个将源代码转换为真正的字节码的程序。它不是 编译器,因为转换是1-1。但是,关于使用哪种字节码,这个术语是模糊的: 它可以是托管的,也可以是非托管的。如果是非托管的,则生成的字节码为 机器代码。如果对其进行管理,则会导致虚拟环境(如。NET.托管代码(例如 C # ,Java)被编译成这种特殊的字节码语言。NET 被称为 Common Intermediate Language (CIL),在 Java 中被称为 Java byte-code。普通程序员通常不需要访问这些代码或直接使用这种语言编写代码,但是当人们访问这些代码时,他们通常将其称为 汇编代码,因为他们使用 汇编程序将其转换为字节码。

在调试 C # 程序时使用 Debug + Windows + DisAssembly 所看到的是这些术语的一个很好的指南。下面是我在发行版配置中编译一个使用 C # 编写的‘ hello world’程序,并启用了 JIT 优化的注释版本:

        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

右键单击窗口并勾选“显示代码字节”以获得类似的显示。

左边的列是机器代码地址。它的值被调试器伪造了,代码实际上位于其他地方。但是这可能在任何地方,具体取决于 JIT 编译器选择的位置,因此调试器只需在方法的开始处从0开始对地址进行编号。

The second column is the 机器代码. The actual 1s and 0s that the CPU executes. Machine code, like here, is commonly displayed in hex. Illustrative perhaps is that 0x8B selects the MOV instruction, the additional bytes are there to tell the CPU exactly what needs to be moved. Also note the two flavors of the CALL instruction, 0xE8 is the direct call, 0xFF is the indirect call instruction.

The third column is the 汇编代码. Assembly is a simple language, designed to make it easier to write machine code. It compares to C# being compiled to IL. The compiler used to translate assembly code is called an "assembler". You probably have the Microsoft assembler on your machine, its executable name is ml.exe, ml64.exe for the 64-bit version. There are two common versions of assembly languages in use. The one you see is the one that Intel and AMD use. In the open source world, assembly in the AT&T notation is common. The language syntax is heavily dependent on the kind of CPU for which is was written, the assembly language for a PowerPC is very different.

Okay, that tackles two of the terms in your question. "Native code" is a fuzzy term, it isn't uncommonly used to describe code in an unmanaged language. Instructive perhaps is to see what kind of machine code is generated by a C compiler. This is the 'hello world' version in 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

我没有注释它,主要是因为它对 C # 程序生成的机器代码来说是如此的 相似。Printf ()函数调用与控制台完全不同。WriteLine ()调用,但其他所有内容都大致相同。还要注意,调试器现在正在生成实际的机器代码地址,而且它对符号的处理更加智能一些。生成调试信息的副作用 之后生成机器代码,就像非托管编译器经常做的那样。我还应该提到,我关闭了一些机器代码优化选项,以使机器代码看起来相似。C/C + + 编译器有更多的时间来优化代码,结果往往很难解释。而且 非常很难调试。

这里的关键点是,由 JIT 编译器生成的托管语言的机器代码和由本机代码编译器生成的机器代码之间的 非常几乎没有差别。这是 C # 语言能够与本机代码编译器竞争的主要原因。它们之间唯一真正的区别是支持函数调用。其中许多是在 CLR 中实现的。主要围绕着垃圾收集器。