C语言位操作完全指南

位操作是 C 语言的核心技能之一,能够高效地处理底层数据、进行权限控制、实现位图数据结构等。本文详细介绍位运算符、位域、位掩码与位图操作的实战技巧。

位运算符基础

C 语言提供了六种位运算符,用于直接操作二进制位:

bit_operators.c
// 位运算符示例
#include 

void printBinary(unsigned int n) {
    for (int i = 31; i >= 0; i--) {
        printf("%d", (n >> i) & 1);
        if (i % 8 == 0) printf(" ");
    }
    printf("\n");
}

int main() {
    unsigned int a = 0b11010110;  // 214
    unsigned int b = 0b00101101;  // 45

    printf("a = "); printBinary(a);
    printf("b = "); printBinary(b);

    // 按位与
    printf("a & b = "); printBinary(a & b);

    // 按位或
    printf("a | b = "); printBinary(a | b);

    // 按位异或
    printf("a ^ b = "); printBinary(a ^ b);

    // 按位取反
    printf("~a = "); printBinary(~a);

    // 左移
    printf("a << 2 = "); printBinary(a << 2);

    // 右移
    printf("a >> 2 = "); printBinary(a >> 2);

    return 0;
}

位掩码操作

位掩码是操作特定位的核心技术,广泛应用于标志位、权限控制等场景:

bitmask.c
// 位掩码操作
#include 
#include 

// 定义标志位掩码
#define FLAG_READ   (1 << 0)  // 0x01
#define FLAG_WRITE  (1 << 1)  // 0x02
#define FLAG_EXEC   (1 << 2)  // 0x04
#define FLAG_SYNC   (1 << 3)  // 0x08
#define FLAG_CACHE  (1 << 4)  // 0x10

// 设置标志位
void setFlag(uint32_t *flags, uint32_t flag) {
    *flags |= flag;
}

// 清除标志位
void clearFlag(uint32_t *flags, uint32_t flag) {
    *flags &= ~flag;
}

// 切换标志位
void toggleFlag(uint32_t *flags, uint32_t flag) {
    *flags ^= flag;
}

// 检查标志位
int checkFlag(uint32_t flags, uint32_t flag) {
    return (flags & flag) != 0;
}

// 获取多位域的值
uint32_t getBitField(uint32_t value, int start, int bits) {
    return (value >> start) & ((1 << bits) - 1);
}

// 设置多位域的值
void setBitField(uint32_t *value, uint32_t newValue, int start, int bits) {
    uint32_t mask = ((1 << bits) - 1);
    *value = (*value & ~(mask << start)) | ((newValue & mask) << start);
}

int main() {
    uint32_t flags = 0;

    // 设置权限标志
    setFlag(&flags, FLAG_READ | FLAG_WRITE);
    printf("设置读写标志: 0x%08X\n", flags);

    // 检查标志
    printf("可读: %s\n", checkFlag(flags, FLAG_READ) ? "是" : "否");
    printf("可执行: %s\n", checkFlag(flags, FLAG_EXEC) ? "是" : "否");

    // 切换标志
    toggleFlag(&flags, FLAG_EXEC);
    printf("切换执行标志后: 0x%08X\n", flags);
    printf("可执行: %s\n", checkFlag(flags, FLAG_EXEC) ? "是" : "否");

    // 清除标志
    clearFlag(&flags, FLAG_WRITE);
    printf("清除写标志后: 0x%08X\n", flags);

    // 位域操作示例
    uint32_t packet = 0x12345678;
    printf("\n位域操作示例\n");
    printf("原始值: 0x%08X\n", packet);

    // 获取第4-7位(4位)
    uint32_t field = getBitField(packet, 4, 4);
    printf("第4-7位: 0x%X\n", field);

    // 设置第4-7位为 0xA
    setBitField(&packet, 0xA, 4, 4);
    printf("设置后: 0x%08X\n", packet);

    return 0;
}

位域结构体

位域允许在结构体中定义占用特定位数成员:

bitfield.c
// 位域结构体
#include 
#include 

// TCP 头部(简化版)
struct tcp_header {
    uint16_t src_port;      // 16位源端口
    uint16_t dst_port;      // 16位目的端口
    uint32_t seq_num;       // 32位序列号
    uint32_t ack_num;       // 32位确认号
    uint8_t data_offset;    // 4位数据偏移
    uint8_t flags;          // 8位标志
    uint16_t window;        // 16位窗口大小
    uint16_t checksum;      // 16位校验和
    uint16_t urgent;        // 16位紧急指针
};

