2016-11-11 26 views
2

我很好奇,爲什麼我的代碼在x86和armeabi平臺上有不同的行爲。代碼的概念(這是不是真正的代碼,但它是足夠了解的問題):轉換不同數據類型時的內存對齊

struct data 
{ 
    int x; 
} 

void method(unsigned char* buff) 
{ 
    data D; 

    memcpy(&D.x, buff, sizeof(int)); //good approach 
    D.x = *(int*)buff; //bad approach 
} 

所以,當這個代碼是由GCC ARM架構下的編譯 - 它在行導致SIGFAULT(未對齊的內存)通過轉換數據類型,儘管msvc編譯的代碼運行正常。據我所知,在這種情況下唯一正確的解決方案是使用memcpy。有人可以解釋運行時真正發生了什麼嗎?

回答

3

這是一個基本的硬件限制。

某些CPU只能執行硬件指令,這些硬件指令僅在適當對齊的地址上訪問2,4或8個字節值。也就是說,它們不能從奇數存儲器地址讀取4個字節的值(例如)。該操作會生成一個硬件異常,並轉換爲信號。所有對4個字節值的訪問都必須來自均勻分配4的物理地址(例如)。

確切的對齊限制因CPU而異。這就是CPU的設計。這是一個理論的例子。假設在給定的硬件平臺上,所有的RAM都是以32位字的形式訪問的。從編程的角度來看,它仍然是8位字節,但每個RAM字都包含4個字節。影響單個字節的CPU指令由CPU通過讀取包含該字節的整個存儲器字,執行操作並將其存回來來執行。但是,例如,影響4字節整數的操作預計會引用該存儲器字中第一個字節的邏輯地址。 CPU讀取整個4字節字,執行操作,然後將其存回。

因此,最終的結果是CPU無法尋址4字節的值,這些值不能在偶數4字節的邊界上開始。理論上講,這可以通過獲取兩個相鄰的存儲字來實現,執行影響與兩個字重疊的4字節值的操作,然後將它們兩者存回RAM中。當然,這會增加很多複雜性,有些CPU根本沒有設計成這樣做。

在您的示例中,指針解引用轉換爲直接的CPU指令,如果實際內存地址爲奇數(例如),則該指令將失敗。 memcpy()執行逐字節複製,並且可以工作。

+0

你很酷。很多。 –

1

C++ 11有你想使用(因爲作爲Sam Varshavchik answered,硬件& instruction setABI有對齊限制)的alignof操作。它還有alignas說明符。

順便說一句,像你這樣使用memcpy是可能的,但可能效率低下(因爲未對齊的數據移動比對齊的數據移動慢)。即使在硬件(特別是x86)中,可能存在錯位訪問(可能是D.x = *(int*)buff;),但效率不高(可能慢10倍)。

實際上,您需要確保每次致電method時都提供了適當對齊的緩衝區。你可以使用alignas & alignof,使用一些union等等。

查看更多about CPU cache。還請看回答部分的http://norvig.com/21-days.html(這是一個非常有趣的閱讀)。