繁体   English   中英

签名数据的逻辑右移

[英]logical shift right on signed data

首先,不,这不是我的作业,它是一本名为《 计算机系统程序员的视角》 (顺便说一句,优秀的书)的书提供的实验室

我需要在不使用任何以下内容的情况下对有符号整数执行逻辑右移:

  • 铸件
  • if , while , switch , for , do - while , ? :
  • 任何类型的指针

允许的操作符是: ! + ~ | >> << ^

到目前为止我尝试了什么?

/* 
 * logicalShift - shift x to the right by n, using a logical shift
 *   Can assume that 0 <= n <= 31
 *   Examples: logicalShift(0x87654321,4) = 0x08765432
 *   Legal ops: ~ & ^ | + << >>
 *   Max ops: 20
 *   Rating: 3 
 */
int logicalShift(int x, int n) {
    int mask = ~0;
    int shiftAmount = 31 + ((~n)+1);//this evaluates to 31 - n on two's complement machines
    mask = mask << shiftAmount;
    mask = ~mask;//If n equals 0, it means we have negated all bits and hence have mask = 0
    x = x >> n;
    return x & mask; 
}

只要n不等于0就可以正常工作,当它执行此代码时,此代码会中断,因为掩码将变为全 0 并且函数将返回0

我很感激任何正确方向的提示,而不是完整的代码。

同样,这不是作业; 实验室作业可在此处公开获得http://csapp.cs.cmu.edu/public/labs.html

PS:不是重复的,不要发布涉及强制转换为未签名然后转移的解决方案。

通常你只是转向无符号和后退,就像在@codewerfer的回答中一样


为了解决不允许强制转换的人为限制,可以通过这种方式制作掩码,以便在算术右移可能已经移位到非零位之后清除顶部位。

int mask = 1 << shiftAmount;
mask |= mask - 1;

这与其他方法相比

                    this approach | other approach
can have 0 bits set :     no      |      yes
can have 32 bits set:    yes      |       no

在实践中,编译器通常无法将其优化为逻辑右移。 这就是你投射的原因。 (特别是在2的补码系统上运行得非常好。)

这个问题是在 C++ 中搜索逻辑移位的第一个结果。

因此,它是有道理也回答一般情况下,在允许-因为这里没有显示的代码被编译(GCC 9.2,-O3)用正确的(和快速)操作码(只是一个单一的shr指令代替sar )。

演员版本

此版本也适用于即将推出的int128_t (目前在 GCC、Clang 和 ICC 中为__int128 )和其他未来类型。 如果您被允许使用type_traits并且您的代码将来应该可以工作而无需考虑正确的无符号类型,您应该将它用于正确的转换。

代码

#include <type_traits>

template<class T>
inline T logicalShift(T t1, T t2) {
  return 
    static_cast<
      typename std::make_unsigned<T>::type
    >(t1) >> t2;
}

汇编代码分析

把它打包成一个f(T x, T y) { return logicalShift(x, y); } f(T x, T y) { return logicalShift(x, y); }导致以下汇编指令(GCC9.2,-O3):

f(int, int):
        mov     eax, edi
        mov     ecx, esi
        shr     eax, cl
        ret
f(unsigned int, unsigned int):
        mov     eax, edi
        mov     ecx, esi
        shr     eax, cl
        ret
f(__int128, __int128):
        mov     rcx, rdx
        mov     rax, rdi
        mov     rdx, rsi
        shrd    rax, rsi, cl
        shr     rdx, cl
        xor     esi, esi
        and     ecx, 64
        cmovne  rax, rdx
        cmovne  rdx, rsi
        ret
f(unsigned __int128, unsigned __int128):
        mov     rcx, rdx
        mov     rax, rdi
        mov     rdx, rsi
        shrd    rax, rsi, cl
        shr     rdx, cl
        xor     esi, esi
        and     ecx, 64
        cmovne  rax, rdx
        cmovne  rdx, rsi
        ret

而 a T a, b; T c = a >> b; T a, b; T c = a >> b; 结果是:

f(int, int):
        mov     eax, edi      # 32-bit registers
        mov     ecx, esi
        sar     eax, cl       # lower 8-bit of cx register
        ret
