函数如何在内存中编码/存储?

8

我知道像数字和字母这样的东西是如何以二进制编码的,因此可以存储为0和1。

但是函数如何存储在内存中呢?我不明白它们怎么能被存储为0和1,也不知道除了0和1之外,还有什么东西可以被存储在内存中。


这是一个非常广泛的问题,答案很大程度上取决于所使用的编程语言(因此它是编译型的、解释型的、转换为字节码的、标记...)。 - jcaron
我给你一个提示:在JPG文件中,所有文件都以特定的字节序列(FF D8和FF D9)开头和结尾。有些序列被编码以便在数据内特别推断它们的类型。 - Unihedron
3个回答

22
他们实际上被存储在内存中,以0和1的形式。
这里有一个现实世界的例子:
int func(int a, int b) {
    return (a + b);
}

这是一个编译器可能为该函数生成的32位x86机器指令示例(以一种称为代码的文本表示形式呈现):
func:
        push    ebp
        mov     ebp, esp
        mov     edx, [ebp+8]
        mov     eax, [ebp+12]
        add     eax, edx
        pop     ebp
        ret

本问题讨论每个指令如何工作超出范围,但其中每个符号(例如add、pop、mov等)及其参数都被编码为1和0。此表格展示了许多英特尔指令及其编码摘要。另请参阅标签维基以获取文档/指南/手册链接。


那么,如何将文本汇编代码转换为机器可读的字节(也称为机器码)呢?以指令add eax, edx为例。此页面展示了add指令的编码方式。eaxedx是一种叫做寄存器的东西,在处理器中用于保存信息进行处理。计算机编程中的变量通常在某个时刻映射到寄存器上。因为我们要添加寄存器,而这些寄存器是32位的,所以我们选择了操作码000000001(请参见英特尔官方指令集参考手册ADD条目,其中列出了所有可用的形式)。
下一步是指定运算数。这个部分在同一页的前一页展示了如何使用示例“add ecx,eax”进行操作,这与我们自己的操作非常相似。前两位必须为“11”,以显示我们正在添加寄存器。接下来的3位指定第一个寄存器,在我们的情况下,我们选择edx而不是他们的示例中的eax ,这使我们得到“100”。接下来的3位指定我们的eax,因此我们最终得到结果。
00000001 11100000

01 D0在十六进制中表示。将任何指令转换为二进制,可以应用类似的过程。自动执行此操作的工具称为汇编器


因此,将上述汇编代码通过汇编器运行会产生以下输出:
66 55 66 89 E5 66 67 8B 55 O8 66 67 8B 45 0C 66 01 D0 66 5D C3

请注意字符串末尾附近的01 D0,这是我们的“add”指令。将机器码字节转换回文本汇编语言助记符称为反汇编:
 address | machine code  |  disassembly
   0:      55              push   ebp
   1:      89 e5           mov    ebp, esp
   3:      8b 55 08        mov    edx, [ebp+0x8]
   6:      8b 45 0c        mov    eax, [ebp+0xc]
   9:      01 d0           add    eax, edx
   b:      5d              pop    ebp
   c:      c3              ret    

地址从零开始,因为这只是一个.o文件,而不是链接的二进制文件。因此,它们只是相对于文件的.text部分的起始位置。
你可以在 Godbolt Compiler Explorer上查看任何你喜欢的函数(或者在你自己的机器上使用反汇编器查看任何二进制文件,无论是新编译的还是旧有的)。
您可能会注意到最终输出中没有提到“func”这个名称。这是因为在机器代码中,函数是通过其在RAM中的位置而不是名称来引用的。编译器输出的目标文件可能在其符号表中具有指向该机器代码块的“func”条目,但符号表由软件读取,而不是CPU硬件可以直接解码和运行的内容。机器代码的位模式直接由CPU中的晶体管看到并解码
有时候我们很难理解计算机如何以低级方式编码指令,因为作为程序员或高级用户,我们有工具可以避免直接处理它们。我们依靠编译器、汇编器和解释器来为我们完成工作。尽管如此,计算机所做的任何事情最终都必须在机器代码中指定。

