当前位置:  开发笔记 > 编程语言 > 正文

从数组0初始化奇怪的汇编

如何解决《从数组0初始化奇怪的汇编》经验,为你挑选了3个好方法。

灵感来自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

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

    第02和05行有什么意义?为什么不给memset一个[0]和10个字节?

    为什么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).对?



1> Cody Brociou..:

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

至于填充,它仅用于对齐内存访问 - 在正常情况下不应使用数据,因此将其设置为浪费.

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



2> MSalters..:

这两段代码都是无错误的.提到的两行并不聪明,但你只是证明这个编译器发出了次优代码.

填充字节通常只在初始化时才会简化程序集或加速代码.例如,如果在两个零填充成员之间有填充,则通常也更容易对填充进行零填充.此外,如果最后有填充,并且memset()针对多字节写入进行了优化,则覆盖该填充也可能更快.


不太可能.你有未对齐的内存访问(一个字节和9个字节 - ARM通常有一个16位总线.这意味着读/修改/写!).此外,你还有额外的注册压力:你也需要R3.
实际上,这段代码非常好.在ARM上流水线指令的方式可以很容易地使strb然后分支和循环更有效.也就是说,性能差异可能可以忽略不计,并且你使用额外的4个字节,所以谁知道.

3> bk1e..:

一些快速测试表明,如果初始化程序列表为空,则Microsoft的x86编译器生成不同的程序集,而不是它包含零.也许他们的ARM编译器也是如此.如果你这样做会怎么样?

byte a[10] = { };

这是我得到的汇编列表(/EHsc /FAs /O2在Visual Studio 2008上有选项).请注意,在初始化程序列表中包含零会导致编译器使用未对齐的内存访问来初始化数组,而空的初始化程序列表版本和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

推荐阅读
360691894_8a5c48
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有