gpt4 book ai didi

c - C 中有符号整数的 "Anomaly"

转载 作者:太空狗 更新时间:2023-10-29 14:56:54 31 4
gpt4 key购买 nike

我目前正在撰写有关 ARM 优化的讲座,特别是将 NEON 等 vector 机作为最终目标。

由于 vector 机不能很好地处理 if-else 激流回旋,我正在尝试演示如何通过位破解来摆脱它们。

我选择了“绝对饱和”函数作为例子。它实际上是一个 ABS 例程,增加了将结果限制在 0x7fffffff 的功能。

最大可能的负 32 位数字是 0x80000000,这是一件非常危险的事情,因为 val = -val; 返回与初始值相同的 0x80000000,这是由 two's complement 中的不对称性引起的系统特别适用于 DSP 操作,因此,它必须被过滤掉,主要是通过“饱和”。

int32_t satAbs1(int32_t val)
{
if (val < 0) val = -val;
if (val < 0) val = 0x7fffffff;
return val;
}

下面是我要用汇编写的内容:

cmp     r0, #0
rsblts r0, r0, #0
mvnlt r0, #0x80000000
bx lr

下面是我从上面的 C 代码中得到的实际结果:

satAbs1
0x00000000: CMP r0,#0
0x00000004: RSBLT r0,r0,#0
0x00000008: BX lr

什么?编译器完全丢弃了饱和部分!

编译器似乎在第一个 if 语句之后排除了 val 为负数的可能性,如果它是 0x80000000 就不是真的

或者函数应该返回一个无符号值?

uint32_t satAbs2(int32_t val)
{
uint32_t result;
if (val < 0) result = (uint32_t) -val; else result = (uint32_t) val;
if (result == 0x80000000) result = 0x7fffffff;
return result;
}

satAbs2
0x0000000C: CMP r0,#0
0x00000010: RSBLT r0,r0,#0
0x00000014: BX lr

不幸的是,它生成的机器代码与签名版本完全相同:没有饱和。

同样,编译器似乎排除了 val 为 0x80000000 的情况

好吧,让我们扩大第二个 if 语句的范围:

uint32_t satAbs3(int32_t val)
{
uint32_t result;
if (val < 0) result = (uint32_t) -val; else result = (uint32_t) val;
if (result >= 0x80000000) result = 0x7fffffff;
return result;
}

satAbs3
0x00000018: CMP r0,#0
0x0000001C: RSBLT r0,r0,#0
0x00000020: CMP r0,#0
0x00000024: MVNLT r0,#0x80000000
0x00000028: BX lr

最后,编译器似乎在做它的工作,尽管是超优化的(与汇编版本相比,一个不必要的 CMP)

我可以忍受编译器不是最优的,但令我困扰的是他们排除了一些他们不应该排除的东西:0x80000000

我什至会就此向 GCC 开发人员提交错误报告,但我发现 Clang 也排除了整数为 0x80000000 的情况,因此我想我遗漏了一些关于 C 标准的东西。

谁能告诉我我错在哪里?

顺便说一句,下面是 if-less bit-hacking 版本的样子:

int32_t satAbs_bh(int32_t val)
{
int32_t temp = val ^ (val>>31);
val = temp + (val>>31);
val ^= val>>31;
return val;
}

satAbs_bh
0x0000002C: EOR r3,r0,r0,ASR #31
0x00000030: ADD r0,r3,r0,ASR #31
0x00000034: EOR r0,r0,r0,ASR #31
0x00000038: BX lr

编辑:我同意这个问题,我的问题在某种程度上是重复的。
但是,它更全面,包括一些汇编级别的东西和位掩码技术,与引用的相比可能会有帮助。

下面是在不破坏编译器选项的情况下解决这个问题的方法;抢先排除整数溢出的可能:

int32_t satAbs4(int32_t val)
{
if (val == 0x80000000) return 0x7fffffff;
if (val < 0) val = -val;
return val;
}
satAbs4
0x0000002C: CMP r0,#0x80000000
0x00000030: BEQ {pc}+0x10 ; 0x40
0x00000034: CMP r0,#0
0x00000038: RSBLT r0,r0,#0
0x0000003C: BX lr
0x00000040: MVN r0,#0x80000000
0x00000044: BX lr

同样,我正在使用的 linaro GCC 7.4.1 展示了它的缺点:我不理解第 2 行中的 BEQmoveq r0, #0x80000001 如源代码中所建议的那样,可以在末尾保存两条指令。

最佳答案

有符号整数上溢或下溢在 C 语言中是未定义的行为,这意味着您需要自己处理这些边缘情况。换句话说,一旦编译器确定某个带符号的整数值是正数,它就不会关心它是否有可能通过 UB 变为负数。

例如,这段代码:

int test(int input)
{
if (input > 0)
input += 100;

if (input > 0)
input += 100;

if (input > 0)
input += 100;

return input;
}

在法律上可以是optimized to this :

int test(int input)
{
if (input > 0)
input += 300;

return input;
}

即使初始代码的作者可能预料到 input可能会在每个连续的语句之间溢出。

这就是为什么优化编译器会将您的代码视为如下内容:

int32_t satAbs1(int32_t val)
{
if (val < 0) val = -val;

// val must be positive here,
// unless you are relying on UB

// the following condition is
// therefore always false:
// if (val < 0) val = 0x7fffffff;

return val;
}

因此,避免 UB 的唯一方法是在有可能调用 UB 的情况下避免对有符号整数求反,即:

int32_t satAbs3_simple(int32_t val)
{
if (val >= 0)
return val;

// we know that val is negative here,
// but unfortunately gcc knows it as well,
// so we'll handle the edge case explicitly

if (val == INT32_MIN)
return INT32_MAX;

return -val;
}

带有 -O2 的 gcc 生成带有分支的代码(在 bxge 处提前条件返回):

satAbs3_basic:
cmp r0, #0
bxge lr // return r0 if ge #0
cmp r0, #0x80000000
rsbne r0, r0, #0
moveq r0, #0x7FFFFFFF
bx lr

正如@rici 在评论中提到的,如果来自 stdint.h 的精确宽度带符号整数类型( intN_t ) 在您的编译器上可用,这意味着它们必须用 N 位表示,没有填充,使用 2 的补码。

这意味着您可以稍微重写代码以使用位掩码,这可能会提供更短的汇编输出(至少使用 gcc 5 or newer ),仍然没有分支:

int32_t satAbs3_c(int32_t val)
{
uint32_t result = (uint32_t)val;
if (result & 0x80000000) result = -result; // <-- avoid UB here by negating uint32_t
if (result == 0x80000000) result = 0x7FFFFFFF;
return (int32_t)result;
}

请注意,优化编译器理论上应该能够为这两种情况生成相同的输出,但无论如何,最后一个片段的最新 gcc 版本(带有 -O1)给出:

satAbs3_c:
cmp r0, #0
rsblt r0, r0, #0
cmp r0, #0x80000000
moveq r0, #0x7FFFFFFF
bx lr

我实际上相信它不能比这更短(除了 xor 位黑客攻击),因为您的初始组装似乎缺少 cmp r0, #0 rsblts 之后的说明(因为 rsblts 更改了 r0 ,而 cmp 是实际比较发生的部分)。

关于c - C 中有符号整数的 "Anomaly",我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57220289/

31 4 0
文章推荐: c - 是否可以通过分配内存来恢复 secret 数据(例如用于解密的空闲内存中的 RSA 私钥)?
文章推荐: python - wiki/docbook/latex文档模板系统
文章推荐: html - 将两个图像对齐到
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com