解码C语言宏
预处理概述
基本概念
预处理是C语言编译过程的第一步,所有以#开头的指令都由预处理器处理,这些指令不属于C语言语法本身。
预处理指令类型
- 头文件包含:
#include - 宏定义:
#define - 宏取消:
#undef - 条件编译:
#if,#ifdef,#ifndef,#else,#elif,#endif - 错误提示:
#error - 行号控制:
#line - 编译器指令:
#pragma
编译过程
-E -s -c
源代码 (.c) → 预处理(.i) → 编译(.s) → 汇编(.o) → 链接 → 可执行文件
预处理操作命令
gcc example.c -o example.i -E# 只进行预处理,生成.i文件
宏的基本概念
什么是宏?
宏是一段特定的文本字符串,在预处理阶段会被替换为指定的表达式或值。
无参宏定义
#define PI 3.14#define SCREEN_SIZE 800*480*4#define NULL ((void *)0)
无参宏使用示例
#include <stdio.h>
#include <sys/mman.h>
#define PI 3.1415926
#define SCREEN_SIZE 800*480*4
#define WELCOME_MSG "Hello, World!"
int main() {
printf("圆周率: %.6f\n", PI);// 替换为 printf("圆周率: %.6f\n", 3.1415926);
// 系统调用中使用宏
void* addr = mmap(NULL, SCREEN_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
printf("%s\n", WELCOME_MSG);
return 0;
}
预处理后的代码效果
// 预处理后,宏被直接替换
int main() {
printf("圆周率: %.6f\n", 3.1415926);
void* addr = mmap(((void *)0), 800*480*4, 0x1|0x2, 0x01, fd, 0);
printf("%s\n", "Hello, World!");
return 0;
}
带参宏
带参宏定义
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define SQUARE(x) ((x) * (x))#define ABS(x) (((x) >= 0) ? (x) : (-(x)))
带参宏使用示例
#include <stdio.h>
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define SQUARE(x) ((x) * (x))
int main() {
int x = 10, y = 20;
printf("最大值: %d\n", MAX(x, y));// 替换为: ((x) > (y) ? (x) : (y))
printf("最小值: %d\n", MIN(x, y));// 替换为: ((x) < (y) ? (x) : (y))
printf("平方值: %d\n", SQUARE(x));// 替换为: ((x) * (x))
return 0;
}
宏的优缺点
优点
- 可读性强:使用有意义的名称代替魔术数字
- 易于修改:只需修改宏定义即可影响所有使用的地方
- 高效执行:直接文本替换,无函数调用开销
- 类型无关:可用于各种数据类型
缺点
- 调试困难:调试器看到的是替换后的代码
- 代码膨胀:在每个使用处都会展开,增加代码量
- 副作用风险:参数可能被多次求值
宏的副作用与解决方案
问题示例:多次求值
#define SQUARE(x) (x * x)// 错误写法!
int main() {
int a = 5;
printf("%d\n", SQUARE(a++));// 展开为: (a++ * a++)
// 结果不可预测,可能输出25或30等
return 0;
}
正确写法:充分使用括号
#define SQUARE(x) ((x) * (x))
// 正确:每个参数和整个表达式都用括号括起来
// 但仍然有多次求值的问题,对于a++这样的参数仍然不安全
更安全的做法:避免副作用参数
// 对于可能产生副作用的参数,最好先求值再传递
int main() {
int a = 5;
int temp = a++;
printf("%d\n", SQUARE(temp));// 安全的使用方式
return 0;
}
实用宏技巧
多语句宏
#define SWAP(a, b) do { \
typeof(a) _temp = (a); \
(a) = (b); \
(b) = _temp; \
} while (0)// 使用do-while(0)结构可以确保宏在使用时像单个语句一样工作
字符串化操作符
#define STRINGIFY(x) #x //字符串化操作符 (#) :将宏参数转换为字符串常量
#define TO_STRING(x) STRINGIFY(x)
int main() {
printf("变量名: %s\n", STRINGIFY(MAX_VALUE));
// 输出: MAX_VALUE
printf("行号: %s\n", TO_STRING(__LINE__));// 输出: 15
return 0;
}
连接操作符
#define CONCAT(a, b) a##b //连接操作符 (##):将两个标记连接成一个新的标记
int main() {
int xy = 100;
printf("%d\n", CONCAT(x, y));// 输出: 100return 0;
}
系统预定义宏
#include <stdio.h>int main() {
printf("当前文件名: %s\n", __FILE__);
printf("当前行号: %d\n", __LINE__);
printf("编译日期: %s\n", __DATE__);
printf("编译时间: %s\n", __TIME__);
printf("是否遵循C标准: %d\n", __STDC__);
#ifdef __linux__printf("这是在Linux系统上编译\n");
#endif
return 0;
}
最佳实践建议
- 宏名全大写:区分于变量和函数
- 充分使用括号:每个参数和整个表达式都要括起来
- 避免副作用参数:不要传递类似
a++的参数 - 多语句使用do-while:确保宏行为像单个语句
- 注释宏的作用:说明宏的功能和注意事项
- 优先考虑函数:除非有性能要求,否则优先使用函数
调试宏技巧
查看预处理结果
gcc -E program.c -o program.i# 生成预处理后的文件
使用#error检查配置
#ifndef CONFIG_VALUE#error "CONFIG_VALUE must be defined!"#endif
条件编译
条件编译 是一种通过预处理指令 动态控制代码参与编译 的技术,常用于 跨平台适配、调试开关、功能模块化 等场景。
条件编译指令(基础指令)
| 指令 | 作用 |
|---|---|
#if |
判断条件是否为真(支持表达式) |
#ifdef |
判断宏是否已定义(等价于 #if defined) |
#ifndef |
判断宏是否未定义 |
#elif |
前一个条件不满足时判断新条件 |
#else |
所有条件均不满足时执行的代码块 |
#endif |
结束条件编译块 |
语法示例
#if defined(PLATFORM_WIN) && (VERSION > 2020)
// Windows 平台且版本大于 2020 的代码
#elif defined(PLATFORM_LINUX)
// Linux 平台的代码
#else
// 其他情况
#endif
解码C语言宏的更多相关文章
- C语言 宏/macor/#define/
C语言 宏/macor/#define 高级技巧 1.在进行调试的时候,需要进行打印/PRINT,可以通过define进行自定义.例如,自己最常用的DEBUG_PRINT() #define DEBU ...
- C语言宏的高级应用
原文:C语言宏的高级应用 关于#和##在C语言的宏中,#的功能是将其后面的宏参数进行字符串化操作(Stringfication),简单说就是在对它所引用的宏变量通过替换后在其左右各加上一个双引号.比如 ...
- C 语言宏定义
C 语言宏定义1.例子如下: #define PRINT_STR(s) printf("%s",s.c_str()) string str = "abcd"; ...
- 将C语言宏定义数值转换成字符串!
将C语言宏定义转换成字符串! 摘自:https://blog.csdn.net/happen23/article/details/50602667 2016年01月28日 19:15:47 六个九十度 ...
- C语言宏应用-------#define STR(X) #X
C语言宏应用-------#define STR(X) #X #:会把参数转换为字符串 #define STR(x) #x #define MAX 100 STR(MAX) 会被扩展成" ...
- C语言 宏定义之可变参数
可变参数宏定义 C99编译器标准允许你可以定义可变参数宏(variadic macros),这样你就可以使用拥有可以变化的参数表的宏.可变参数宏就像下面这个样子: #define dbgprint(. ...
- C语言宏的使用
使用条件宏进行条件编译 譬如,对于同一份代码,我想编译出两个不同的版本,在其中一个版本中去掉某一部分功能, 这时可以通过条件宏判断是否编译,例: 如果不使用条件宏进行控制,想编译两个不同版本的程序,就 ...
- C语言宏定义时#(井号)和##(双井号)的用法1
#在英语里面叫做 pound 在C语言的宏定义中,一个#表示字符串化:两个#代表concatenate 举例如下: #include <iostream> void quit_comman ...
- C语言宏定义时#(井号)和##(双井号)的用法
C语言中如何使用宏C(和C++)中的宏(Macro)属于编译器预处理的范畴,属于编译期概念(而非运行期概念).下面对常遇到的宏的使用问题做了简单总结. 关于#和## 在C语言的宏中,#的功能是将其后面 ...
- c语言宏定义
一. #define是C语言中提供的宏定义命令,其主要目的是为程序员在编程时提供一定的方便,并能在一定程度上提高程序的运行效率,但学生在学习时往往不能理解该命令的本质,总是在此处产生一些困惑,在编程时 ...
随机推荐
- java--对象的输入输出流、字节流、序列化与反序列化
SequenceInputStream(序列流) 合并 //把三个文件合并成一个文件 public static void merge3() throws IOException{ //找到目标文件 ...
- 最新VMware Workstation虚拟机下载并安装
[2025年]最新VMware Workstation虚拟机下载并安装 1.最新VMware Workstation下载地址 2024年5月5日之后,VMware workstation不能直接在vm ...
- 卸载vivo或iqoo或其它手机的预装软件
前言 众说周知,现在安卓手机做的越来越闭源,(除了一加和小米以及红蓝厂的部分型号 大部分)根本无法root. 那就意味着 手机上一些预装的软件 根本无法卸载 比如:阅读.xx官网.自带的视频和音乐软件 ...
- jenkins部署到另一台服务器
安装插件 搜索安装插件:publish over ssh 配置插件 系统管>SSH Servers 前端部署到另一台服务器 其实前端就是将编译后的代码传送至目标服务器的nginx的html目录下 ...
- CF1227G Not Same 题解
CF1227G Not Same 构造.考虑按照每个数字进行考虑,每次填充一列. 观察样例 \(1\),不难发现可以构造使每一行或列一定有一个位置为 \(0\).我们不妨对于每一列限定这个 \(0\) ...
- Codeforces Round #619 (Div. 2) ABC 题解
A. Three Strings 题意:每次可以把c[i]拿去和a[i]或b[i]交换. 问你能否把ab变成相等. 思路:在ab不相等的时候看看c能不能与一方相等来中和.不能的话就不行. view c ...
- ETL与ELT核心技术解析:如何选择最优数据集成方案
在数字化转型浪潮中,数据集成作为企业数据战略的核心环节,ETL与ELT两种技术路径的抉择直接影响着数据处理效率.本文将通过谷云科技在数据集成领域的实践经验,深入解析两种模式的本质差异与应用场景. 技术 ...
- 理解 Streamlit 的客户端-服务器架构
Streamlit 应用程序具有客户端-服务器结构. 您应用程序的 Python 后端即为服务器.您通过浏览器看到的前端即为客户端. 当您在本地开发应用程序时,您的计算机同时运行服务器和客户端.如果有 ...
- [转载]Quantum Logic and Probability Theory
Origin: https://plato.stanford.edu/entries/qt-quantlog/ Quantum Logic and Probability Theory First p ...
- POLIR-Society-Organization-Communication: 交流: 组织/社会化的沟通: 首先确定对方姓名+Role/身份+判断其目的、立场和认知
要用适合沟通对方的Role/身份,容易理解和舒服的方式,达成我们的目标 POLIR-Society-Organization-Communication: 交流: 组织/社会化的沟通: 首先确定对方 ...