简体   繁体   English

签名数据的逻辑右移

[英]logical shift right on signed data

Before anything, no this is not my homework, it's a lab given by a book called Computer Systems A Programmer's Perspective (Excellent book btw)首先,不,这不是我的作业,它是一本名为《 计算机系统程序员的视角》 (顺便说一句,优秀的书)的书提供的实验室

I need to perform a logical shift right on signed integers without using any the following:我需要在不使用任何以下内容的情况下对有符号整数执行逻辑右移:

  • casting铸件
  • if , while , switch , for , do - while , ? if , while , switch , for , do - while , ? :
  • pointers of any type任何类型的指针

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

What have I tried so far?到目前为止我尝试了什么?

/* 
 * 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; 
}

This works just fine as long as n doesn't equal 0 , when it does this code breaks because the mask will turn to all 0s and the function will return 0 .只要n不等于0就可以正常工作,当它执行此代码时,此代码会中断,因为掩码将变为全 0 并且函数将返回0

I'd appreciate any hint in the right direction rather than a complete code.我很感激任何正确方向的提示,而不是完整的代码。

Again, this is not a homework;同样,这不是作业; the lab-assignments are publicly available here http://csapp.cs.cmu.edu/public/labs.html实验室作业可在此处公开获得http://csapp.cs.cmu.edu/public/labs.html

PS: Not a duplicate, do not post solutions that involve casting to unsigned and then shifting. PS:不是重复的,不要发布涉及强制转换为未签名然后转移的解决方案。

Normally you'd just cast to unsigned and back, like in @codewerfer's answer 通常你只是转向无符号和后退,就像在@codewerfer的回答中一样


To work around the artificial restriction of not being allowed to cast, you can make a mask this way to clear the top bits after an arithmetic right shift might have shifted in non-zero bits. 为了解决不允许强制转换的人为限制,可以通过这种方式制作掩码,以便在算术右移可能已经移位到非零位之后清除顶部位。

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

Which compares to the other approach like this 这与其他方法相比

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

In practice compilers generally fail to optimize this to a logical right shift. 在实践中,编译器通常无法将其优化为逻辑右移。 That's why you cast instead. 这就是你投射的原因。 (Especially on a 2's complement system that works very well.) (特别是在2的补码系统上运行得非常好。)

This question is the first result searching for logical shift in C++ .这个问题是在 C++ 中搜索逻辑移位的第一个结果。

Therefore, it makes sense also to answer the general case, where cast is allowed - because none of the codes shown here is compiled (GCC 9.2, -O3) with the correct (and fast) op-code (just a single shr instruction instead of sar ).因此,它是有道理也回答一般情况下,在允许-因为这里没有显示的代码被编译(GCC 9.2,-O3)用正确的(和快速)操作码(只是一个单一的shr指令代替sar )。

Cast Version演员版本

This version works also for the upcoming int128_t (currently __int128 in GCC, Clang and ICC) and other future types.此版本也适用于即将推出的int128_t (目前在 GCC、Clang 和 ICC 中为__int128 )和其他未来类型。 If you're allowed to use type_traits and your code should work in future without thinking what would be the correct unsigned type, you should use it for the correct cast.如果您被允许使用type_traits并且您的代码将来应该可以工作而无需考虑正确的无符号类型,您应该将它用于正确的转换。

Code代码

#include <type_traits>

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

Analysis of Assembler code汇编代码分析

Packed it into a f(T x, T y) { return logicalShift(x, y); }把它打包成一个f(T x, T y) { return logicalShift(x, y); } f(T x, T y) { return logicalShift(x, y); } results in following Assembler instructions (GCC9.2, -O3): 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

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

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

We see, difference is mainly only shr instead of sar (and some more for __int128).我们看到,区别主要只是shr而不是sar (还有一些 __int128)。 What code could be faster?什么代码可以更快?


No-Cast Versions非演员版本

(with a reduced instruction set to ~ & ^ | + << >>) (减少指令设置为 ~ & ^ | + << >>)

The Problem - Shift Left ( SAL , SHL )问题 - 左移( SALSHL

The original idea of @Fingolfin is quite fine. @Fingolfin 最初的想法很不错。 But our processor won't do what we aspect on our first mind of int mask = ~0 << n for n >= 32;但是我们的处理器不会做我们首先想到的int mask = ~0 << n for n >= 32; , but why? , 但为什么?

The C++ Standard (draft N4713, 8.5.7, 2nd) says for <<: C++ 标准(草案 N4713, 8.5.7, 2nd)表示对于 <<:

The value of E1 << E2 is E1 left-shifted E2 bit positions; E1 << E2的值是E1左移的E2位位置; vacated bits are zero-filled .空出的位用零填充 If E1 has an unsigned type, the value of the result is E1 × 2^E2 , reduced modulo one more than the maximum value representable in the result type.如果E1具有无符号类型,则结果的值为E1 × 2^E2 ,比结果类型中可表示的最大值减少模 1。 Otherwise, if E1 has a signed type and non-negative value , and E1 × 2^E2 is representable in the corresponding unsigned type of the result type, then that value, converted to the result type, is the resulting value;否则,如果E1具有有符号类型和非负值,并且E1 × 2^E2在结果类型的相应无符号类型中可表示,则该值转换为结果类型即为结果值; otherwise, the behavior is undefined .否则,行为是 undefined

Sound like (E1 << E2) % (UINTxx_MAX + 1) , where we just fill from right with 0 and cut off the leading bits with the modulo operation.听起来像(E1 << E2) % (UINTxx_MAX + 1) ,我们只是从右边用 0 填充并用模运算切断前导位。 Simple and clear.简单明了。

Analyze Shift Left分析左移

Assembler codes (GCC 9.2, -O3) for 16-bit short, 32-bit int and 64-bit long are: 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

So, we discussed what we assert from ~0 << i for int i = 0; i <= 33; i++所以,我们讨论了我们从~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++ , but what do we really get? 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

We see, the result is more like ~0 << (i%2^5) .我们看到,结果更像是~0 << (i%2^5)

So, look at the long (long aka. int64_t) case: (compiled with MSVC for x86)所以,看看长(长又名。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

Boom!繁荣!

(same till 31 holds for short in GCC too, cause it uses 32-bit EAX register for sal ) (直到 31 在 GCC 中也被简称为相同,因为它使用 32 位EAX寄存器作为sal

But, this results are only created by the compiler:但是,此结果仅由编译器创建:

x86 msvc v19.22 , /O2: 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: 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

And the x64 MSVC code shows the same behavior like the GCC 9.2 code - with shl instead of sal .并且 x64 MSVC 代码显示出与 GCC 9.2 代码相同的行为 - 使用shl而不是sal

From that point, we know now that the processor itself (Intel Core 6th Gen.) use only the last digits of the cl register, depending on the length of the first register for the shift operation, even if the C++ standard says something else.从那时起,我们现在知道处理器本身(英特尔酷睿第 6 代)仅使用cl寄存器的最后一位数字,这取决于移位操作的第一个寄存器的长度,即使 C++ 标准另有说明。

A small fix一个小修复

So, this is, where the code breaks.所以,这是代码中断的地方。 Instinctively I would take a shiftAmount of 32 - n and get into the upper problem, you already avoided this by using a shiftAmount of 31 - n .本能地,我会采用32 - n的 shiftAmount 并进入上面的问题,您已经通过使用31 - nshiftAmount避免了这一点。 With knowledge that n is 0..31, there is none shiftAmount of 32. Fine.知道 n 是 0..31,就没有 32 的 shiftAmount。很好。

But, decreasing on one side means increasing on the other.但是,一方面减少意味着另一方面增加。 The mask now need to start at -2 (we do not shift 0b1111 , we shift 0b1110 ): 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; }

And it works!它有效!

Alternate code and analysis替代代码和分析

Fingolfins code (fixed) Finolfins 代码(固定)

The upper code, as Assembler (GCC 9.2, -O3):上层代码,作为汇编程序(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 instructions 9 说明

harolds code哈罗德密码

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 instructions 8条指令

Can we do better?我们能做得更好吗?

Another solutions另一种解决方案

Instead of a left shift, we could do a right shift of 0b1000 , shift it back by one and inverse.我们可以将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 instruction 7 指令

Better way?更好的方法?

Let's shift 0b0111 to the right, shift it one back to left and add 1. So we spare us the inverse:让我们将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 instructions left.还剩 6 条指令。 Fine.美好的。 (But a simple cast is still the best solution in practice) (但简单的演员表仍然是实践中的最佳解决方案)

Cheesy answer that clearly violates the spirit of the question: casting 1 isn't the only way to do type conversion in C or C++明显违反问题精神的俗气答案:强制转换1不是在 C 或 C++ 中进行类型转换的唯一方法

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

( Godbolt for x86-64 GCC ). 用于 x86-64 GCC 的 Godbolt )。 Note that it also relies on implementation-defined behaviour for n==0 with negative x , because that leaves the sign bit set.请注意,它也依赖于n==0与负x实现定义的行为,因为这会留下符号位设置。 But on normal 2's complement implementations the conversion between unsigned and int simply preserves the bit-pattern, as well as the value of positive ints.但是在正常的 2 的补码实现中,unsigned 和 int 之间的转换只是保留了位模式,以及正整数的值。 Making the type-pun explicit with memcpy instead of using implicit type-conversion could make it more robust.使用memcpy使类型双关语显式而不是使用隐式类型转换可以使其更健壮。

The || operator uses the rules for the usual arithmetic conversions to make both operands the same type.运算符使用通常的算术转换规则使两个操作数具有相同的类型。 We can use this to implicitly convert to unsigned, rules-lawyering around the artificial restriction on casting.我们可以使用它来隐式转换为未签名的,围绕人为限制的规则律师。

0U numeric literal suffix is not a cast, it's just plain language syntax for an unsigned constant. 0U数字文字后缀不是0U ,它只是无符号常量的简单语言语法。 An alternative is x & UINT_MAX or x | (~UINT_MAX)另一种选择是x & UINT_MAXx | (~UINT_MAX) x | (~UINT_MAX) . x | (~UINT_MAX) Or even more fun, if you know the width of int and it's less than or equal to unsigned , you can write a constant like 0xffffffff that's too large for a positive signed int , and it will have type unsigned if it fits in that, or long or long long or even the unsigned versions of those if it's larger.或者更有趣的是,如果你知道int的宽度并且它小于或等于unsigned ,你可以写一个像0xffffffff这样的常量,它对于正有符号的int来说太大了,如果它适合它,它将具有unsigned类型,或者longlong long甚至那些更大的无符号版本。

Of course, unsigned tmp = x;当然, unsigned tmp = x; is also not a cast, so really just do that instead!也不是演员表,所以真的只是这样做! But this is even more clearly just a silly rules-lawyering to find a loophole in question, not what was intended.但这更明显只是一种愚蠢的规则-律师寻找问题的漏洞,而不是意图。


Assumptions:假设:

  • This might only work for 2's complement, where conversion from signed to unsigned doesn't modify the bit-pattern of the object representation.这可能仅适用于 2 的补码,其中从有符号到无符号的转换不会修改对象表示的位模式。 (Related: How can an (int) be converted to (unsigned int) while preserving the original bit pattern? - conversion preserves values, not bit-patterns, before applying the modulo-reduction to unsigned.) (相关: 如何在保留原始位模式的同时将 (int) 转换为 (unsigned int)? - 在将模归约应用于无符号之前,转换会保留值,而不是位模式。)

  • unsigned int is the same width as int , so we don't lose any bits, and don't sign-extend to introduce high 1 bits before a logical right shift.** unsigned int是相同的宽度, int ,所以我们不丢失任何位,并且不符号扩展以引入高1的逻辑右移之前的位。**

eg (x | 0ULL) >> n doesn't work;例如(x | 0ULL) >> n不起作用; that would sign -extend x to 64-bit (or whatever) before a logical right shift, so the low int -width of the result would effectively have copies of the sign bit shifted in. (Assuming 2's complement.)这将在逻辑右移之前将x符号扩展为 64 位(或其他),因此结果的低int宽度将有效地将符号位的副本移入。(假设为 2 的补码。)

Workaround: use an unsigned bitfield with the exact number of non-padding bits as int .解决方法:使用非填充位的确切数量为int的无符号位域。 Signed integer types have numeric_limits<T>::digits non-sign bits, and 1 sign bit.有符号整数类型有numeric_limits<T>::digits非符号位和 1 个符号位。 (And unknown amounts of padding). (以及未知数量的填充)。 This will require assignment, not just using it with |这将需要赋值,而不仅仅是将它与|一起使用| . .

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

If you want to memcpy into this struct instead of assign (eg to make sure the bit-pattern doesn't change before right shifting, on non-2's-complement systems?) you could pad with something like char pad[sizeof(int)];如果你想memcpy进入这个结构而不是分配(例如,确保位模式在右移之前不会改变,在非 2 的补码系统上?)你可以用类似char pad[sizeof(int)]; . . That's definitely overkill, more padding than necessary to make it at least as large as int (which is required for memcpy(&struct_tmp, &x, sizeof(x)) or the other direction.).这绝对是矫枉过正,比必要的填充更多,使其至少与int一样大(这是memcpy(&struct_tmp, &x, sizeof(x))或其他方向所必需的。)。 I'm not sure if the struct is guaranteed to be at least as wide as an unsigned long because we used that as the base type for the bitfield, I'd have to double-check that part of the standard, too.我不确定该结构是否保证至少与unsigned long一样宽,因为我们使用它作为位域的基本类型,我也必须仔细检查标准的那部分。 It is the case for GCC and MSVC, though;不过,GCC 和 MSVC 就是这种情况。 unsigned long long brings its sizeof up to 8. IIRC, unsigned long is guaranteed to be at least as wide as int . unsigned long long使其 sizeof 达到 8。IIRC, unsigned long保证至少与int一样宽。

If we wanted to calculate the exact amount of padding needed for a char[] array, we'd have to deal with the possibility of sizeof(int) - sizeof(unsigned) being negative.如果我们想计算char[]数组所需的确切填充量,我们必须处理sizeof(int) - sizeof(unsigned)为负的可能性。 But calculating the number of extra bits for another padding bitfield member could work, calculating padding as CHAR_BIT * sizeof(int) - (1 + digits)但是计算另一个填充位域成员的额外位数是可行的,将填充计算为CHAR_BIT * sizeof(int) - (1 + digits)

In C99 we could use union type punning to replace memcpy, but we still probably need a bitfield to make sure we truncate an unsigned value to the right width exactly.在 C99 中,我们可以使用联合类型双关来替换 memcpy,但我们可能仍然需要一个位域来确保我们将无符号值准确地截断为正确的宽度。


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

2: An explicit type conversion can be expressed using functional notation, a type conversion operator (dynamic_cast, static_cast, reinterpret_cast, const_cast), or the cast notation. 2:显式类型转换可以使用函数表示法、类型转换运算符(dynamic_cast、static_cast、reinterpret_cast、const_cast)或强制转换表示法来表达。

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

The name of this whole section is [expr.cast] , so it's reasonable to treat any of these syntaxes for type conversion as casts, not just a "cast expression".整个部分的名称是[expr.cast] ,因此将这些类型转换的任何语法视为强制转换是合理的,而不仅仅是“强制转换表达式”。 Fortunately there's no need to try to justify unsigned(x) >> n not including a cast.幸运的是,没有必要尝试证明unsigned(x) >> n不包括强制转换。

I know I'm a bit late here, but I think the best method would be to do something like this:我知道我在这里有点晚了,但我认为最好的方法是做这样的事情:

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

The mask sets the uppermost n bits to 0.掩码将最高n位设置为 0。

Of course this can alternatively be written as:当然,这也可以写成:

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

But the compiler will probably understand how to optimize the first one.但是编译器可能会理解如何优化第一个。

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

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