簡體   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