让我们考虑一下这个非常简单的代码
int main(void)
{
char buff[500];
int i;
for (i=0; i<500; i++)
{
(buff[i])++;
}
}
因此,它只需要经过500个字节并递增它。此代码在x86-64架构上使用gcc编译,并使用objdump -D实用程序进行反汇编。看看反汇编的代码,我发现数据是逐字节地从内存传输到寄存器的(参见,movzbl指令用于从内存中获取数据,mov%dl用于在内存中存储数据)
00000000004004ed <main>:
4004ed: 55 push %rbp
4004ee: 48 89 e5 mov %rsp,%rbp
4004f1: 48 81 ec 88 01 00 00 sub $0x188,%rsp
4004f8: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp)
4004ff: eb 20 jmp 400521 <main+0x34>
400501: 8b 45 fc mov -0x4(%rbp),%eax
400504: 48 98 cltq
400506: 0f b6 84 05 00 fe ff movzbl -0x200(%rbp,%rax,1),%eax
40050d: ff
40050e: 8d 50 01 lea 0x1(%rax),%edx
400511: 8b 45 fc mov -0x4(%rbp),%eax
400514: 48 98 cltq
400516: 88 94 05 00 fe ff ff mov %dl,-0x200(%rbp,%rax,1)
40051d: 83 45 fc 01 addl $0x1,-0x4(%rbp)
400521: 81 7d fc f3 01 00 00 cmpl $0x1f3,-0x4(%rbp)
400528: 7e d7 jle 400501 <main+0x14>
40052a: c9 leaveq
40052b: c3 retq
40052c: 0f 1f 40 00 nopl 0x0(%rax)
看起来它有一些性能影响,因为在这种情况下,您必须访问内存500次才能读取,500次才能存储。我知道缓存系统会以某种方式处理它,但无论如何。 我的问题是为什么我们不能加载四字,只做一些位操作来增加该四字的每个字节,然后将其推回内存?显然,它需要一些额外的逻辑来处理小于四字的数据的最后部分和一些额外的寄存器。但是这种方法将大大减少内存访问的数量,这是最昂贵的操作。可能我没有看到一些阻碍这种优化的障碍。所以,在这里得到一些解释会很棒。
答案 0 :(得分:1)
为什么不应该这样做的原因:想象一下,如果char
恰好是无符号的(使溢出有定义的行为)并且你有一个字节0xFF
后面(或前面,取决于字节顺序) 0x1
。
一次增加一个字节,最终0xFF
变为0x00
,0x01
变为0x02
。但是,如果您一次只加载4或8个字节并添加0x01010101(或等效的8个字节)来实现相同的结果,0xFF
将溢出到0x01
,因此您最终会0x00
和0x03
,而不是0x00
和0x02
。
签名char
通常也会出现类似问题;有符号的溢出和截断规则(或缺少它)使它更复杂,但要点是一次增加一个字节限制对该字节的影响,没有交叉字节“干扰”。
答案 1 :(得分:1)
当你在没有优化的情况下编译时,编译器会对代码进行更直接的代码转换,部分原因是当你在调试器中单步执行代码时,这些步骤对应于你的代码。
如果启用优化,则装配可能看起来完全不同。
此外,您的程序会通过读取未初始化的char
来导致未定义的行为。