簡體   English   中英

從數組0初始化奇怪的匯編

[英]Strange assembly from array 0-initialization

靈感來自c / c ++中初始化和歸零數組的問題 在我的例子中,我決定實際檢查一下針對Windows Mobile Professional(ARM處理器,來自Microsoft Optimizing Compiler)的優化發布版本。 我發現的有點令人驚訝,我想知道是否有人可以解釋我的問題。

檢查這兩個例子:

byte a[10] = { 0 };

byte b[10];
memset(b, 0, sizeof(b));

它們在同一個函數中使用,因此堆棧如下所示:

[ ] // padding byte to reach DWORD boundary
[ ] // padding byte to reach DWORD boundary
[ ] // b[9] (last element of b)
[ ]
[ ]
[ ]
[ ]
[ ]
[ ]
[ ]
[ ]
[ ] // b[0] = sp + 12 (stack pointer + 12 bytes)
[ ] // padding byte to reach DWORD boundary
[ ] // padding byte to reach DWORD boundary
[ ] // a[9] (last element of a)
[ ]
[ ]
[ ]
[ ]
[ ]
[ ]
[ ]
[ ]
[ ] // a[0] = sp (stack pointer, at bottom)

生成的程序集帶有我的注釋:

; byte a[10] = { 0 };

01: mov   r3, #0        // r3 = 0
02: mov   r2, #9        // 3rd arg to memset: 9 bytes, note that sizeof(a) = 10
03: mov   r1, #0        // 2nd arg to memset: 0-initializer
04: add   r0, sp, #1    // 1st arg to memset: &a[1] = a + 1, since only 9 bytes will be set
05: strb  r3, [sp]      // a[0] = r3 = 0, sets the first element of a
06: bl    memset        // continue in memset

; byte b[10];
; memset(b, 0, sizeof(b));

07: mov   r2, #0xA      // 3rd arg to memset: 10 bytes, sizeof(b)
08: mov   r1, #0        // 2nd arg to memset: 0-initializer
09: add   r0, sp, #0xC  // 1st arg to memset: sp + 12 bytes (the 10 elements
                        // of a + 2 padding bytes for alignment) = &b[0]
10: bl    memset        // continue in memset

現在,有兩件事讓我困惑:

  1. 第02和05行有什么意義? 為什么不給memset一個[0]和10個字節?
  2. 為什么0初始化的填充字節不是? 那只是結構中的填充嗎?

編輯:我太好奇,不測試結構案例:

struct Padded
{
    DWORD x;
    byte y;
};

用於初始化0的匯編程序:

; Padded p1 = { 0 };

01: mov   r3, #0
02: str   r3, [sp]
03: mov   r3, #0
04: str   r3, [sp, #4]

; Padded p2;
; memset(&p2, 0, sizeof(p2));

05: mov   r3, #0
06: str   r3, [sp]
07: andcs r4, r0, #0xFF
08: str   r3, [sp, #4]

這里我們在第04行中看到填充確實發生,因為使用了str (而不是strb )。 對?

第2行和第5行的原因是因為您在數組初始值設定項中指定了0。 編譯器將初始化所有常量,然后使用memset填充其余常量。 如果要在初始化程序中放置兩個零,則會看到它是strw(字而不是字節)然后是memset 8字節。

至於填充,它僅用於對齊內存訪問 - 在正常情況下不應使用數據,因此將其設置為浪費。

編輯:為了記錄,我可能錯誤地認為上面的strw假設。 99%的ARM經驗都是逆轉iPhone上GCC / LLVM生成的代碼,所以我的假設可能不會延續到MSVC。

這兩段代碼都是無錯誤的。 提到的兩行並不聰明,但你只是證明這個編譯器發出了次優代碼。

填充字節通常只在初始化時才會簡化程序集或加速代碼。 例如,如果在兩個零填充成員之間有填充,則通常也更容易對填充進行零填充。 此外,如果最后有填充,並且memset()針對多字節寫入進行了優化,則覆蓋該填充也可能更快。

一些快速測試表明,如果初始化程序列表為空,則Microsoft的x86編譯器生成不同的程序集,而不是它包含零。 也許他們的ARM編譯器也是如此。 如果你這樣做會怎么樣?

byte a[10] = { };

這是我得到的匯編列表(在Visual Studio 2008上使用選項/EHsc /FAs /O2 )。 請注意,在初始化列表中包含零會導致編譯器使用未對齊的內存訪問來初始化數組,而空的初始化列表版本和memset()版本都使用對齊的內存訪問:

; unsigned char a[10] = { };

xor eax, eax
mov DWORD PTR _a$[esp+40], eax
mov DWORD PTR _a$[esp+44], eax
mov WORD PTR _a$[esp+48], ax

; unsigned char b[10] = { 0 };

mov BYTE PTR _b$[esp+40], al
mov DWORD PTR _b$[esp+41], eax
mov DWORD PTR _b$[esp+45], eax
mov BYTE PTR _b$[esp+49], al

; unsigned char c[10];
; memset(c, 0, sizeof(c));

mov DWORD PTR _c$[esp+40], eax
mov DWORD PTR _c$[esp+44], eax
mov WORD PTR _c$[esp+48], ax

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM