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

122

在.NET语言中,我对于机器码和本地码感到困惑。

它们有什么区别?它们是相同的吗?


3
我有一个关于这个问题的问题。这个问题是否符合 StackOverflow 的要求?据我所知,它不符合,但同时这种问题非常有帮助/信息量丰富。假设这种类型的问题不被允许,在这里不能问这类问题,我们应该去哪里问呢? - Yousuf Azad
参见:https://dev59.com/_HRC5IYBdhLWcg3wVvUt - T.Todua
4个回答

172

这些术语确实有点混淆,因为它们有时候被不一致地使用。

机器码: 这是最明确定义的术语。它是使用字节码指令编写的代码,这些指令是您的处理器(实际执行工作的物理金属部件)理解并直接执行的。在执行其他代码之前,必须将所有其他代码转换或转化为机器码

本地代码: 该术语有时在指代机器码(参见上文)时使用。但是,有时也用于指代非托管代码(参见下文)。

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

严格来说,大多数动态类型语言-例如Perl、Python、PHP和Ruby-也是托管代码。但是,它们不常被描述为此类代码,这表明托管代码实际上是一种营销术语,适用于真正大型、严肃、商业化的编程环境(.NET和Java)。

汇编代码: 该术语通常是指那些想要编写字节码的源代码。 汇编器 是一个将这个源代码转换为真正的字节码的程序。它不属于 编译器 ,因为转换是1对1的。然而,该术语在使用什么样的字节码方面是模棱两可的:它可以是托管或非托管的。如果是非托管的,则生成的字节码就是 机器码。如果是托管的,则产生的字节码是由类似 .NET 这样的虚拟环境在幕后使用的字节码。托管代码(例如 C#、Java)被编译成这种特殊的字节码语言,在 .NET 的情况下称为 公共中间语言 (CIL) ,在 Java 中称为 Java 字节码。通常情况下,普通程序员很少需要访问或直接使用这段代码,但是当人们这样做时,他们经常把它称为 汇编代码,因为他们使用 汇编器 将其转换为字节码。


C++可以编译成机器码,但很常被编译成其他格式,比如exe,以便在操作系统上运行。 - Gordon Gustafson
4
@CrazyJugglerDrummer:由C++编译器生成的EXE文件中包含的代码仍然是机器码。@David Thornley:我提到了比那些语言更多的语言,但我不想通过提及每个奇怪的鲜为人知的语言来使事情变得复杂。 - Timwi
一些编译器,很多,实际上会将C/C++或其他语言编译成汇编语言,然后调用汇编器将其转换为目标文件,这些文件大部分是机器码,但在进入处理器内存之前需要进行一些处理,然后链接器将所有内容链接成程序的机器码版本。重点是C/C++等通常不直接编译成机器码,对用户来说它在过程中是不可见的,需要经过两到三个步骤。例如,TCC就是一个例外,它可以直接生成机器码。 - old_timer
1
这感觉像是吹毛求疵,但并非所有汇编器都能一对一地转换为操作码。事实上,许多现代汇编器支持类似类的抽象结构。例如:TASM、Borland 的汇编器。http://en.wikipedia.org/wiki/TASM - Prime
感谢您对不同类型的低级代码进行深入解释。我以为只有机器码/汇编(是同一件事)。很高兴知道有所区别,以及本地代码实际上意味着什么......托管/非托管代码对我来说也是一个新术语,所以谢谢您。好答案+1。 - Arthur Bowers
显示剩余2条评论

49
当您在调试C#程序时使用Debug + Windows + Disassembly时看到的内容是这些术语的很好指南。以下是我使用启用JIT优化的Release配置编译的C#“hello world”程序的注释版本:
        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开始。
第二列是机器代码。CPU执行的实际1和0。机器码通常以十六进制显示。可参考的是,0x8B选择MOV指令,其他字节用于告诉CPU需要移动的内容。还要注意两种CALL指令,0xE8是直接调用,0xFF是间接调用指令。
第三列是汇编代码。汇编语言是一种简单的语言,旨在更容易编写机器代码。它类似于将C#编译为IL。用于翻译汇编代码的编译器称为“汇编器”。您可能已经在计算机上安装了Microsoft汇编器,其可执行文件名为ml.exe,64位版本为ml64.exe。目前有两个常见的汇编语言版本。您看到的是Intel和AMD使用的版本。在开源世界中,使用AT&T符号的汇编语言很常见。语言语法与编写代码所用的CPU类型密切相关,PowerPC的汇编语言差别很大。
好的,这解决了你问题中的两个术语。 “本机代码”是一个含糊的术语,通常用于描述非托管语言中的代码。值得注意的是,C编译器生成的机器码是什么样子的。这是在C中的“hello world”版本:
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()函数调用与Console.WriteLine()调用有很大不同,但其他部分几乎相同。请注意,调试器现在正在生成实际的机器代码地址,并且对符号更加智能。这是生成调试信息之后生成机器代码的副作用,就像不受管辖的编译器经常做的一样。我还应该提到,我关闭了一些机器代码优化选项,使机器代码看起来类似。 C / C ++编译器有更多时间可用于优化代码,结果通常难以解释。而且非常难调试。

关键点是由JIT编译器从托管语言生成的机器代码和由本地代码编译器生成的机器代码之间几乎没有区别。这就是C#语言可以与本地代码编译器竞争的主要原因。它们之间唯一的真正区别是支持函数调用。其中许多实现在CLR中。这主要涉及垃圾收集器。


6

本地代码和机器码是同一件事情 - CPU执行的实际字节。

汇编代码有两个意思:一个是将机器码翻译成更易读的形式(使用类似于“JMP”这样的短词汇来翻译指令的字节,该指令会“跳转”到代码中的另一个位置)。另一个是IL字节码(由C#或VB等编译器生成的指令字节,最终将被翻译为机器码,但目前还没有),它存在于DLL或EXE中。


这个回答含糊不清,歪曲了真正的定义。 - RobbB

2
在.NET中,程序集包含MS Intermediate Language代码(MSIL,有时称为CIL)。它类似于“高级”机器码。
当加载时,MSIL由JIT编译器编译成本地代码(Intel x86或x64机器码)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接