这个问题有两个部分:钱德勒关于基于未定义的溢出的优化的观点,以及您在汇编输出中发现的差异。
钱德勒的观点是,如果溢出是未定义的行为,那么编译器可以假设它不会发生。考虑以下代码:
typedef int T;
void CopyInts(int *dest, const int *src) {
T x = 0;
for (; src[x]; ++x) {
dest[x] = src[x];
}
}
在这里,编译器可以安全地更改for
循环到以下内容:
while (*src) {
*dest++ = *src++;
}
这是因为编译器不必担心以下情况x
溢出。如果编译器必须担心x
溢出,源指针和目标指针突然减去 16 GB,因此上面的简单转换将不起作用。
在汇编级别,上面是(对于 x86-64 的 GCC 7.3.0,-O2
):
_Z8CopyIntsPiPKii:
movl (%rsi), %edx
testl %edx, %edx
je .L1
xorl %eax, %eax
.L3:
movl %edx, (%rdi,%rax)
addq $4, %rax
movl (%rsi,%rax), %edx
testl %edx, %edx
jne .L3
.L1:
rep ret
如果我们改变T
to be unsigned int
,我们得到这个较慢的代码:
_Z8CopyIntsPiPKij:
movl (%rsi), %eax
testl %eax, %eax
je .L1
xorl %edx, %edx
xorl %ecx, %ecx
.L3:
movl %eax, (%rdi,%rcx)
leal 1(%rdx), %eax
movq %rax, %rdx
leaq 0(,%rax,4), %rcx
movl (%rsi,%rax,4), %eax
testl %eax, %eax
jne .L3
.L1:
rep ret
在这里,编译器保留x
作为一个单独的变量,以便正确处理溢出。
您可以使用与指针大小相同的大小类型,而不是依赖未定义的有符号溢出来提高性能。这意味着这样的变量只能与指针同时溢出,而指针也是未定义的。因此,至少对于 x86-64,size_t
也将作为T
以获得更好的性能。
现在回答你问题的第二部分:add
操作说明。上的后缀add
指令来自所谓的“AT&T”风格的 x86 汇编语言。在 AT&T 汇编语言中,参数与 Intel 编写指令的方式相反,并且通过在助记符中添加后缀来消除指令大小的歧义,而不是像这样dword ptr
在英特尔案中。
Example:
Intel: add dword ptr [eax], 1
美国电话电报公司:addl $1, (%eax)
这些是相同的指令,只是写法不同。这l
取代dword ptr
.
如果 AT&T 指令中缺少后缀,这是因为它不是必需的:大小是从操作数中隐含的。
add $1, %eax
The l
后缀是不必要的,因为该指令显然是32位的,因为eax
is.
简而言之,它与溢出无关。溢出始终在处理器级别定义。在某些架构上,例如使用非u
MIPS上的指令,溢出抛出异常,但仍然defined。 C/C++ 是唯一使溢出行为变得不可预测的主要语言。