大多数处理器为处理不同类型的数据提供了不同的指令,因此类型信息通常被“嵌入”到生成的机器代码中。无需存储其他类型的元数据。
一些具体的例子可能会有所帮助。以下机器代码是在运行SuSE Linux Enterprise Server(SLES)10的x86_64系统上使用gcc 4.1.2生成的。
假定以下源代码:
int main( void )
{
int x, y, z;
x = 1;
y = 2;
z = x + y;
return 0;
}
这是与上面的源相对应的生成的汇编代码的内容(使用gcc -S
),我添加了注释:
main:
.LFB2:
pushq %rbp ;; save the current frame pointer value
.LCFI0:
movq %rsp, %rbp ;; make the current stack pointer value the new frame pointer value
.LCFI1:
movl $1, -12(%rbp) ;; x = 1
movl $2, -8(%rbp) ;; y = 2
movl -8(%rbp), %eax ;; copy the value of y to the eax register
addl -12(%rbp), %eax ;; add the value of x to the eax register
movl %eax, -4(%rbp) ;; copy the value in eax to z
movl $0, %eax ;; eax gets the return value of the function
leave ;; exit and restore the stack
ret
接下来还有一些其他内容ret
,但这与讨论无关。
%eax
是32位通用数据寄存器。 %rsp
是保留用于保存堆栈指针的64位寄存器,该指针包含压入堆栈的最后一件事的地址。 %rbp
是保留用于保存帧指针的64位寄存器,其中包含当前堆栈帧的地址。输入函数时,会在堆栈上创建一个堆栈框架,并为该函数的参数和局部变量保留空间。通过使用帧指针的偏移量来访问参数和变量。在这种情况下,该变量的内存x
位于存储在中的地址“下方”的12个字节%rbp
。
在上述代码中,我们使用指令将x
(1 的整数值,存储在-12(%rbp)
)复制到寄存器%eax
中,该movl
指令用于将32位字从一个位置复制到另一位置。然后addl
,我们调用,将y
(存储在-8(%rbp)
)的整数值添加到中已有的值%eax
。然后-4(%rbp)
,我们将结果保存到z
。
现在让我们对其进行更改,以便处理double
值而不是int
值:
int main( void )
{
double x, y, z;
x = 1;
y = 2;
z = x + y;
return 0;
}
gcc -S
再次运行给我们:
main:
.LFB2:
pushq %rbp
.LCFI0:
movq %rsp, %rbp
.LCFI1:
movabsq $4607182418800017408, %rax ;; copy literal 64-bit floating-point representation of 1.00 to rax
movq %rax, -24(%rbp) ;; save rax to x
movabsq $4611686018427387904, %rax ;; copy literal 64-bit floating-point representation of 2.00 to rax
movq %rax, -16(%rbp) ;; save rax to y
movsd -24(%rbp), %xmm0 ;; copy value of x to xmm0 register
addsd -16(%rbp), %xmm0 ;; add value of y to xmm0 register
movsd %xmm0, -8(%rbp) ;; save result to z
movl $0, %eax ;; eax gets return value of function
leave ;; exit and restore the stack
ret
几个差异。代替movl
和addl
,我们使用movsd
和addsd
(分配并添加双精度浮点数)。%eax
我们使用代替存储临时值%xmm0
。
这就是我说类型已“嵌入”到机器代码中时的意思。编译器只是生成正确的机器代码来处理该特定类型。