f(long, long):
        mov     rax, rdi      # 64-bit registers
        mov     ecx, esi
        sar     rax, cl
        ret
f(unsigned int, unsigned int):
        mov     eax, edi
        mov     ecx, esi
        shr     eax, cl
        ret
f(__int128, __int128):
        mov     rcx, rdx
        mov     rdx, rsi
        mov     rax, rdi
        sar     rdx, cl
        shrd    rax, rsi, cl
        mov     rsi, rdx
        sar     rsi, 63
        and     ecx, 64
        cmovne  rax, rdx
        cmovne  rdx, rsi
        ret

我们看到,区别主要只是shr而不是sar (还有一些 __int128)。 什么代码可以更快?


非演员版本

(减少指令设置为 ~ & ^ | + << >>)

问题 - 左移( SALSHL

@Fingolfin 最初的想法很不错。 但是我们的处理器不会做我们首先想到的int mask = ~0 << n for n >= 32; , 但为什么?

C++ 标准(草案 N4713, 8.5.7, 2nd)表示对于 <<:

E1 << E2的值是E1左移的E2位位置; 空出的位用零填充 如果E1具有无符号类型,则结果的值为E1 × 2^E2 ,比结果类型中可表示的最大值减少模 1。 否则,如果E1具有有符号类型和非负值,并且E1 × 2^E2在结果类型的相应无符号类型中可表示,则该值转换为结果类型即为结果值; 否则,行为是 undefined

听起来像(E1 << E2) % (UINTxx_MAX + 1) ,我们只是从右边用 0 填充并用模运算切断前导位。 简单明了。

分析左移

16 位短、32 位整数和 64 位长的汇编代码(GCC 9.2,-O3)是:

g(short, short):
        movsx   eax, di    # 16-bit to 32-bit register
        mov     ecx, esi
        sal     eax, cl    # 1st is 32-bit, 2nd is 8-bit register
        ret
g(int, int):
        mov     eax, edi   # 32-bit registers
        mov     ecx, esi
        sal     eax, cl    # 1st is 32-bit, 2nd is 8-bit register
        ret
g(long, long):
        mov     rax, rdi   # 64-bit registers
        mov     ecx, esi
        sal     rax, cl    # 1st is 64-bit, 2nd is 8-bit register
        ret

所以,我们讨论了我们从~0 << i for int i = 0; i <= 33; i++断言的内容int i = 0; i <= 33; i++ int i = 0; i <= 33; i++ int i = 0; i <= 33; i++ ,但我们真正得到了什么?

 0: 11111111111111111111111111111111
 1: 11111111111111111111111111111110
 2: 11111111111111111111111111111100
 3: 11111111111111111111111111111000
 4: 11111111111111111111111111110000
 5: 11111111111111111111111111100000
 6: 11111111111111111111111111000000
 7: 11111111111111111111111110000000
 8: 11111111111111111111111100000000
 9: 11111111111111111111111000000000
10: 11111111111111111111110000000000
11: 11111111111111111111100000000000
12: 11111111111111111111000000000000
13: 11111111111111111110000000000000
14: 11111111111111111100000000000000
15: 11111111111111111000000000000000
16: 11111111111111110000000000000000
17: 11111111111111100000000000000000
18: 11111111111111000000000000000000
19: 11111111111110000000000000000000
20: 11111111111100000000000000000000
21: 11111111111000000000000000000000
22: 11111111110000000000000000000000
23: 11111111100000000000000000000000
24: 11111111000000000000000000000000
25: 11111110000000000000000000000000
26: 11111100000000000000000000000000
27: 11111000000000000000000000000000
28: 11110000000000000000000000000000
29: 11100000000000000000000000000000
30: 11000000000000000000000000000000
31: 10000000000000000000000000000000
32: 11111111111111111111111111111111
33: 11111111111111111111111111111110

我们看到,结果更像是~0 << (i%2^5)

所以,看看长(长又名。int64_t)案例:(用MSVC编译x86)

 0: 1111111111111111111111111111111111111111111111111111111111111111
 1: 1111111111111111111111111111111111111111111111111111111111111110
 2: 1111111111111111111111111111111111111111111111111111111111111100
 3: 1111111111111111111111111111111111111111111111111111111111111000
 4: 1111111111111111111111111111111111111111111111111111111111110000
 5: 1111111111111111111111111111111111111111111111111111111111100000
 6: 1111111111111111111111111111111111111111111111111111111111000000
 7: 1111111111111111111111111111111111111111111111111111111110000000
 8: 1111111111111111111111111111111111111111111111111111111100000000
 9: 1111111111111111111111111111111111111111111111111111111000000000
10: 1111111111111111111111111111111111111111111111111111110000000000
11: 1111111111111111111111111111111111111111111111111111100000000000
12: 1111111111111111111111111111111111111111111111111111000000000000
13: 1111111111111111111111111111111111111111111111111110000000000000
14: 1111111111111111111111111111111111111111111111111100000000000000
15: 1111111111111111111111111111111111111111111111111000000000000000
16: 1111111111111111111111111111111111111111111111110000000000000000
17: 1111111111111111111111111111111111111111111111100000000000000000
18: 1111111111111111111111111111111111111111111111000000000000000000
19: 1111111111111111111111111111111111111111111110000000000000000000
20: 1111111111111111111111111111111111111111111100000000000000000000
21: 1111111111111111111111111111111111111111111000000000000000000000
22: 1111111111111111111111111111111111111111110000000000000000000000
23: 1111111111111111111111111111111111111111100000000000000000000000
24: 1111111111111111111111111111111111111111000000000000000000000000
25: 1111111111111111111111111111111111111110000000000000000000000000
26: 1111111111111111111111111111111111111100000000000000000000000000
27: 1111111111111111111111111111111111111000000000000000000000000000
28: 1111111111111111111111111111111111110000000000000000000000000000
29: 1111111111111111111111111111111111100000000000000000000000000000
30: 1111111111111111111111111111111111000000000000000000000000000000
31: 1111111111111111111111111111111110000000000000000000000000000000
32: 1111111111111111111111111111111100000000000000000000000000000000
33: 1111111111111111111111111111111000000000000000000000000000000000
34: 1111111111111111111111111111110000000000000000000000000000000000
35: 1111111111111111111111111111100000000000000000000000000000000000
36: 1111111111111111111111111111000000000000000000000000000000000000
37: 1111111111111111111111111110000000000000000000000000000000000000
38: 1111111111111111111111111100000000000000000000000000000000000000
39: 1111111111111111111111111000000000000000000000000000000000000000
40: 1111111111111111111111110000000000000000000000000000000000000000
41: 1111111111111111111111100000000000000000000000000000000000000000
42: 1111111111111111111111000000000000000000000000000000000000000000
43: 1111111111111111111110000000000000000000000000000000000000000000
44: 1111111111111111111100000000000000000000000000000000000000000000
45: 1111111111111111111000000000000000000000000000000000000000000000
46: 1111111111111111110000000000000000000000000000000000000000000000
47: 1111111111111111100000000000000000000000000000000000000000000000
48: 1111111111111111000000000000000000000000000000000000000000000000
49: 1111111111111110000000000000000000000000000000000000000000000000
50: 1111111111111100000000000000000000000000000000000000000000000000
51: 1111111111111000000000000000000000000000000000000000000000000000
52: 1111111111110000000000000000000000000000000000000000000000000000
53: 1111111111100000000000000000000000000000000000000000000000000000
54: 1111111111000000000000000000000000000000000000000000000000000000
55: 1111111110000000000000000000000000000000000000000000000000000000
56: 1111111100000000000000000000000000000000000000000000000000000000
57: 1111111000000000000000000000000000000000000000000000000000000000
58: 1111110000000000000000000000000000000000000000000000000000000000
59: 1111100000000000000000000000000000000000000000000000000000000000
60: 1111000000000000000000000000000000000000000000000000000000000000
61: 1110000000000000000000000000000000000000000000000000000000000000
62: 1100000000000000000000000000000000000000000000000000000000000000
63: 1000000000000000000000000000000000000000000000000000000000000000
64: 0000000000000000000000000000000000000000000000000000000000000000
65: 0000000000000000000000000000000000000000000000000000000000000000

繁荣!

(直到 31 在 GCC 中也被简称为相同,因为它使用 32 位EAX寄存器作为sal

但是,此结果仅由编译器创建:

x86 msvc v19.22 ,/O2:
 _x$ = 8 ; size = 8 _y$ = 16 ; size = 8 __int64 g(__int64,__int64) PROC ; g, COMDAT mov eax, DWORD PTR _x$[esp-4] mov edx, DWORD PTR _x$[esp] mov ecx, DWORD PTR _y$[esp-4] jmp __allshl __int64 g(__int64,__int64) ENDP
x64 msvc v19.22 , /O2:
 x$ = 8 y$ = 16 __int64 g(__int64,__int64) PROC ; g, COMDAT mov rax, rcx mov rcx, rdx shl rax, cl ret 0 __int64 g(__int64,__int64) ENDP

并且 x64 MSVC 代码显示出与 GCC 9.2 代码相同的行为 - 使用shl而不是sal

从那时起,我们现在知道处理器本身(英特尔酷睿第 6 代)仅使用cl寄存器的最后一位数字,这取决于移位操作的第一个寄存器的长度,即使 C++ 标准另有说明。

一个小修复

所以,这是代码中断的地方。 本能地,我会采用32 - n的 shiftAmount 并进入上面的问题,您已经通过使用31 - nshiftAmount避免了这一点。 知道 n 是 0..31,就没有 32 的 shiftAmount。很好。

但是,一方面减少意味着另一方面增加。 mask现在需要从-2开始(我们不移动0b1111 ,而是移动0b1110 ):

 int logSh3(int x, int n) { int mask = ~2 + 1; int shiftAmount = 31 + ((~n) + 1);//this evaluates to 31 - n on two's complement machines mask = mask << shiftAmount; mask = ~mask;//If n equals 0, it means we have negated all bits and hence have mask = 0 x = x >> n; return x & mask; }

它有效!

替代代码和分析

Finolfins 代码(固定)

上层代码,作为汇编程序(GCC 9.2,-O3):

 logSh3(int, int): mov ecx, 31 mov edx, -2 mov eax, edi sub ecx, esi sal edx, cl mov ecx, esi not edx sar eax, cl and eax, edx ret

9 说明

哈罗德密码

int logSh2(int x, int n) { int shiftAmount = 31 + ((~n) + 1);//this evaluates to 31 - n on two's complement machines int mask = 1 << shiftAmount; mask |= mask + ((~1) + 1); x = x >> n; return x & mask; }
 logSh2(int, int): mov ecx, esi mov r8d, edi mov edi, -2147483648 shr edi, cl sar r8d, cl lea eax, [rdi-1] or eax, edi and eax, r8d ret

8条指令

我们能做得更好吗?

另一种解决方案

我们可以将0b1000右移而0b1000 ,然后将其移回 1 并反转。

 int logSh4(int x, int n) { int mask = 0x80000000; mask = mask >> n; mask = mask << 1; mask = ~mask;//If n equals 0, it means we have negated all bits and hence have mask = 0 x = x >> n; return x & mask; }
 logSh4(int, int): mov ecx, esi mov edx, -2147483648 sar edx, cl sar edi, cl lea eax, [rdx+rdx] not eax and eax, edi ret

7 指令

更好的方法?

让我们将0b0111向右移动,将它向左移动一位并加 1。所以我们0b0111了相反的事情:

 int logSh5(int x, int n) { int mask = 0x7fffffff; mask = mask >> n; mask = (mask << 1) | 1; x = x >> n; return x & mask; }
 logSh5(int, int): mov ecx, esi mov eax, 2147483647 sar eax, cl sar edi, cl lea eax, [rax+1+rax] and eax, edi ret

还剩 6 条指令。 美好的。 (但简单的演员表仍然是实践中的最佳解决方案)

明显违反问题精神的俗气答案:强制转换1不是在 C 或 C++ 中进行类型转换的唯一方法

return (x | 0U) >> n;    // assumes int and unsigned are the same width

用于 x86-64 GCC 的 Godbolt )。 请注意,它也依赖于n==0与负x实现定义的行为,因为这会留下符号位设置。 但是在正常的 2 的补码实现中,unsigned 和 int 之间的转换只是保留了位模式,以及正整数的值。 使用memcpy使类型双关语显式而不是使用隐式类型转换可以使其更健壮。

| 运算符使用通常的算术转换规则使两个操作数具有相同的类型。 我们可以使用它来隐式转换为未签名的,围绕人为限制的规则律师。

0U数字文字后缀不是0U ,它只是无符号常量的简单语言语法。 另一种选择是x & UINT_MAXx | (~UINT_MAX) x | (~UINT_MAX) 或者更有趣的是,如果你知道int的宽度并且它小于或等于unsigned ,你可以写一个像0xffffffff这样的常量,它对于正有符号的int来说太大了,如果它适合它,它将具有unsigned类型,或者longlong long甚至那些更大的无符号版本。

当然, unsigned tmp = x; 也不是演员表,所以真的只是这样做! 但这更明显只是一种愚蠢的规则-律师寻找问题的漏洞,而不是意图。


假设:

  • 这可能仅适用于 2 的补码,其中从有符号到无符号的转换不会修改对象表示的位模式。 (相关: 如何在保留原始位模式的同时将 (int) 转换为 (unsigned int)? - 在将模归约应用于无符号之前,转换会保留值,而不是位模式。)

  • unsigned int是相同的宽度, int ,所以我们不丢失任何位,并且不符号扩展以引入高1的逻辑右移之前的位。**

例如(x | 0ULL) >> n不起作用; 这将在逻辑右移之前将x符号扩展为 64 位(或其他),因此结果的低int宽度将有效地将符号位的副本移入。(假设为 2 的补码。)

解决方法:使用非填充位的确切数量为int的无符号位域。 有符号整数类型有numeric_limits<T>::digits非符号位和 1 个符号位。 (以及未知数量的填充)。 这将需要赋值,而不仅仅是将它与|一起使用| .

struct uint { 
   unsigned long u : (1 + std::numeric_limits<int>::digits);
}

如果你想memcpy进入这个结构而不是分配(例如,确保位模式在右移之前不会改变,在非 2 的补码系统上?)你可以用类似char pad[sizeof(int)]; . 这绝对是矫枉过正,比必要的填充更多,使其至少与int一样大(这是memcpy(&struct_tmp, &x, sizeof(x))或其他方向所必需的。)。 我不确定该结构是否保证至少与unsigned long一样宽,因为我们使用它作为位域的基本类型,我也必须仔细检查标准的那部分。 不过,GCC 和 MSVC 就是这种情况。 unsigned long long使其 sizeof 达到 8。IIRC, unsigned long保证至少与int一样宽。

如果我们想计算char[]数组所需的确切填充量,我们必须处理sizeof(int) - sizeof(unsigned)为负的可能性。 但是计算另一个填充位域成员的额外位数是可行的,将填充计算为CHAR_BIT * sizeof(int) - (1 + digits)

在 C99 中,我们可以使用联合类型双关来替换 memcpy,但我们可能仍然需要一个位域来确保我们将无符号值准确地截断为正确的宽度。


脚注 1 :ISO C++ 7.6.3 显式类型转换(转换符号)[expr.cast]说:

2:显式类型转换可以使用函数表示法、类型转换运算符(dynamic_cast、static_cast、reinterpret_cast、const_cast)或强制转换表示法来表达。

 cast-expression: unary-expression ( type-id ) cast-expression

整个部分的名称是[expr.cast] ,因此将这些类型转换的任何语法视为强制转换是合理的,而不仅仅是“强制转换表达式”。 幸运的是,没有必要尝试证明unsigned(x) >> n不包括强制转换。

我知道我在这里有点晚了,但我认为最好的方法是做这样的事情:

int logicalShift(int x, int n) {
   int mask = ( 1 << sizeof(int) - n ) - 1;
   return x >> n & mask;
}

掩码将最高n位设置为 0。

当然,这也可以写成:

int logicalShift(int x, int n) {
   return x >> n & ( 1 << sizeof(int) - n ) - 1;
}

但是编译器可能会理解如何优化第一个。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM