每日小练——宏的运用

🚀【C语言宏进阶】两个你必须掌握的宏技巧:位交换 + 结构体偏移量计算(带原理详解)

最近在复习 C 语言的宏(Macro)时刷到两个非常有代表性的题目,它们都充分体现了"宏能做编译器本来做不到的事"这一特性,也能让我们对底层内存与位运算有更深刻的理解。

今天我就结合我自己做题的过程,把这两个题目整理成一篇博客,方便自己以后翻阅,也分享给正在学习宏的同学。

本文包含两个经典宏题目:

  1. 使用宏交换整数的奇偶位(二进制位交换)
  2. 实现 OFFSETOF 宏:计算结构体成员相对于结构体首地址的偏移

两段代码你都已经提供,我会在此基础上做进一步讲解,保证你看完就能彻底理解。


📌 题目一:使用宏交换整数的奇数位与偶数位

🔍 题目要求

编写一个宏,完成:
将一个整数的二进制位中的奇数位与偶数位互换

示例:

10(二进制:1010)

交换奇偶位后 → 0101(十进制:5)


🧠 思路分析(重点)

先看 32 位整数的二进制位编号:

复制代码
位: 31 30 29 ... 3 2 1 0
类型:奇 偶 奇 ... 奇 偶 奇 偶

要交换奇数位与偶数位,核心做法是:

  1. 保留奇数位

    用二进制掩码:0101 0101 ... 0101

    十六进制:0x55555555

    复制代码
    x & 0x55555555

    然后把奇数位左移一位 << 1

  2. 保留偶数位

    掩码:1010 1010 ... 1010

    十六进制:0xAAAAAAAA

    复制代码
    x & 0xAAAAAAAA

    然后右移一位 >> 1

  3. 将两部分相加即可完成交换


✅ 宏实现

c 复制代码
#define SWAP(x) (((x & 0x55555555) << 1) + ((x & 0xaaaaaaaa) >> 1))

示例代码(来自你的文件)

c 复制代码
int main() {
    int a = 10;  // 1010b
    a = SWAP(a);
    printf("%d\n", a);  // 输出:5
    return 0;
}

🔚 结果验证

10 → 1010

交换奇偶位 → 0101 = 5

完全正确 ✔


📌 题目二:使用宏计算结构体成员的偏移量(OFFSETOF 原理解释)

这个题更经典!很多人第一次看到 (type*)0 都会懵。


🔍 题目要求

写一个宏 OFFSETOF(type, member)

用于计算结构体中某个成员相对于结构体起始地址(首地址)的偏移量。

这与标准库 offsetof() 功能完全相同。

你的结构体定义如下:

c 复制代码
struct S {
    char c1;
    int i;
    char c2;
};

🧠 核心原理详细讲解(这段很多人都不懂)

我们来看关键语句:

c 复制代码
(size_t)&((type*)0)->member

逐步拆解👇


(type*)0

这是把数字 0 强制转换成指向 struct S 的指针:

复制代码
0x00000000 ------> struct S*

注意:这里没有真正访问地址 0,只是用它来做指针运算。不会产生段错误。


((type*)0)->member

假设结构体从 0 地址开始,那么成员 member 的地址就是它的偏移量。

示例:

复制代码
(struct S*)0 -> c1   地址 = 0 + c1 的偏移量 = 0
(struct S*)0 -> i    地址 = 0 + i 的偏移量 = 4(可能有对齐)
(struct S*)0 -> c2   地址 = 0 + c2 的偏移量 = 8(可能有对齐)

即它返回的是一个"假想的地址",但是这个地址的数字大小就是偏移量!


&((type*)0)->member

对该成员取地址,就得到:

复制代码
结构体成员的偏移量(字节数)

🎯 于是宏可以写成:

c 复制代码
#define OFFSETOF(type, member) (size_t)&((type*)0)->member

🔬 示例代码

c 复制代码
int main() {
    printf("%d\n", OFFSETOF(struct S, c1));
    printf("%d\n", OFFSETOF(struct S, i));
    printf("%d\n", OFFSETOF(struct S, c2));
    return 0;
}

若结构体按默认对齐:

成员 类型 偏移量
c1 char 0
i int 4
c2 char 8

程序输出也会是:

复制代码
0
4
8

📝 总结:宏的威力远超你想象

这两个题展示了宏的两个强大能力:

✔ 1. 进行复杂的位级操作(如奇偶位交换)

宏比函数快,不需要参数压栈,不会产生运行时代码。

✔ 2. 操纵类型、指针与编译期行为(如 OFFSETOF)

这是函数无法完成的工作,因为 offsetof 必须在编译期求值,而非运行期。


🎯 学习宏应该注意什么?

  • 宏不检查类型,容易导致 bug
  • 宏展开可能产生副作用
  • 宏必须加括号
  • 宏适合做 常量小型计算类型运算
  • 不要用宏写复杂逻辑代码

掌握这两个例子,你已经能理解宏的核心用途了。


相关推荐
handler0115 小时前
【C++】二叉搜索树详解及其模拟实现(代码)
开发语言·c++·算法·c··二叉搜索树·搜索树
爱学习的程序媛2 天前
C 语言全景指南:从底层原理到工业级实战
c++·c#·c
dozenyaoyida3 天前
RISC-V嵌入式开发:彻底解决“undefined reference to isatty“错误全攻略
经验分享·c·cmake·嵌入式开发·isatty·没有定义问题
Shadow(⊙o⊙)4 天前
模拟实现:glibc_1.0-文件操作函数fopen fclose fwrite fflush实现。
开发语言·c++·学习·c
liulilittle6 天前
TCP UCP:基于卡尔曼滤波的BBR增强型拥塞控制算法
linux·网络·c++·tcp/ip·算法·c·通讯
weixin_421725267 天前
C语言、C++与C#深度研究报告:从底层控制到现代企业级开发的演进
c语言·c++·c·内存管理·编译模型
不吃土豆的马铃薯9 天前
Spdlog 入门:日志记录器与日志槽基础详解
服务器·开发语言·c++·c·日志·spdlog
金创想9 天前
积木移动题目分析及解题思路——木块问题(1)
c++·算法·字符串·c·刷题·信息学奥赛·积木
不吃土豆的马铃薯12 天前
5.SGI STL 二级空间配置器 _S_chunk_alloc核心函数解析
开发语言·c++·vscode·c·内存池
一只小灿灿13 天前
深度详解计算机补码原理
c·补码