预处理命令
C程序编译流程:源文件 → 预处理(处理宏、头文件) → 编译 → 链接 → 可执行文件。
宏定义
不带参数的宏:
#define PI 3.1415926 // 定义常量
#define DEBUG_MODE // 定义条件编译标志,为条件编译做准备工作
带参数的宏:
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define SQUARE(x) ((x) * (x))
陷阱与解决:参数需加括号,避免运算符优先级错误。
// 错误示例:
#define MUL(a, b) a * b
MUL(1+2, 3) → 1+2*3=7(预期9)
// 正确写法:
#define MUL(a, b) ((a) * (b))
带参数的宏比函数调用效率高,但更消耗内存。函数需要保护现场和恢复现场,宏直接展开,如果使用过多,会导致代码缺乏复用性。
文件包含处理
语法:
#include <stdio.h> // 系统头文件
#include "my_lib.h" // 用户自定义头文件
头文件保护:防止重复包含,避免多次定义。
// my_lib.h
#ifndef MY_LIB_H
#define MY_LIB_H
// 头文件内容
#endif
条件编译
常见指令:
#ifdef DEBUG
printf("调试信息");
#endif
#if CPU_TYPE == ARM
// ARM平台专用代码
#elif CPU_TYPE == X86
// x86平台专用代码
#else
#error "不支持的平台"
#endif
应用场景:跨平台代码适配和调试代码开关。
指针基础
指针变量是一种用于存储内存地址的数据类型。其定义方式为:基类型 * 指针变量名。其中,基类型表示指针所指向的数据类型,*在定义时作为类型说明符,而在运算时则作为指针运算符。
定义指针变量后,可以通过赋值或初始化来为其分配地址。
int *p; // 定义一个指向int类型的指针变量p
int a = 10;
p = &a; // 将变量a的地址赋值给指针p
通过*指针变量名可以间接访问指针所指向的内存地址中的值。
int b = *p; // 将指针p所指向的值赋给变量b
在64位操作系统中,指针变量通常占用8个字节,而在32位操作系统中,指针变量占用4个字节。
指针的使用
指针可以通过被调函数修改主调函数中的变量值。
void modify(int *ptr) {
*ptr = 20; // 修改指针所指向的值
}
int main() {
int x = 10;
modify(&x); // 传递x的地址
printf("%d", x); // 输出20
return 0;
}
和&运算符可以相互抵消。例如,&x等价于x。
指针的注意事项
指针变量和其指向的内存地址必须明确,否则会导致未定义行为,这种指针称为野指针。野指针通常是由于指针未初始化或指向已释放的内存区域造成的。
int *wild_ptr; // 未初始化的指针,野指针
指针的访问过程
指针的访问过程可以分为以下三个步骤:
- 根据指针变量的值去内存中定位。
- 从定位处开始向后偏移sizeof(基类型)个字节。
- 将偏移后的那部分内存空间当作是一个基类型来看。
例如,对于int p,假设p指向地址0x1000,则p会从0x1000开始,读取4个字节(假设int类型为4字节),并将其解释为一个int类型的值。
int *p = (int *)0x1000;
int value = *p; // 从地址0x1000开始读取4个字节,并将其解释为int类型
开发技巧
宏的优缺点
优点:提高代码复用性,减少函数调用开销。 缺点:调试困难,易引发隐蔽错误。
条件编译调试
#define DEBUG 1
#if DEBUG // 调试代码
#endif
头文件设计
仅包含必要的声明(函数原型、宏、extern变量)。 禁止在头文件中定义全局变量(易引发重复定义)。
总结
预处理命令:宏替换、文件包含、条件编译是代码优化的核心工具。 指针本质:通过地址直接操作内存,需谨慎避免野指针。 嵌入式注意:宏定义替代简单函数提升效率,指针操作需严格校验地址合法性。