unsigned long类型使用abs函数有意义吗?

21

我发现了这段代码,恰好我的分析器报告它是一个瓶颈:

#include <stdlib.h>

unsigned long a, b;
// Calculate values for a and b
unsigned long c;

c = abs(a - b);

除了c = a - b;这条语句,它还有更加有趣的功能吗?这两个选项是否会调用未定义或实现定义的行为,并且是否存在其他潜在的陷阱?请注意,包含C标准库<stdlib.h>,而不是<cstdlib>


由于这些值是无符号的,它们不能为负数,因此 abs() 调用没有意义。但是,在 C++ 中,abs() 被重载并且也可能是一个宏,所以我不完全清楚它的作用。它甚至可以是一个接受 signed long 的函数,然后根据参数的 MSB 做一些事情。为什么要取某些东西的绝对值,而这些东西不能为负数呢?如果没有原因,只需删除 abs() 调用即可。 - Ulrich Eckhardt
3
将unsigned转换为无法表示该值的int是未定义行为。这种转换也是唯一一种abs函数会起作用的情况。 - Öö Tiib
如果 a = 10UL;b = 30UL;,正确的答案应该是 20UL,但是如果去掉 abs 就会得到错误的答案。 - Ken Y-N
2
在调用abs()函数之前,10UL - 30UL会得到4294967276UL的结果(假设是32位无符号长整型)。如果abs()函数按照其建议进行操作(请参考我的有关宏/重载的注释),则结果也是4294967276UL。如果您想要得到20,您必须使用带符号的算术运算,例如abs(static_cast<long>(a) - static_cast<long>(b))。但是,您必须确保这不会发生溢出,因为那样会产生“未定义行为”。总之,我建议您使用Mohit Jain的建议,即首先检查哪个更大。 - Ulrich Eckhardt
1
对于C++情况,这可能会在未来变成格式不正确的代码 - Shafik Yaghmour
3个回答

30
不,这没有意义。
如果你想要区分,请使用:
c = (a > b) ? a - b : b - a;

或者

c = max(a, b) - min(a, b);

无符号数如果小于零将会回绕(效果类似于加上2的sizeof(unsigned long) * CHAR_BIT次方)

如果你想要求两个数字之间的差异,你可以编写一个小模板,如下所示:

namespace MyUtils {
  template<typename T>
  T diff(const T&a, const T&b) {
    return (a > b) ? (a - b) : (b - a);
  }
}

查看从C继承而来的声明abs(因为您包括了stdlib.h)。

int       abs( int n );
long      abs( long n );
long long abs( long long n ); //    (since C++11)
//Defined in header <cinttypes>
std::intmax_t abs( std::intmax_t n ); //    (since C++11)

C++ 中使用 abs(来自 cmath)函数

float       abs( float arg );
double      abs( double arg );
long double abs( long double arg );

如果您注意到,每个函数的参数和返回类型都是“signed”。因此,如果将无符号类型传递给这些函数之一,将发生隐式转换“unsigned T1 -> signed T2 -> unsigned T1”(其中T1和T2可能相同,并且在您的情况下T1是“long”)。当将无符号整数转换为有符号整数时,如果它无法用有符号类型表示,则其行为取决于实现。
从“4.7整数转换[conv.integral]”中

  1. 如果目标类型是无符号的,则结果值是与源整数同余的最小无符号整数(模2 ^ n,其中n是用于表示无符号类型的位数)。 [注意:在二进制补码表示中,这种转换是概念性的,如果没有截断,则位模式不会改变。-注解]
  2. 如果目标类型是有符号的,则该值在可以在目标类型(和位场宽度)中表示的情况下不变;否则,该值是实现定义的。

请注意,虽然它不能用于“unsigned long”或“unsigned int”,但它可以用于“unsigned char”和“unsigned short”(除了没有人使用的病态系统)。 - Dietrich Epp
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - dwn
s/sizeof(unsigned long)/sizeof(unsigned long) * CHAR_BIT - The Paramagnetic Croissant
@TheParamagneticCroissant 谢谢您的纠正 :) - Mohit Jain

9

我不知道您是否认为这是有意义的,但是应用于无符号值的abs()函数肯定会返回与传入的值不同的值。这是因为abs()函数接受一个int类型的参数并返回一个int类型的值。

例如:

#include <stdlib.h>
#include <stdio.h>

int main(void)
{
    unsigned u1 = 0x98765432;
    printf("u1 = 0x%.8X; abs(u1) = 0x%.8X\n", u1, abs(u1));
    unsigned long u2 = 0x9876543201234567UL;
    printf("u2 = 0x%.16lX; abs(u2) = 0x%.16lX\n", u2, labs(u2));
    return 0;
}

在使用GCC 4.9.1编译器在Mac OS X 10.10.1 Yosemite上将其编译为C或C++时,它会产生以下结果:

u1 = 0x98765432; abs(u1) = 0x6789ABCE
u2 = 0x9876543201234567; abs(u2) = 0x6789ABCDFEDCBA99

如果无符号值的最高位设置了,则abs()的结果不是传递给函数的值。
减法只是一种干扰;如果结果具有最高有效位设置,那么从abs()返回的值将与传递给它的值不同。
当您使用C++头文件编译此代码而不是问题中显示的C头文件时,它无法编译并出现模棱两可的调用错误。
#include <cstdlib>
#include <iostream>
using namespace std;

