【问题标题】:How to avoid trap representations when doing XOR bit-cancellation on signed ints?在对有符号整数进行 XOR 位取消时如何避免陷阱表示?
【发布时间】:2016-10-28 17:29:58
【问题描述】:

正如Given three numbers, find the second greatest of them 的建议解决方案,我写道:

int second_largest(int a, int b, int c) {
    int smallest = min(min(a, b), c);
    int largest = max(max(a, b), c);

    /* Toss all three numbers into a bag, then exclude the
       minimum and the maximum */
    return a ^ b ^ c ^ smallest ^ largest;
}

这个想法是^ smallest ^ largest 取消了这些位,以便保留中间数字。

不过,@chux 指出了一个问题:

inta ^ b ^ c ^ smallest ^ largest 的一个独特问题是中间结果可能是罕见的非 2 补码平台上的陷阱表示。 – chux

@chux 请解释一下? XOR 只是逐位运算,并不关心位代表什么,对吧? – 200_成功

XOR 不关心,但结果可能有问题:例如使用符号大小整数,-1 ^ 1 转到-0,这可能是一个陷阱值 - 停止代码。参见 C11 §6.2.6.2 2. 位运算更好地用于无符号类型。 – chux

Further C11 §6.2.6.2 3 在罕见的非 2 的补码平台上指定 ^ 的实现定义行为 int 。特别是“未指定这些情况是否实际生成负零或正常零”,呈现 a ^ b ^ c ^ minimum ^ maximum 未指定即使不使用陷阱值它也会按需要工作。下一节将解释这如何成为 UB。最好将这个新颖的代码留给无符号类型。 – chux

令人遗憾的是,本应在逻辑和数学上合理的技术可能会因技术问题而脱轨。

有没有办法挽救这种 XOR 技术并使其在法律上安全,理想情况下运行时开销为零? (可能涉及工会的事情?)

【问题讨论】:

  • 请注意,这不是 XOR 独有的 - 相同的参数可以应用于任何位运算符。
  • 您只需要三个比较即可获得三个中第二大的比较。这比前两行中的多重比较差多少?
  • 我会做三个比较并将结果编码为索引,然后将逻辑实现为 8-case 开关。
  • “有没有办法挽救这种 XOR 技术”对我来说,在此之前的第一个问题应该是 是否有任何理由挽救这种技术? ...我没有看到。至于“一种应该在逻辑上和数学上合理的技术”,这假设(A)数学关心位表示,(B)语言标准化用于存储这些值的表示,并且(C)两者都同意这一点。这些都不是真的。为什么不只使用数学运算符并正确使用它,而不是搞乱位操作(我喜欢,但似乎对此并不相关/有用)
  • @underscore_d 除了陷阱表示问题之外,此技术工作的唯一其他要求是位集合(例如0x2545f28a)在ab 中意味着相同的事情, c 就像在 smallestlargest 中一样。位代表什么无关紧要,只要它是一致的,以便位抵消。

标签: c c99 undefined-behavior bitwise-xor signed-integer


【解决方案1】:

有没有办法挽救这种 XOR 技术并使其合法安全,理想情况下运行时开销为零

使用内联汇编。汇编语言不受 C/C++ 规则的约束。

可能类似于以下内容。

$ cat test.c 
#include <stdio.h>
#include <stdlib.h>

#define min(x, y) ((x) < (y) ? (x) : (y))
#define max(x, y) ((x) > (y) ? (x) : (y))

int second_largest(int a, int b, int c)
{
    int s = min(min(a, b), c);
    int l = max(max(a, b), c);

    int result;
    __asm volatile (
    "mov %1, %0    \n\t"
    "xor %2, %0    \n\t"
    "xor %3, %0    \n\t"
    "xor %4, %0    \n\t"
    "xor %5, %0    \n\t"
        : "=r"(result)
        : "g"(a), "g"(b), "g"(c), "g"(s), "g"(l)
    );

    return result;
}

int main(int argc, char* argv[])
{
    int n = second_largest(10,20,30);
    printf("Result: %d\n", n);

    return 0;
}

上面的 r 机器约束意味着一个寄存器。上面的 g 机器约束表示寄存器、内存位置或立即数。另请参阅 GCC 手册的 Section 6.42.1 Simple Constraints

然后:

$ gcc -Wall -Wstrict-overflow=5 test.c -o test.exe
$ ./test.exe 
Result: 20

下面是来自objdump --disassemble test.o-O3 编译后的内容。看起来开销保持在最低限度。看起来字节 0x00-0x17 执行 minmax;看起来xor 是在字节 0x18-0x20 处执行的。

0000000000000000 <second_largest>:
   0:   39 d7                   cmp    %edx,%edi
   2:   89 d0                   mov    %edx,%eax
   4:   89 d1                   mov    %edx,%ecx
   6:   0f 4e c7                cmovle %edi,%eax
   9:   39 f0                   cmp    %esi,%eax
   b:   0f 4f c6                cmovg  %esi,%eax
   e:   39 d7                   cmp    %edx,%edi
  10:   0f 4d cf                cmovge %edi,%ecx
  13:   39 f1                   cmp    %esi,%ecx
  15:   0f 4c ce                cmovl  %esi,%ecx
  18:   89 f8                   mov    %edi,%eax
  1a:   31 f0                   xor    %esi,%eax
  1c:   31 d0                   xor    %edx,%eax
  1e:   31 c0                   xor    %eax,%eax
  20:   31 c8                   xor    %ecx,%eax
  22:   c3                      retq   

内联汇编的缺点是它并非无处不在。与上述类似的 IA-32 代码适用于 BSD、Linux、OS X、某些 Solaris 和某些 Windows。对于保留,如 Solaris Studio 12 或 Visual Studio x64 之前的版本,您将需要一个由汇编程序汇编并从 C/C++ 代码调用的汇编源文件中提供的函数(即 *.S*.asm文件)。

Solaris Studio 12 added support for GCC inline assembly,因此早期版本的 Sun Studio 需要汇编程序。 Windows Win32 支持 Intel 语法中的内联 ASM,但 X64 要求您使用 MASM,因为没有内联汇编。

【讨论】:

  • 有趣的解决方法。但是 X86 使用二进制补码,所以无论如何都不会有陷阱表示。使用 X86 程序集作为解决方法基本上通过使代码不可移植来“解决”问题。
  • @200_success - XOR 与 AND、OR 和 NEG 一样,是一个比特。它不关心您是否将位模式解释为有符号或无符号。是的,代码是不可移植的。我们经常不得不使用汇编语言来解决 C 语言的缺点。使用汇编语言来避免 C 语言缺点的一些其他示例是检查溢出、恒定时间操作、缓存敏感操作、cpu 特征检测和归零器。课程标准。
  • @200_success - 对于它的价值,我在这里是因为我想知道它的含义:a = 20; b = -40; a ^ b;。我试图发现在归一化为符号/幅度表示后得到的符号应该是什么。是否像 NEG 一样,bittop 仅针对某些类型或范围进行了明确定义?例如,uint32_t a = 10; uint32_t b= -a 对数据类型没有意义。还是我需要标准化为 2 的恭维表示?还是我需要将范围限制为正数?
猜你喜欢
  • 1970-01-01
  • 2022-11-29
  • 1970-01-01
  • 2018-04-13
  • 1970-01-01
  • 2010-11-16
  • 2015-04-08
  • 2011-10-07
  • 2017-06-16
相关资源
最近更新 更多