移位運算看似簡單,卻是 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)
除錯時常需查看變數實際位元模式,以下可移植函式可列印任意型別的原始位元組:
#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;
}
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)); }
01 移至低位 → 01000101 左移 2 位 → 0001010000010101 = 0x15uint8_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 邊界、編譯器實作差異,才能寫出正確、可攜、高效的位運算程式碼。