繁体   English   中英

处理器如何处理不同的数据类型?

[英]How does a processor handle different data types?

机器如何区分数据类型之间的解释协议,例如:1字节,2字节,4字节整数,浮点数,双精度数,字符和Unicode格式?

我假设存在某种标题符号,以区分对构成最终可执行文件一部分的数据类型有效的操作集的配置,但是如果有人愿意解释,我希望得到一个准确的答案。

通常,处理器/逻辑彼此之间一点也不了解,这主要是在用户或程序员看来。 例如在计算地址时

* ptr ++;

指针只是某个地址上的位,也就是位,因此您将一些位放入寄存器中,然后在一个时钟周期内从这些位中读取数据,因为这些位移向主总线,这些位是一个地址,一些位返回,它们只是位,它们进入寄存器。 这些位使用常量发送到alu,指针可能是一个结构,所以++可能不是一个数字,在任何一种情况下,它也一起发送,或者在某些体系结构中,您必须将其加载到寄存器中第一。 alu对这些位进行一些数学运算,在这几个时钟周期内,这些位是操作数。 加法和减法不知道无符号与带符号,所以这只是位。 那些掉落的东西说到一个寄存器中,现在我们将那些位写入以前使用的地址,该地址只是一个/几个时钟周期的地址,它是从一个寄存器中采样并按其方式发送的。

float只是发送到fpu的位。

字节,半字,字,双字等的传输是由指令或其他逻辑驱动的(通常不使用指令进行提取)在使用现代32或64左右宽度总线的读取上进行读取,该读取具有整个宽度到达处理器核心的目标字节通道被剥离,这取决于体系结构和总线,当然,针对接受字节半字的目标的写操作将采用某种方案,即表示哪些通道有效的字节掩码(与哪些通道无效的字节掩码)或以字节为单位的大小,并且双方同意以某种方式在总线上对齐这些字节(位通道0到n-1是最有意义的,但无论它们是什么设计的)。

现在,通常对计算机不友好的计算机语言对位有很多看法,此变量​​就是这种类型,而该变量就是这种类型,在某些语言中,如果我想将8位从一种类型“转换”为另一种,将ascii字符转换为数据字节我必须进行某种转换,这当然是无效代码,因为处理器中没有ascii或数据字节之类的东西。 现在有时有些转换很重要,如果字节没有存储为字节而是存储在寄存器中,那么即使数据也可能转换一次,如果未签名,则必须进行符号扩展。

大部分这些都是很琐碎的,看看您是否使用任何语言反汇编代码。 您最终会发现,例如,在32位或64位计算机上,尝试节省空间并使用8位变量而不是32位或64位效率不高,因为编译器必须为倾向于使用带符号的人添加掩码和符号扩展整数。

请注意,处理器也不知道来自数据的指令,无论输入的是什么,它作为指令消耗。 由程序员和编译器决定不要求它消耗位作为不是指令的指令。 模式中没有魔术位来指示来自指令的数据。 有学术上的“哈佛体系结构”,但是在现实世界中确实不能很好地工作,现代处理器通常是修改后的哈佛,因为它们使用一条主总线,但是将事务标记为指令提取或数据周期(因此,如果您拥有i cache vs d您可以对它们进行排序)。

位是位,它们对计算机没有意义,对人类没有意义。

编辑

char *ptr;
float f;
void more_fun ( float );
void fun ( void )
{
    ptr=(char *)0x3000;
    *ptr++=5;
    *ptr++=4;
    *ptr++=6;
    f=1.0F;
    more_fun(f);
}

用一个处理器的一个编译器给出

