[英]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.