啊,好的,这很有启发性!虽然我不理解细节,但是我可以看出一个函数只是一系列指令,而这些指令可以编码成0和1。嗯,或者说有点像。你能再详细解释一下指令如何被编码为0和1吗? - Adam Zerner
1
@AdamZerner 当然,我大大扩展了这一部分。小心点,可能有点复杂,但我认为你会理解的。 - Dougvj
@AdamZerner 实际上,相对于图像或视频来说,你不需要那么多。例如,Windows内核中的所有代码编译后大约只有25兆字节的指令,因此当我们谈论视频游戏或其他东西的大小时,涉及纹理和图像的数据远远超过代码。 - Dougvj
1
可能有用的是在每行显示机器码的十六进制转储和反汇编指令助记符+操作数的代码块,例如从objdump -drwC -Mintel中获取。例如像这个x86机器码的codegolf答案一样。Godbolt编译器浏览器为gcc / clang / icc输出提供了显示此内容的二进制模式 - Peter Cordes
1
我添加了一些我认为是改进的东西。但这是你的答案,所以请仔细查看并检查是否喜欢我的更改,如果不喜欢可以回滚或编辑。 - Peter Cordes
显示剩余2条评论

0

函数由指令组成,例如字节码机器码。指令是数字,可以用二进制编码。

关于这个问题的一个很好的介绍是查尔斯·佩兹尔德的书《代码》


-1

我将以最简单的方式解释函数是如何存储的。在本文的结尾,您会惊讶于这一切的惊人简单性。这是最基本的解释,任何类型的计算机都会以某种方式工作。

计算机中唯一能够对数据执行任何操作(例如加法、减法、乘法和除法)的部分。人类存在的所有数据操作(任何类型的数学或公式)都由这些操作组成。

现在让我们来看看二进制指令的基本结构。如果我们正在使用32位机器,则指令将采取以下形式:

1 001 32位地址 32位地址

1(如果此位为1,则将指令转移到逻辑单元进行计算;如果为零,则基本上在两个内存地址之间移动数据)001(这3位确定我们在此指令周期中是添加(001)、减去(010)、乘(011)还是除(100))(第一个内存位置的32位内存地址)(第二个内存地址的32位内存地址)

函数基本上是一系列指令的字符串,用于操作定义的内存位置中的数据。

让我们来看一个随机函数,它先加一个数字,然后再乘。它的指令字符串将是:

(让我用MA表示内存地址)

1 001 MAone MAtwo (将MAone中的值加上MAtwo中的值,并将结果存储在MAone中)

1 011 MAtwo MAthree (将MAtwo中的值乘以MAthree中的值,并将结果存储在MAthree中)

返回MAthree中的值

因此,函数存储的唯一区别是它们在最左边的位上带有1,以便CPU知道它是需要逻辑操作的函数,并将其转向ALU。


人类存在中的每个数据操作(任何类型的数学或公式)都由这些操作组成。您在谈论加/减/乘/除吗?位运算,如AND/OR/XOR,则明显不同,并且在模拟算术运算方面相当痛苦。Popcount、count-leading-zero和shuffle或left-pack(例如x86 pext / pdep)也是单独的原始操作,不便宜模拟。 - Peter Cordes
子和分支足以使计算机成为图灵完备,这意味着它可以计算任何其他操作,但我不会说这意味着加法、乘法和除法是由减法“组成”的。但这是按位AND/OR/XOR“由”你提到的算术运算的唯一意义。或者如果你指的是ALU可以执行的所有操作(包括按位),那么措辞不够清晰。 - Peter Cordes
如果我们正在使用32位机器-很少有32位机器在每个机器指令中具有两个单独的32位地址。可能没有真实世界的设计。这只是你编造的一个假设性例子,具有68位固定宽度的指令,非常不像RISC或x86。 RISC机器将具有4位或5位寄存器号码,而不是32位内存地址,因为这是寄存器的一部分。可变长度机器代码的x86指令可以从1个字节到15个字节,但仅包括最多1个绝对地址。像VAX这样的一些机器可能允许2个,但这很少见。 - Peter Cordes
无论如何,一个过于简化的假设性例子是可以的,但只有在你明确表示的情况下,而不是暗示所有32位机器都是这样,或者细节与典型情况相差甚远。大多数ISA具有更多的操作码位,以及每个指令中作为地址位的数量要少得多(无论是寄存器编号还是内存地址)。许多32位机器使用32位指令,因此即使是单个绝对内存地址也不能成为单个指令的一部分。 - Peter Cordes
我认为忽略寄存器存在的低级解释/示例是没有帮助的。在很多方面,知道它们与内存分开存在非常重要。(例如,了解编译时内存重排序以及如何优化可以影响未使用volatileatomic的共享内存。) - Peter Cordes
我实际上正在尝试将其简化到最抽象和直观的水平,没有细节。这是我最初理解计算机基本工作原理的方式,现在我正在缓慢地深入了解细节。 - Davis Kipchirchir

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