00000000 <fun>:
   0:   e3a02a03    mov r2, #12288  ; 0x3000
   4:   e3a00005    mov r0, #5
   8:   e3a01004    mov r1, #4
   c:   e59f304c    ldr r3, [pc, #76]   ; 60 <fun+0x60>
  10:   e59fc04c    ldr r12, [pc, #76]  ; 64 <fun+0x64>
  14:   e92d4010    push    {r4, lr}
  18:   e583c000    str r12, [r3]
  1c:   e5c20000    strb    r0, [r2]
  20:   e5932000    ldr r2, [r3]
  24:   e2820001    add r0, r2, #1
  28:   e5830000    str r0, [r3]
  2c:   e3a0e006    mov lr, #6
  30:   e5c21000    strb    r1, [r2]
  34:   e3a025fe    mov r2, #1065353216 ; 0x3f800000
  38:   e5931000    ldr r1, [r3]
  3c:   e59fc024    ldr r12, [pc, #36]  ; 68 <fun+0x68>
  40:   e2810001    add r0, r1, #1
  44:   e5830000    str r0, [r3]
  48:   e5c1e000    strb    lr, [r1]
  4c:   e1a00002    mov r0, r2
  50:   e58c2000    str r2, [r12]
  54:   ebfffffe    bl  0 <more_fun>
  58:   e8bd4010    pop {r4, lr}
  5c:   e12fff1e    bx  lr
  60:   00000000    andeq   r0, r0, r0
  64:   00003001    andeq   r3, r0, r1
  68:   00000000    andeq   r0, r0, r0

这是未链接的。

它将ptr的地址放在一个寄存器中(这也是优化的),它准备了一些常量。 它获取ptr的地址(全局变量,因此在编译时它不知道它在哪里,因此它必须留下一个可以到达的位置,以供链接器填充,在其他指令集中存在相同的问题,但是“位置”是一个紧接该指令,并且该指令在链接时间之前是不完整的,但无论哪种方式都留有空格)。 对于每个ptr ++,我们必须保存回全局,因为它是全局的,因此r3在持续时间内将地址保存到ptr。

ldr r12, [pc, #76]  ; 64 <fun+0x64>

啊,错过优化机会再加上r12,r2,#1会便宜得多。

因此ptr + 1会保存到内存中的ptr中(处理器的所有这些位都会注意到,它不知道这是指针还是带符号的指针,这些位中的某些位是地址,但只有当寄存器用作地址时,它才是地址一个地址。

add r0, r2, #1

在这里,我们认为是地址的位只是被添加的位。

str r0, [r3]

并只存储一些位。

mov r2, #1065353216 ; 0x3f800000

浮点数1.0单精度,仅几位

就cisc与risc对待事物的区别而言,cisc使用较小的寄存器,而cisc使用32或64位。

unsigned short fun ( unsigned short x )
{
    return(x+0x1000);
}

00000000 <fun>:
   0:   e2800a01    add r0, r0, #4096   ; 0x1000
   4:   e1a00800    lsl r0, r0, #16
   8:   e1a00820    lsr r0, r0, #16
   c:   e12fff1e    bx  lr


0000000000000000 <fun>:
   0:   8d 87 00 10 00 00       lea    0x1000(%rdi),%eax
   6:   c3                      retq   

手臂至少要遵守16位边界,希望x86可以解决其他问题。

unsigned short fun ( void  )
{
    return(sizeof(unsigned short));
}

00000000 <fun>:
   0:   e3a00002    mov r0, #2
   4:   e12fff1e    bx  lr

0000000000000000 <fun>:
   0:   b8 02 00 00 00          mov    $0x2,%eax
   5:   c3                      retq   

编辑2

从opencores获取一些代码,该处理器提供了8位或16位加法,这并不是一个意外的解决方案。

wire [16:0] alu_add        = op_src_in_jmp + op_dst_in;

wire    V           = inst_bw ? ((~op_src_in[7]  & ~op_dst_in[7]  &  alu_out[7])  |
                                 ( op_src_in[7]  &  op_dst_in[7]  & ~alu_out[7])) :
                                ((~op_src_in[15] & ~op_dst_in[15] &  alu_out[15]) |
                                 ( op_src_in[15] &  op_dst_in[15] & ~alu_out[15]));

wire    N           = inst_bw ?  alu_out[7]       : alu_out[15];
wire    Z           = inst_bw ? (alu_out[7:0]==0) : (alu_out==0);
wire    C           = inst_bw ?  alu_out[8]       : alu_out_nxt[16];

使用单个16位加法器,但基于8或16位运算来复用标志。 现在,verilog编译器实际产生什么? 它很有可能以菊花链方式将两个8位加法器链接在一起,以便可以提取标志结果。 或者库中有一个16位加法器,其中的这些标志已经被窃听。

编辑

至于数据的大小,宽度。 可以在指令中间接或直接编码。 如上所示,这成为一个或多个控制信号,说明如何处理数据。 使用ARM进行字节宽的读取,由于没有理由不进行读取,因此可以读取32位或64位。 然后,当数据到达内核时,选择字节通道,如果指令被设计为对符号扩展进行设计,则它将以零填充或符号扩展将读取的数据保存到指令中定义的寄存器中。 与其他总线宽度大于一个字节的体系结构类似,尽管可以使目标隔离该字节而不是处理器内核,但这仅取决于总线设计,我敢肯定那里至少每个都有一个示例。 由于内存往往是总线宽度或倍数,因此读取整行的内存并移动它或至少改变总线宽度不会花更多的钱。 寡妇的分数具有(通常是最小的)成本,不需要消耗两端的成本,所以选择一个。 写操作很麻烦,任何小于ram宽度的操作都会导致控制器进行read-modify-write操作。 该指令直接或间接指示以某种方式在总线上编码的宽度,在内存附近的内存控制器必须处理分数。 这是您从缓存中获得的好处之一,即使大多数dram调光是由8位宽或16位宽的部分制成的,它们都可以以64位宽进行访问,但缓存行的宽度是一个或多个dram,这样您就不必对如此慢的内存进行读-修改-写操作,而对缓存中的sram进行读-修改-写操作则相对要快得多。

性能来自对齐,因为如果32位对齐而不是64位总线上的64位对齐,则可以减少逻辑并提高臂上四个寄存器的stm效率,因此第一个字必须进行三个传输,一个用于下两个,另一个用于第三个,第一个和最后一个要求总线的字节掩码,因为它们不填充总线,但是如果相同的四个寄存器stm在64位对齐的地址处,则是一次传输。 每次传输都需要几到多个时钟的开销。 同样地,在内存端,如果您对齐并且是存储器宽度的全宽度或倍数,那么它只是写,如果事务的任何部分是小数,则它必须进行读取-修改-写入。 如果在上述stm组合中您的缓存ram碰巧是64位宽,则不仅会降低总线开销,而且还会降低ram的内存,在ram中您有3次写入,其中2次为读取-修改写入,而不是只有2次的干净写入作为两个时钟周期。 臂eabi更改以要求将堆栈对齐在64位边界上的原因之一可能是原因之一,还有SO上的无数问题,为什么不使用此额外寄存器就将其压入。

如上所示,为了屏蔽/填充高位,编译器在知道第二个零填充的情况下选择了两次移位,如果这是一个带符号的int,则编译器很可能已经选择了一个带符号的移位来对结果进行扩展,以扩展到32位。

00000000 <fun>:
   0:   e2800a01    add r0, r0, #4096   ; 0x1000
   4:   e1a00800    lsl r0, r0, #16
   8:   e1a00820    lsr r0, r0, #16
   c:   e12fff1e    bx  lr

理想情况下,由于架构的原因,希望在途中将编译器/人员所需的大小转换为寄存器的本机大小。 对于x86,您可以在各个点上拉标志,因此不需要对扩展符号或零填充进行签名,以后的操作可以进行数学运算而忽略高位,并根据需要从中间拉出标志。

现在,mips可以根据立即数的工作原理在一条指令中将高位清零。 英特尔使用立即数来刻录大量指令空间,并且可以执行任何大小的操作。 (由其他很小的指令补偿)。 如果它是8位数据类型

unsigned char fun ( unsigned char x )
{
    return(x+0x10);
}

00000000 <fun>:
   0:   e2800010    add r0, r0, #16
   4:   e20000ff    and r0, r0, #255    ; 0xff
   8:   e12fff1e    bx  lr

编译器比将两个移位都移至零填充更了解。

但正如人们所期望的

signed char fun ( signed char x )
{
    return(x+0x10);
}

00000000 <fun>:
   0:   e2800010    add r0, r0, #16
   4:   e1a00c00    lsl r0, r0, #24
   8:   e1a00c40    asr r0, r0, #24
   c:   e12fff1e    bx  lr

我可以带一个非常大的容器来存放各种lego积木,用这些积木我可以盖房子,也可以盖桥梁,等等。这些积木只有人类才能从桥上得知房子。 处理器不知道无符号整数的整数布尔值,有些处理器知道一些不同的宽度,因为它们可以掩蔽,填充或符号扩展,但是人类和编译器都知道这一切,并以正确的顺序组装了乐高积木的正确混合物实现他们的愿景。

没有。

在x86上,操作数大小被编码为指令的一部分。 每当使用两个大小不同的操作数时,较小的一个就必须先扩展到较大的一个。 根据特定的操作,可以使用零扩展(填充零的高位)或符号扩展(将较小值的高位复制到新值的高位)来完成。 例如,程序可能会先使用movsx指令对值进行符号扩展,然后再将其用于其他操作。

在RISC架构上,通常一次一次将操作应用于整个字(例如32或64位),这取决于软件是否知道如何解释结果位。

浮点值由不同于整数值的电路处理,因此存储在单独的一组寄存器中。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM