大多数处理器为处理不同类型的数据提供了不同的指令,因此类型信息通常被“嵌入”到生成的机器代码中。无需存储其他类型的元数据。
一些具体的例子可能会有所帮助。以下机器代码是在运行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。
这就是我说类型已“嵌入”到机器代码中时的意思。编译器只是生成正确的机器代码来处理该特定类型。