为什么stdlib.h的abs()函数家族返回有符号值?

10

man页中指出了这种情况的负面影响:

注意事项 尝试获取最小负整数的绝对值是未定义的。

为什么会这样?有何最佳解决方案可以避免未定义行为?我是否必须采取以下措施:

unsigned uabs(signed val) {
    return val > 0
        ? val
        : (val == 1U << ((sizeof(val) * 8) - 1))
            ? -1U
            : -val;
}

(故意使用hacky来强调对stdlib的不满;-)

示例

假设您有一个4位有符号值(为了易于理解)。 无符号最大值为15,有符号(正数)最大值为7,有符号(负数)最小值为-8,因此abs(-8)无法适应有符号值。 当然,可以将其表示为-8,但是对结果进行除法和乘法会产生预期之外的结果。

2个回答

19

这个问题的真正答案在于类型提升规则。

如果我将算术运算符应用于一个unsigned int和一个int,那么int参数将被提升为unsigned,结果也是unsigned

如果abs()函数返回unsigned,那么当它在表达式中被使用时,会导致其他值的这种类型提升,从而导致意外的结果。例如,下面的代码:

if (abs(-1) * -1 < 0)
    printf("< 0\n");
else
    printf(">= 0\n");

会打印出“>= 0”的结果,这个结果可能不受许多人的喜欢。为了避免无法使用单一值INT_MIN的代价,这种情况似乎是可以接受的。


2
我可以购买这个——类型提升很复杂,你不能责怪任何人希望所有东西都适合有符号值。我想如果我能回到过去和委员会争论,我会说abs应该尽可能准确,并且C程序员已经必须知道如何处理函数返回类型和表达式中的类型提升,但我曾经把我的时间机器落在了公交车上。 - cdleary
另一方面的论点是,如果您已经在有符号值上进行算术运算,则需要检查下溢/上溢 - 这意味着您需要检查操作是否会产生低于“-INT_MAX”的结果,而不是INT_MIN,如果您将其传递给abs() - caf
同意这是一般情况的反驳。只是一个小细节的澄清:如果它是int的一种类型,你永远不必检查某些东西是否低于INT_MIN,所以在这个特定的unsigned abs(signed)情况下,在调用之前根本不需要检查参数。 - cdleary
1
这就是为什么我没有说你需要检查一个值是否低于INT_MIN - 相反,你需要检查计算结果是否会低于INT_MIN(因为这会导致有符号溢出,这是未定义的行为)。例如,如果你正在计算a-b,在正常情况下,你需要验证a>=INT_MIN+b - 如果你要将其传递给abs(),则需要将其更改为a>INT_MIN+b - caf
理论上,你仍然需要检查溢出,因为标准允许 INT_MAX == UINT_MAX。我不知道是否有任何实现这样做,但我认为在80年代这种可能性听起来是合理的。 - martinkunev

0
为什么会使用无符号空间返回值呢?
让我们考虑8位有符号和无符号数字。如果你有-128,结果是未定义的……我猜stdlib不想减慢速度那么多。如果你认为你可能有一个在那个范围内的数字,那么你需要使用其他东西。
如果你认为你的有符号字符中可能有一个大于127的值,那么你就错了。
因此,它不必能够容纳大于127的值,并且保持有符号不会失去任何东西。如果你想将它转换为无符号,那就去吧。由于它以前只是一个有符号整数,所以很有可能你将再次进行有符号运算。就我个人而言,我认为我更喜欢类型保持有符号,因为我实际上很少想要处理无符号并且我没有进行位操作。
但也许其他人可以从标准委员会挖出一些笔记。

1
如果 abs 函数返回 unsigned,那么定义 abs(INT_MIN) 就不会有问题 - 这不会使计算变慢 - 标准的底层取反操作仍然可以正常工作。这不是问题所在 - 真正的问题是类型提升。 - caf
它不应该变慢——绝对值的常见汇编序列为((x + y) ^ y),这对于最大负值产生无符号字是有效的。 - cdleary
我的意思是,你唯一的缺点就是如果你在INT_MIN处,这种情况非常罕见。我句子开头的“个人而言”暗示了caf提供的“类型”解释。我回答的主要重点是试图说服人们他们没有失去太多。 - markets

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接