跨多个平台的带有无符号数的 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
参数调用函数。 x
和 y
都是 uint32_t
,所以结果也是 uint32_t
,所以它永远不会是负数。无符号类型的算术运算“环绕”。
labs
接受long
参数,因此参数在传递给函数之前转换 为long
。所以uint32_t
转换为long
,这是实现定义的,但基本上意味着大于LONG_MAX
的值会导致负值。
您的abs
是一个以uint32_t
类型调用的模板,因为参数具有uint32_t
类型。 uint32_t
永远不会为负数,因此 (val >= static_cast<T>(0))
始终为真,它是一个恒等函数。
llabs
接受long long
参数,因此参数转换为long long
。 long 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
类型,它可以代表< -UINT32_t , UINT32_MAX >
值的所有范围。以上是关于跨多个平台的带有无符号数的 std::labs 的未定义行为的主要内容,如果未能解决你的问题,请参考以下文章