首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C >> 關於C

C 語言移位運算完整指南:邏輯與算術右移、循環移位實作

     編輯:關於C

移位運算看似簡單,卻是 C/C++ 面試與嵌入式開發的高頻考點。本文系統整理有號/無號左移右移的語義差異、標準規定與實作細節,並提供循環移位的標準巨集實作。

核心規則速查表

運算無號整數有號整數 (非負)有號整數 (負數)
左移 <<補 0,等同 ×2ⁿ補 0,等同 ×2ⁿ補 0,行為未定義 (UB)*
右移 >>邏輯右移,補 0多為算術右移,補符號位算術右移,補 1 (保持負號)

* C 標準規定:負數左移屬於未定義行為 (UB),正數左移若溢位亦為 UB。

實戰演練:三組經典測試案例

/* 案例 1 */ unsigned char x = 3;  // 0000 0011
x << 1  →  0000 0110  =  6
x >> 1  →  0000 0001  =  1  (邏輯右移,補 0)

/* 案例 2 */ signed char x = 3;   // 0000 0011
x << 1  →  0000 0110  =  6
x >> 1  →  0000 0001  =  1  (多數平台算術右移,補 0)

/* 案例 3 */ signed char x = -3;  // 1111 1101 (二補數)
x << 1  →  1111 1010  = -6  (補 0,但屬 UB 風險)
x >> 1  →  1111 1110  = -2  (算術右移,補符號位 1)

關鍵標準條文解讀

左移
無論有號無號,低位皆補 0。有號正數左移導致符號位改變 = UB;負數左移 = UB。
無號右移
標準明確規定為邏輯右移 (Logical Right Shift),高位補 0。行為完全可攜。
有號右移
C89/C99/C11/C17 未強制規定為算術或邏輯右移,視實作而定 (Implementation-defined)。絕大多數現代編譯器 (GCC, Clang, MSVC, ICC) 採用算術右移,但標準不保證,跨平台程式碼不應依賴此行為。

記憶體位元模式檢視工具

除錯時常需查看變數實際位元模式,以下可移植函式可列印任意型別的原始位元組:

#include <stdio.h>
#include <stdint.h>

void print_bytes(const void *ptr, size_t size)
{
    const unsigned char *p = (const unsigned char *)ptr;
    for (size_t i = 0; i < size; ++i) {
        printf("%02X ", p[i]);
    }
    putchar('\n');
}

/* 使用範例 */
int main(void)
{
    int32_t val = -3;
    print_bytes(&val, sizeof(val));  // 輸出小端序:FD FF FF FF
    return 0;
}

循環移位:標準巨集實作 (C99 相容)

C 語言無內建循環移位運算子,但可用位運算組合實現,現代編譯器 (GCC/Clang/MSVC) 能識別此模式並優化為單條 CPU 指令 (ROL/ROR):

#include <stdint.h>>
#include <limits.h>>

/* 編譯期檢查型別寬度 */
#define ROTL(x, n) _Generic((x), \
    uint8_t:  rotl8,  uint16_t: rotl16, \
    uint32_t: rotl32, uint64_t: rotl64 \
)(x, n)

#define ROTR(x, n) _Generic((x), \
    uint8_t:  rotr8,  uint16_t: rotr16, \
    uint32_t: rotr32, uint64_t: rotr64 \
)(x, n)

/* 具體實作:掩碼避免 UB (位移量 ≥ 位寬屬 UB) */
static inline uint8_t  rotl8(uint8_t  x, unsigned n) { n &= 7;  return (x << n) | (x >> (8 - n)); }
static inline uint16_t rotl16(uint16_t x, unsigned n) { n &= 15; return (x << n) | (x >> (16 - n)); }
static inline uint32_t rotl32(uint32_t x, unsigned n) { n &= 31; return (x << n) | (x >> (32 - n)); }
static inline uint64_t rotl64(uint64_t x, unsigned n) { n &= 63; return (x << n) | (x >> (64 - n)); }

static inline uint8_t  rotr8(uint8_t  x, unsigned n) { n &= 7;  return (x >> n) | (x << (8 - n)); }
static inline uint16_t rotr16(uint16_t x, unsigned n) { n &= 15; return (x >> n) | (x << (16 - n)); }
static inline uint32_t rotr32(uint32_t x, unsigned n) { n &= 31; return (x >> n) | (x << (32 - n)); }
static inline uint64_t rotr64(uint64_t x, unsigned n) { n &= 63; return (x >> n) | (x << (64 - n)); }

手動推導範例:0x45 (0100 0101) 循環左移 2 位

  1. 高 2 位 01 移至低位 → 01
  2. 剩餘 6 位 000101 左移 2 位 → 00010100
  3. 合併 → 00010101 = 0x15
uint8_t a = 0x45;
uint8_t b = a >> (8 - 2);  // 取高 2 位 → 0x01
uint8_t c = a << 2;        // 低 6 位左移 → 0x14
a = c | b;                 // 0x14 | 0x01 = 0x15 ✓

工程避坑清單

  • ✅ 優先使用 uintN_t 等定寬無號型別進行位運算
  • ✅ 位移量以 & (width-1) 掩碼,防止 ≥ 位寬導致 UB
  • ✅ 循環移位使用內建函式 (__builtin_rotateleft / _rotl) 或經驗證的巨集
  • ❌ 避免對有號負數左移
  • ❌ 避免依賴有號右移為算術右移 (若需可攜性,改用無號型別或手動實作)
  • ❌ 切勿對浮點數直接位移

一言蔽之:移位運算的本質是「位元模式重組」。搞懂二補數表示、UB 邊界、編譯器實作差異,才能寫出正確、可攜、高效的位運算程式碼。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved