跨多个平台的带有无符号数的 std::labs 的未定义行为

Posted

技术标签:

【中文标题】跨多个平台的带有无符号数的 std::labs 的未定义行为【英文标题】:undefined behavior with std::labs with unsigned number across multiple platforms 【发布时间】:2022-01-08 22:56:27 【问题描述】:

我想找到 (a-b) 的绝对值,其中 a、b 是 32 位无符号整数。我使用了 std::labs,如下所示。但是操作在不同平台上的表现不同!

#include <iostream>
#include <cstdlib>
using namespace std;

int main()

    uint32_t x = 0, y = 0, z_u = 0, result_labs = 0, result_abs = 0, result_llabs = 0;
    int32_t z = 0;
    for (size_t i = 0; i < 10; i++)
    
        x = rand();
        y = rand();

        z = x - y;
        z_u = x - y;

        result_labs = labs(x - y);   //Problematic Call

        result_abs = std::abs(static_cast<int32_t>(x) - static_cast<int32_t>(y));
        result_llabs = static_cast<uint32_t>(llabs(x - y));

        if ((result_abs != result_labs) || (result_abs != result_llabs))
        
            printf("[Error] X: %d       Y: %d      z: %d    z_u: %u   \tlabs: %d    - abs: %d    llabs: %d\n", x, y, z, z_u, result_labs, result_abs, result_llabs);
        
    

    return 0;

问题:

使用std::labs 对无符号整数进行的操作在不同平台上会产生不同的结果。例如 gcc linux , ghs 平台 如何正确处理这种 abs 差异计算?

/*Windows PC 中 VS 中的示例输出

[Error] X: 41         Y: 18467      z: -18426    z_u: 4294948870          labs: 18426    - abs: 18426    llabs: -18426
[Error] X: 6334       Y: 26500      z: -20166    z_u: 4294947130          labs: 20166    - abs: 20166    llabs: -20166
[Error] X: 11478      Y: 29358      z: -17880    z_u: 4294949416          labs: 17880    - abs: 17880    llabs: -17880
[Error] X: 5705       Y: 28145      z: -22440    z_u: 4294944856          labs: 22440    - abs: 22440    llabs: -22440
[Error] X: 2995       Y: 11942      z: -8947     z_u: 4294958349          labs: 8947     - abs: 8947     llabs: -8947
[Error] X: 4827       Y: 5436       z: -609      z_u: 4294966687          labs: 609      - abs: 609      llabs: -609

GHS 输出

[Error] X: 11188   Y: 27640   z: -16452    z_u: 4294950844  labs: -16452    - abs: 16452    llabs: -16452
[Error] X: 4295    Y: 12490   z: -8195     z_u: 4294959101  labs: -8195     - abs: 8195     llabs: -8195
[Error] X: 5062    Y: 27943   z: -22881    z_u: 4294944415  labs: -22881    - abs: 22881    llabs: -22881
[Error] X: 21352   Y: 32044   z: -10692    z_u: 4294956604  labs: -10692    - abs: 10692    llabs: -10692
[Error] X: 4714    Y: 9737    z: -5023     z_u: 4294962273  labs: -5023     - abs: 5023     llabs: -5023
[Error] X: 17346   Y: 28482   z: -11136    z_u: 4294956160  labs: -11136    - abs: 11136    llabs: -11136
 

【问题讨论】:

错误/意外结果不一定是未定义的行为。在您的代码中,对labs 的调用被注释为// correct output,那么它有什么问题? 正确的输出在嵌入式中产生错误的输出,类似于未定义的行为! 请在问题中包含预期和实际输出。您发布的labs 的输出看起来不错 in embedded 什么“嵌入”?对于每个经过测试的“平台”:您使用的是什么编译器,确切地说是什么平台,您使用的是什么编译器选项?您的代码可能无效 - 您正在使用 %u 打印 uint32_t。您正在比较苹果和橙子 - 您的函数适用于无符号值,labs 适用于 signed long。未签名的肯定会是一个很大的数字。 T 需要签名,您必须在计算之前或之后转换您的值。 uint32_t - uint32_t 总是积极的,它环绕着...... 测试被破坏 - 实时 - godbolt.org/z/Gc8GTnc6d - clang 检测到 UB 并删除大部分代码。注释掉第 21 行 result_abs = abs(x - y); //Undifined behaviour 看看会发生什么。其他编译器很多或可能不会检测到 UB,但无论哪种方式,代码中的 UB 都会使所有测试无效。 【参考方案1】:

行为已定义(printf 除外)。

您使用x - y 参数调用函数。 xy 都是 uint32_t,所以结果也是 uint32_t,所以它永远不会是负数。无符号类型的算术运算“环绕”。

labs 接受long 参数,因此参数在传递给函数之前转换long。所以uint32_t转换long,这是实现定义的,但基本上意味着大于LONG_MAX的值会导致负值。

您的abs 是一个以uint32_t 类型调用的模板,因为参数具有uint32_t 类型。 uint32_t永远不会为负数,因此 (val &gt;= static_cast&lt;T&gt;(0)) 始终为真,它是一个恒等函数。

llabs 接受long long 参数,因此参数转换为long longlong long 至少有 64 位,LLONG_MAX 至少在 2^63-1 附近。任何uint32_t 类型的值都可以用long long 表示。 uint32_t 永远不会是负数,转换为long long 不会改变值,所以llabs 只是接收一个正值,所以llabs 它什么都不做并返回原始值。

您的printf 调用可能无效-%u 用于打印unsigned int,而不是uint32_t。使用inttypes.h 中的PRIu32,或使用C++:

#include <cinttypes>
int main() 
   uint32_t val;
   printf("%"PRIu32"\n", val);
   // or, for example explicit C-style cast:
   printf("%u\n", (unsigned)val);

在 c++ 中实现 std::labs 的正确方法是什么?

long labs(long x) 
   return x < 0 ? -x : x;

就够了。请注意,类型是明确长的。

【讨论】:

感谢您快速详细的回答! printf 中 %u 的原因是将代码中的用法复制为 uint32_t! “x 和 y 都是 uint32_t,所以结果也是 uint32_t,所以它永远不会是负数。对无符号类型的算术运算“环绕”“我从来没有想过这个! So uint32_t is converted to long, which is implementation-defined 这是自 C++20 以来定义的标准。 只是为了确认找到 abs(x-y) 的正确方法是什么? 你必须定义“正确”。您的代码按原样正常工作 - x-y 是一个正值,而您的函数 abs(x-y) 返回一个正值 - 对我来说看起来是正确的。你似乎想要实现subtract_then_abs(x, y)。那可能只是llabs((long long)x - (loing long)y),它可以“正确”地为uint32_t 变量工作,并返回一个long long 类型,它可以代表&lt; -UINT32_t , UINT32_MAX &gt; 值的所有范围。

以上是关于跨多个平台的带有无符号数的 std::labs 的未定义行为的主要内容,如果未能解决你的问题,请参考以下文章

JVM | Java程序如何执行

有符号数的加减法 和无符号数的加减法,和,系统是如何识别有符号数和无符号数的

汇编语言无符号数与有符号数转换

有符号数和无符号数

Class类文件结构

有符号数和无符号数