来自数组 0 初始化的奇怪程序集

2022-01-17 00:00:00 c assembly compiler-construction c++

受问题的启发 初始化和归零数组的区别在 c/c++ ? 中,我决定实际检查 Windows Mobile Professional(ARM 处理器,来自 Microsoft Optimizing Compiler)的优化版本构建的程序集.我的发现有点令人惊讶,我想知道是否有人可以解释我的问题.

Inspired by the question Difference in initalizing and zeroing an array in c/c++ ?, I decided to actually examine the assembly of, in my case, an optimized release build for Windows Mobile Professional (ARM processor, from the Microsoft Optimizing Compiler). What I found was somewhat surprising, and I wonder if someone can shed some light on my questions concerning it.

检查这两个示例:

byte a[10] = { 0 };

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

它们用在同一个函数中,所以栈是这样的:

They are used in the same function, so the stack looks like this:

[ ] // 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)

带有我的评论的生成程序集:

The generated assembly with my comments:

; 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

现在,有两件事让我感到困惑:

Now, there are two things that confuses me:

  1. 第 2 行和第 5 行有什么意义?为什么不直接给 memset &a[0] 和 10 个字节?
  2. 为什么没有初始化 0 的填充字节?这仅适用于结构中的填充吗?

我很好奇不测试结构案例:

I was too curious to not test the struct case:

struct Padded
{
    DWORD x;
    byte y;
};

0 初始化它的汇编器:

The assembler for 0-initializing it:

; 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 相对).对吧?

Here we see in line 04 that a padding indeed occur, since str (as opposed to strb) is used. Right?

推荐答案

第 2 行和第 5 行的原因是因为您在数组初始值设定项中指定了 0.编译器将初始化所有常量,然后使用 memset 填充其余的常量.如果你在初始化器中放两个零,你会看到它是 strw(字而不是字节)然后 memset 8 个字节.

The reason for lines 2 and 5 is because you specified a 0 in the array initializer. The compiler will initialize all constants then pad out the rest using memset. If you were to put two zeros in your initializer, you'd see it strw (word instead of byte) then memset 8 bytes.

至于padding,它只是用来对齐内存访问――一般情况下不应该使用数据,所以memsetting很浪费.

As for the padding, it's only used to align memory accesses -- the data shouldn't be used under normal circumstances, so memsetting it is wasteful.

为了记录,我对上面的 strw 假设可能是错误的.我 99% 的 ARM 经验是在 iPhone 上反转 GCC/LLVM 生成的代码,所以我的假设可能不会延续到 MSVC.

For the record, I may be wrong about the strw assumption above. 99% of my ARM experience is reversing code generated by GCC/LLVM on the iPhone, so my assumption may not carry over to MSVC.

相关文章