4

考虑这个添加常量的简单函数:

unsigned char f(unsigned char x) {
    return x + 5;
}

这会生成以下程序集(-O3在 gcc 4.7.2 上使用):

leal    5(%rdi), %eax
ret

现在由于无符号溢出是 C 中定义明确的行为,人们会假设添加模运算本质上应该是一个 nop:

unsigned char f(unsigned char x) {
    return (x + 5) % 256; // assume char is 8-bits, which is typical
}

但是生成的程序集有一个额外的指令:

leal    5(%rdi), %eax
movzbl  %al, %eax
ret

有人能告诉我为什么会这样吗?不过我对组装不是很熟悉。

(注意:这只是我为了理解 GCC 如何优化代码而制作的一个玩具问题。)

4

1 回答 1

4

对于“为什么生成的代码不同”的明确答案,您可能需要一位对该gcc编译器细节有深入了解的工程师。您可能想尝试更多示例,如下所示:

unsigned char f1(unsigned char x) { return x + 5; }
unsigned char f2(unsigned char x) { return (x + 5) % 256; }
unsigned char f3(unsigned char x) { return (x + 5) % 256U; }
unsigned char f4(unsigned char x) { return (x + 5) & 0xFFU; }

使用gcc适用于 64 位系统的 4.1.2 版,我为所有这些功能获得了相同的代码,无论是 64 位代码还是 32 位代码。其中实际上都包括movzbl. 这可能是gcc编译中的错误f1(并且可能在调用方更正)。这实际上取决于调用约定:64 位寄存器中的 8 位值是否应该是零/符号扩展。我在2005 年 6 月 14 日的System V Application Binary Interface,AMD64 Architecture Processor Supplement的草稿版本 0.96 中找不到对此的决定性答案。gcc编译器 4.1.2 似乎采用了“比抱歉更安全”的理念作为movzbl也发生在调用方。以我的经验,通常要求这些值是零/符号扩展,除非有人对寄存器的某些部分进行操作,这是非常不寻常的。

有趣的是,我的家庭编译器gcc版本 4.3.2 确实f2通过and操作实现了一个小的差异。所有其他人只是添加 5,强烈建议调用者有责任执行零/符号扩展,它确实这样做了。但这是 32 位代码。

如果我在任何架构规范中找到关于超大寄存器中值的零/符号扩展的结论性答案,那么我会告诉你的。我碰巧也需要专业地了解这一点。

gcc为您的编译器辩护。您正在研究小型啤酒优化。普通代码不包含这样的模数,如果编译器在某处将这样一个特殊的模数减少到 and ,那就太好。在%256(vs %256U) 的情况下,需要进行一些值范围分析来确定 an就足够了,因为模是在“有符号”算术中完成的。显然,我的编译器确实在某些时候得出结论,并且就足够了,但显然为时已晚,无法确定它随后被结果的类型所包含,它在其他情况下确实确定了这一点。这就是我们编译器工程师所说的“相位排序问题”。

更新寄存器中值的零/符号扩展。

我现在已经放弃了这个任务,将不得不继续与一些同事一起工作,因为如果参数/函数结果预计为零/符​​号扩展,我还没有找到一个结论性的声明。

我确实在上述 ABI 规范中找到了与此相关的以下内容。

布尔值,当存储在内存对象中时,存储为单字节对象,其值始终为 0(假)或 1(真)。当存储在整数寄存器中或作为参数传递到堆栈时,寄存器的所有 8 个字节都是有效的;任何非零值都被认为是真的。

所以布尔类型必须是零扩展的。

对于可能调用使用 varargs 或 stdargs 的函数的调用(无原型调用或对声明中包含省略号 (...) 的函数的调用)%al(注 14)用作隐藏参数以指定使用的 SSE 寄存器的数量。的内容%al不需要与寄存器的数量完全匹配,但必须是使用的 SSE 寄存器数量的上限,并且在 0-8 范围内(含)。

注14:注意其余部分%rax未定义,仅%al定义了内容。

所以对于这种特殊用途,%al它不需要扩展。

考虑到布尔值必须是零扩展的,人们可能会得出结论,ABI 的精神是其他子词类型也应该被扩展。采取更正式的立场可以争辩说,没有任何陈述应该被解释为不需要零/符号扩展。总而言之,不太满意。

寄存器中值的零/符号扩展更新 2。

我已经和同事讨论过这个问题。2012 版 0.99 的最新版本的 ABI 已在布尔值的参数传递上进行了完全更改,因为这些布尔值仅从零扩展到 8 位。这表明这已被修改为与传递其他子字类型一致,因为它们都不是零/符号扩展。AMD64架构还支持一半64位寄存器的子字寄存器,可以对这些子字寄存器进行操作。这可能是不以零/符号扩展方式传递参数的动机。

于 2013-07-01T17:34:13.023 回答