int main(void)
{
    unsigned u1 = 0x98765432;
    cout << "u1 = 0x" << hex << u1 << "; abs(u1) = 0x" << hex << abs(u1) << "\n";
    unsigned long u2 = 0x9876543201234567UL;
    cout << "u2 = 0x" << hex << u2 << "; abs(u2) = 0x" << hex << abs(u2) << "\n";
    return 0;
}

编译错误:

absuns2.cpp: In function ‘int main()’:
absuns2.cpp:8:72: error: call of overloaded ‘abs(unsigned int&)’ is ambiguous
     cout << "u1 = 0x" << hex << u1 << "; abs(u1) = 0x" << hex << abs(u1) << "\n";
                                                                        ^
absuns2.cpp:8:72: note: candidates are:
In file included from /usr/gcc/v4.9.1/include/c++/4.9.1/cstdlib:72:0,
                 from absuns2.cpp:1:
/usr/include/stdlib.h:129:6: note: int abs(int)
 int  abs(int) __pure2;
      ^
In file included from absuns2.cpp:1:0:
/usr/gcc/v4.9.1/include/c++/4.9.1/cstdlib:174:3: note: long long int std::abs(long long int)
   abs(long long __x) { return __builtin_llabs (__x); }
   ^
/usr/gcc/v4.9.1/include/c++/4.9.1/cstdlib:166:3: note: long int std::abs(long int)
   abs(long __i) { return __builtin_labs(__i); }
   ^
absuns2.cpp:10:72: error: call of overloaded ‘abs(long unsigned int&)’ is ambiguous
     cout << "u2 = 0x" << hex << u2 << "; abs(u2) = 0x" << hex << abs(u2) << "\n";
                                                                        ^
absuns2.cpp:10:72: note: candidates are:
In file included from /usr/gcc/v4.9.1/include/c++/4.9.1/cstdlib:72:0,
                 from absuns2.cpp:1:
/usr/include/stdlib.h:129:6: note: int abs(int)
 int  abs(int) __pure2;
      ^
In file included from absuns2.cpp:1:0:
/usr/gcc/v4.9.1/include/c++/4.9.1/cstdlib:174:3: note: long long int std::abs(long long int)
   abs(long long __x) { return __builtin_llabs (__x); }
   ^
/usr/gcc/v4.9.1/include/c++/4.9.1/cstdlib:166:3: note: long int std::abs(long int)
   abs(long __i) { return __builtin_labs(__i); }
   ^

因此,问题中的代码仅在使用C样式头文件时编译,当使用C ++头文件时不会编译。如果您添加<stdlib.h>以及<cstdlib>,则有一个额外的重载可用于使调用更加模糊。
如果您在对abs()的调用中添加(不)适当的转换,则可以使代码编译,有符号数量的绝对值可能与原始有符号数量不同,这并不奇怪。
#include <cstdlib>
#include <iostream>
using namespace std;

int main(void)
{
    unsigned u1 = 0x98765432;
    cout << "u1 = 0x" << hex << u1 << "; abs(u1) = 0x" << hex << abs(static_cast<int>(u1)) << "\n";
    unsigned long u2 = 0x9876543201234567UL;
    cout << "u2 = 0x" << hex << u2 << "; abs(u2) = 0x" << hex << abs(static_cast<long>(u2)) << "\n";
    return 0;
}

输出:

u1 = 0x98765432; abs(u1) = 0x6789abce
u2 = 0x9876543201234567; abs(u2) = 0x6789abcdfedcba99

教训:在C++代码中不要使用已有C++等效的C头文件,而应该使用C++头文件。


即使包含了<stdlib.h>,在C++中abs应该是一个重载函数。但是,要么实现没有这样做,要么abs(int)不会被选择为重载。 - user743382
如果您只包括<stdlib.h>,那就不行。这只为您提供标准C函数。我不知道哪些C++头文件定义了abs()的重载。而且我不知道这些重载是否包括无符号类型。 - Jonathan Leffler
<cstdlib>被指定为包含C的<stdlib.h>的声明,除了在命名空间std中,还包括long abs(long)long long abs(long long)。C++的<stdlib.h>被指定为包含C++的<cstdlib>的声明,除了在全局命名空间中。(引用C++11:“每个C头文件,其名称形式为name.h,都表现得好像由相应的cname头文件放置在标准库命名空间中的每个名称都被放置在全局命名空间范围内。”)这将包括添加的重载。但是实现存在错误,并且并非所有实现都能正确处理此问题。 - user743382
如果问题中的代码包含了<cstdlib>而不是<stdlib.h>,我就不会创建一个双语测试程序。我引用了我使用的环境以便于跟踪错误。今天稍后我可能会尝试一下是否使用<cstdlib>而不是<stdlib.h>有所不同;在没有实验的情况下,我不会进行调用。 - Jonathan Leffler

-3

我认为当c=a-b为负数时,如果c是无符号数,那么c不是准确的答案。使用abs函数可以保证c为正数。


2
如果你是指(学校数学)自然数计算,那么“不是准确的答案”是可能发生的。但在C++中,如果超出了无符号值的范围,它们将会环绕,因此结果始终为正数。在其上使用abs()函数不应改变其值。 - Ulrich Eckhardt

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