机器如何区分数据类型之间的解释协议,例如:1字节,2字节,4字节整数,浮点数,双精度,字符和Unicode格式?
我假设存在某种标题符号来区分对构成最终可执行文件的一部分的数据类型有效的操作集的配置,但是如果有人愿意解释,我想要一个精确的答案。
答案 0 :(得分:2)
处理器/逻辑通常不会从另一个位置知道,主要是在用户或程序员眼中。计算地址时例如
* PTR ++;
指针只是位于某个地址的位,只是位,所以你把一些位放在一个寄存器中然后你在这一个时钟周期读取这些位,因为这些位移向主总线这些位是一个地址,有些位返回它们只是位,它们进入寄存器。这些位被发送到alu,并且指针可以是一个结构,因此++可以是除了一个以外的某个数字,在任何一种情况下,它也会被发送,或者在某些体系结构中你必须将它加载到寄存器中第一。 alu对这些位进行了一些数学计算,对于那几个时钟周期,这些位是操作数。加法和减法不知道unsigned vs signed,所以它只是位。那些失败的人会说到一个寄存器,现在我们将这些位写入我们之前使用的地址,该地址只是一个/几个时钟周期的地址,它从寄存器中采样并在路上发送。
float只是发送到fpu的位。
字节,半字,字,双字等传输由指令或其他逻辑驱动(通常不通过指令获取)在具有现代32或64左右宽度总线的读取上读取的是整个宽度当它到达处理器核心时,感兴趣的字节通道被剥离,当然取决于架构和总线,对于接受字节半字字的目标的写入将具有一些方案或者指示哪些通道有效的字节掩码(对于哪些通道)它们不关心)或者以字节为单位存在大小,并且双方同意以某种方式对齐总线上的那些字节(位通道0到n-1最有意义,但无论它们设计什么)。
现在通常不是计算机友好的计算机语言有很多关于位的意见,这个变量就是这种类型,变量就是那种类型,如果我想转换"从一种类型到另一种类型的8位ascii字符到数据字节我必须进行某种转换,这当然是死代码,因为在处理器中没有ascii和数据字节这样的东西。现在有时候转换很重要,如果字节没有作为字节存储但是在寄存器中,那么甚至可能是数据,如果无符号,则必须发生符号扩展。
大多数情况下,无论您使用何种语言反汇编代码,都是微不足道的。您最终会发现,在32位或64位计算机上,尝试节省空间并使用8位变量而不是32位或64位是低效的,因为编译器必须为倾向于使用签名的人员添加屏蔽和符号扩展名整数。
请注意,处理器不知道来自数据的指令,无论输入什么是它作为指令消耗的内容。程序员和编译器不要求它将位作为非指令的指令。模式中没有神奇的位来指示来自指令的数据。有学术和哈佛的架构"但是那些在现实世界中效果不佳的现代处理器通常会被修改为哈佛,因为它们使用一条主总线但是将事务标记为指令提取或数据循环(因此,如果你有一个i缓存与d缓存,你可以对它们进行排序)。 / p>
位是位,它们对计算机没有任何意义,只对人类有意义。
修改
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的地址(全局变量,因此在编译时它不知道它在哪里,所以它必须留下一个它可以到达链接器填充的位置,在其他指令集中同样的问题但是&#34;位置& #34;是指令的直接指令,并且指令在链接时间之前是不完整的,但空间是左边的。)对于每个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 vs 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并移动它或至少是总线宽度不会花费更多。 widt的分数具有(通常是最小的)成本,不需要在两端燃烧该成本,因此选择一个。写入是痛苦的,任何小于ram的宽度都会导致控制器进行读取 - 修改 - 写入。该指令直接或间接地指示以某种方式在总线上编码的宽度,靠近该ram的存储器控制器必须处理该分数。这是从缓存中获得的好处之一,即使大多数dram dimms由8位宽或16位宽的部分组成,它们以64位宽度访问,缓存行是这样的,它是一个或多个宽度的因为你不必对这么慢的内存进行读取 - 修改 - 写入,读取 - 修改 - 写入发生在缓存中的sram相对更快。
性能来自对齐,因为您可以减少逻辑并提高效率如果32位对齐而不是64位对64位宽总线,则需要在臂上执行四个寄存器,您必须有三个传输第一个字,一个用于接下来的两个,一个用于第三个,第一个和最后一个要求总线的字节掩码,因为它们不填充它但如果相同的四个寄存器stm在64位对齐的地址,则它是单个传输。每次传输需要几到几个时钟的开销。同样在ram端,如果你对齐并且内存宽度的整个宽度或倍数然后它只是写入,如果事务的任何部分是一个分数,那么它必须读取 - 修改 - 写入。如果你的缓存ram在上面的stm组合中恰好是64位宽,那么对于总线开销来说不仅会慢一些,而且在你有三次写入的ram中,其中两次写入 - 修改写入而不是两次干净写入。作为两个时钟周期。可能其中一个原因就是为什么arm 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可以根据他们的immediates如何工作,在一条指令中将高位缩小到零。英特尔通过immediates烧掉了大量的指令空间,可以做任何规模。 (由非常小的其他指令补偿)。它是一个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
我可以拿一个非常大的各种各样的乐高积木容器,我可以用这些积木建造一座房子,我可以建造一座桥梁等等。这些街区不知道只有人类才能从桥上找到一座房子。处理器不知道来自unsigned int的int的bool,有些知道一些不同的宽度,因为它们可以掩码,填充或符号扩展,但是人和编译器都知道所有并以正确的顺序组装正确的lego块混合实现他们的愿景。
答案 1 :(得分:1)
它没有。
在x86上,操作数大小被编码为指令的一部分。任何时候使用两个不同大小的操作数,较小的操作数需要首先扩展到较大的操作数。根据具体操作,可以使用零扩展(填充为零的高位)或符号扩展(将较小值的高位复制到新值的高位)来完成。例如,程序可能会使用movsx
指令对值进行签名扩展,然后再将其用于其他操作。
在RISC架构上,通常操作一次应用于整个字(例如,32或64位),并且由软件决定如何解释结果位。
浮点值由不同于整数值的电路处理,因此存储在一组单独的寄存器中。