// 使用位域定义 IP 头部
struct ip_header_bitfield {
    uint8_t version: 4;      // 4位版本
    uint8_t ihl: 4;           // 4位头长度
    uint8_t tos;             // 8位服务类型
    uint16_t total_length;   // 16位总长度
    uint16_t id;             // 16位标识
    uint8_t flags: 3;        // 3位标志
    uint16_t fragment_offset: 13; // 13位片偏移
    uint8_t ttl;             // 8位生存时间
    uint8_t protocol;        // 8位协议
    uint16_t checksum;       // 16位头校验和
    uint32_t src_addr;       // 32位源地址
    uint32_t dst_addr;       // 32位目的地址
};

// 使用联合体访问位域或原始字节
union IPHeader {
    struct ip_header_bitfield bits;
    uint8_t bytes[20];
};

void printIPHeader(struct ip_header_bitfield *ip) {
    printf("Version: %d\n", ip->version);
    printf("Header Length: %d words (%d bytes)\n", ip->ihl, ip->ihl * 4);
    printf("Type of Service: 0x%02X\n", ip->tos);
    printf("Total Length: %d bytes\n", ip->total_length);
    printf("ID: 0x%04X\n", ip->id);
    printf("Flags: 0x%X\n", ip->flags);
    printf("Fragment Offset: %d\n", ip->fragment_offset);
    printf("TTL: %d\n", ip->ttl);
    printf("Protocol: %d\n", ip->protocol);
}

int main() {
    // 创建 IP 头
    struct ip_header_bitfield ip = {
        .version = 4,
        .ihl = 5,
        .tos = 0,
        .total_length = 60,
        .id = 0x1234,
        .flags = 0,
        .fragment_offset = 0,
        .ttl = 64,
        .protocol = 6,
        .checksum = 0,
    };

    printIPHeader(&ip);

    // 通过联合体访问原始字节
    printf("\n原始字节:\n");
    union IPHeader header;
    header.bits = ip;
    for (int i = 0; i < 20; i++) {
        printf("%02X ", header.bytes[i]);
        if ((i + 1) % 4 == 0) printf("\n");
    }

    return 0;
}

位图数据结构

位图(Bitmap)是一种高效的稀疏数据结构,特别适合需要快速查找和去重的场景:

bitmap.c
// 位图实现
#include 
#include 
#include 
#include 

#define BITS_PER_WORD  32
#define WORD_INDEX(n) ((n) / BITS_PER_WORD)
#define BIT_OFFSET(n)  ((n) % BITS_PER_WORD)

typedef struct {
    uint32_t *bits;
    size_t size;
} Bitmap;

// 创建位图
Bitmap *bitmapCreate(size_t maxBits) {
    Bitmap *bm = (Bitmap *)malloc(sizeof(Bitmap));
    if (!bm) return NULL;

    bm->size = (WORD_INDEX(maxBits) + 1);
    bm->bits = (uint32_t *)calloc(bm->size, sizeof(uint32_t));

    if (!bm->bits) {
        free(bm);
        return NULL;
    }

    return bm;
}

// 释放位图
void bitmapFree(Bitmap *bm) {
    if (bm) {
        free(bm->bits);
        free(bm);
    }
}

// 设置位
void bitmapSet(Bitmap *bm, size_t pos) {
    if (pos >= bm->size * BITS_PER_WORD) return;
    bm->bits[WORD_INDEX(pos)] |= (1U << BIT_OFFSET(pos));
}

// 清除位
void bitmapClear(Bitmap *bm, size_t pos) {
    if (pos >= bm->size * BITS_PER_WORD) return;
    bm->bits[WORD_INDEX(pos)] &= ~(1U << BIT_OFFSET(pos));
}

// 测试位
int bitmapTest(Bitmap *bm, size_t pos) {
    if (pos >= bm->size * BITS_PER_WORD) return 0;
    return (bm->bits[WORD_INDEX(pos)] & (1U << BIT_OFFSET(pos))) != 0;
}

// 查找第一个未设置的位
int bitmapFindFree(Bitmap *bm, size_t *pos) {
    for (size_t i = 0; i < bm->size; i++) {
        if (bm->bits[i] != 0xFFFFFFFF) {
            // 找到有空闲位的字
            for (uint32_t j = 0; j < BITS_PER_WORD; j++) {
                if (!(bm->bits[i] & (1U << j))) {
                    *pos = i * BITS_PER_WORD + j;
                    return 0;
                }
            }
        }
    }
    return -1;
}

