在过去的几天里,我一直在使用这段“无害”的代码(最小可复制的例子,一个更大的模乘例程的一部分):
#include <iostream>
#include <limits>
using ubigint = unsigned long long int;
using bigint = long long int;
void modmul(bigint a, bigint b, ubigint p) {
ubigint ua = a < 0 ? -a : a;
ubigint ub = b < 0 ? -b : b;
ua %= p;
ub %= p;
std::cout << "ua: " << ua << '\n';
}
int main() {
bigint minbigint = std::numeric_limits<bigint>::min();
bigint maxbigint = std::numeric_limits<bigint>::max();
std::cout << "minbigint: " << minbigint << '\n';
std::cout << "maxbigint: " << maxbigint << '\n';
modmul(minbigint, maxbigint, 2314); // expect ua: 2036, got ua: 0
}我正在编译macOS 11.4中的clang12.0,安装自Homebrew
clang version 12.0.0
Target: arm64-apple-darwin20.5.0
Thread model:posix
InstalledDir: /opt/homebrew/opt/llvm/bin在用clang -O1编译时,程序会输出预期的结果(在本例中,2036年,我已经使用Wolfram Mathematica、Mod[9223372036854775808, 2314]检查过了,这是正确的)。但是,当我使用clang -O2或clang -O3 (完全优化)编译时,变量ua不知何故被归零(其值变为0)。我在这里完全不知所措,不知道为什么会这样。国际海事组织,这段代码中没有UB,也没有溢出,或者任何可疑的东西。我非常感谢你的任何建议,或者如果你能在你这一边重复这个问题。
PS:代码在任何其他平台(包括Windows/Linux/FreeBSD/Solaris)和任何编译器组合上的行为都是预期的。我只在Apple 12上得到这个错误(没有在M1上用其他编译器进行测试)。
发布于 2021-07-14 03:28:57
UPDATE:正如@哈罗德在评论部分指出的那样,0中的negq和subq完全相同。因此,我下面关于negq和subq的讨论是不正确的。请不要理会这部分,很抱歉在发帖之前没有再检查一遍。
关于最初的问题,我重新编译了一个稍微简单一些的代码哥德波特版本,并发现有问题的编译器的优化是在main而不是modmul中。在main中,clang看到其用于modmul的所有操作数都是常数,因此它决定在编译时计算modmul。在计算ubigint ua = a < 0 ? -a : a;时,clang会发现它是带符号整数溢出UB,因此它决定返回0并打印出来。这似乎是一个激进的做法,但这是合法的,因为UB。此外,由于由于两种恭维系统的限制,没有数学上正确的答案,所以返回0可以说是与任何其他结果一样好(或同样糟糕)。
老字号回答
正如有人在注释部分中指出的那样,代码下面的2行是未定义的行为--有符号整数溢出UB。
ubigint ua = a < 0 ? -a : a;
ubigint ub = b < 0 ? -b : b;如果您想知道clang在引擎盖下到底做了什么才能在两个不同的优化级别上产生两个不同的结果,那么请考虑一个简单的例子如下。
using ubigint = unsigned long long int;
using bigint = long long int;
ubigint
negate(bigint a)
{
ubigint ua = -a;
return ua;
}用-O0编译时
negate(long long): # @negate(long long)
pushq %rbp
movq %rsp, %rbp
movq %rdi, -8(%rbp)
xorl %eax, %eax
subq -8(%rbp), %rax # Negation is performed here
movq %rax, -16(%rbp)
movq -16(%rbp), %rax
popq %rbp
retq用-O3编译
negate(long long): # @negate(long long)
movq %rdi, %rax
negq %rax # Negation is performed here
retq在-O0,clang使用普通的subq指令执行0和%rax的二进制减法,并产生整数环绕行为的结果。
在-O3,clang可以做得更好,它使用negq指令,它只替换操作数和它的两个补码(即翻转所有位并添加1)。但是,您可以看到,只有在有符号整数溢出是未定义的行为(因此编译器可以忽略溢出情况)时,此优化才是合法的。如果标准要求的整数环绕行为,clang必须回到未优化的版本。
https://stackoverflow.com/questions/68371261
复制相似问题