// 统计设置的位数
size_t bitmapCount(Bitmap *bm) {
    size_t count = 0;
    for (size_t i = 0; i < bm->size; i++) {
        uint32_t word = bm->bits[i];
        // 使用 Brian Kernighan 算法
        while (word) {
            word &= (word - 1);
            count++;
        }
    }
    return count;
}

int main() {
    // 创建能存储 1000 个状态的位图
    Bitmap *bm = bitmapCreate(1000);
    if (!bm) {
        printf("位图创建失败\n");
        return 1;
    }

    // 设置一些位
    bitmapSet(bm, 5);
    bitmapSet(bm, 10);
    bitmapSet(bm, 100);
    bitmapSet(bm, 500);

    printf("已设置 %zu 个位置\n", bitmapCount(bm));

    printf("位置 5 已设置: %s\n", bitmapTest(bm, 5) ? "是" : "否");
    printf("位置 6 已设置: %s\n", bitmapTest(bm, 6) ? "是" : "否");

    // 查找第一个空闲位置
    size_t freePos;
    if (bitmapFindFree(bm, &freePos) == 0) {
        printf("第一个空闲位置: %zu\n", freePos);
    }

    // 清除位 5
    bitmapClear(bm, 5);
    printf("清除位置 5 后,已设置 %zu 个位置\n", bitmapCount(bm));

    bitmapFree(bm);
    return 0;
}

位操作技巧

一些常用的位操作技巧:

bit_tricks.c
// 位操作技巧
#include 
#include 

// 判断是否为2的幂
int isPowerOfTwo(uint32_t n) {
    return n != 0 && (n & (n - 1)) == 0;
}

// 获取最高的设置位
int highestBit(uint32_t n) {
    int pos = -1;
    while (n > 0) {
        n >>= 1;
        pos++;
    }
    return pos;
}

// 计算 2 的幂的指数
int log2(uint32_t n) {
    int pos = 0;
    while (n > 1) {
        n >>= 1;
        pos++;
    }
    return pos;
}

// 四舍五入到最接近的2的幂
uint32_t roundToPowerOfTwo(uint32_t n) {
    n--;
    n |= n >> 1;
    n |= n >> 2;
    n |= n >> 4;
    n |= n >> 8;
    n |= n >> 16;
    n++;
    return n;
}

// 交换两个数(不使用临时变量)
void swap(int *a, int *b) {
    *a = *a ^ *b;
    *b = *a ^ *b;
    *a = *a ^ *b;
}

// 判断符号是否相同
int sameSign(int a, int b) {
    return (a ^ b) >= 0;
}

// 计算绝对值(无分支)
int abs(int n) {
    int mask = n >> (sizeof(int) * 8 - 1);
    return (n + mask) ^ mask;
}

// Brian Kernighan 算法:统计设置位数
int countBits(uint32_t n) {
    int count = 0;
    while (n) {
        n &= (n - 1);
        count++;
    }
    return count;
}

// 检查第 n 位是否设置
int checkBit(uint32_t n, int bit) {
    return (n & (1 << bit)) != 0;
}

// 设置第 n 位
uint32_t setBit(uint32_t n, int bit) {
    return n | (1 << bit);
}

// 清除第 n 位
uint32_t clearBit(uint32_t n, int bit) {
    return n & ~(1 << bit);
}

int main() {
    printf("isPowerOfTwo(8): %s\n", isPowerOfTwo(8) ? "true" : "false");
    printf("isPowerOfTwo(10): %s\n", isPowerOfTwo(10) ? "true" : "false");

    printf("highestBit(256): %d\n", highestBit(256));
    printf("log2(256): %d\n", log2(256));

    printf("roundToPowerOfTwo(100): %u\n", roundToPowerOfTwo(100));

    int x = 5, y = 10;
    swap(&x, &y);
    printf("swap(5, 10): x=%d, y=%d\n", x, y);

    printf("countBits(255): %d\n", countBits(255));

    return 0;
}

总结

位操作是 C 语言的强大特性:

  • 位运算符:按位与、或、异或、取反、左移、右移
  • 位掩码:高效设置、清除、切换和检查特定位
  • 位域:在结构体中定义占用特定位数的成员
  • 位图:高效的稀疏数据结构和快速查找实现
  • 位操作技巧:判断2的幂、统计位数、交换变量等

熟练掌握位操作能够编写出更高效、更节省内存的代码。