内联函数与宏
00:00
inline函数与预处理器宏
概述
内联函数(inline function)和预处理器宏(macro)是C语言中两种实现代码复用的机制。宏在预处理阶段进行文本替换,而内联函数在编译阶段由编译器决定是否将函数体直接嵌入调用处。理解两者的区别和适用场景,有助于编写既高效又安全的代码。
基础概念
宏的工作原理
宏由预处理器处理,在编译之前进行纯文本替换。宏没有类型检查,不遵循作用域规则,参数每次使用都会被求值:
#define SQUARE(x) ((x) * (x))
int main(void) {
int a = 5;
int result = SQUARE(a); // 展开为: ((a) * (a))
printf("%d\n", result); // 输出: 25
return 0;
}
内联函数的工作原理
内联函数由编译器处理,编译器可能将函数调用替换为函数体本身,省去函数调用的开销。内联函数有类型检查,遵循作用域规则,参数只求值一次:
static inline int square(int x) {
return x * x;
}
int main(void) {
int a = 5;
int result = square(a); // 编译器可能直接嵌入: a * a
printf("%d\n", result); // 输出: 25
return 0;
}
宏 vs 内联函数对比
| 特性 | 宏 | 内联函数 |
|---|---|---|
| 类型检查 | 无 | 有 |
| 调试 | 困难(预处理后消失) | 容易(有符号信息) |
| 参数求值 | 每次使用都求值 | 只求值一次 |
| 作用域 | 全局(预处理替换) | 遵循作用域规则 |
| 递归 | 不支持 | 支持 |
| 取地址 | 不可以 | 可以 |
| 代码膨胀 | 可能严重 | 编译器控制 |
快速上手
使用内联函数
#include <stdio.h>
// 使用 static inline 定义内联函数
static inline int max(int a, int b) {
return a > b ? a : b;
}
static inline double clamp(double val, double lo, double hi) {
if (val < lo) return lo;
if (val > hi) return hi;
return val;
}
int main(void) {
printf("max(3, 5) = %d\n", max(3, 5)); // 输出: 5
printf("clamp(1.5, 0, 1) = %.1f\n", clamp(1.5, 0, 1)); // 输出: 1.0
return 0;
}
使用宏定义常量和简单操作
#include <stdio.h>
// 宏常量
#define MAX_SIZE 100
#define PI 3.14159265
// 带参数的宏
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define ABS(x) ((x) >= 0 ? (x) : -(x))
int main(void) {
int arr[MAX_SIZE];
printf("MIN(3, 7) = %d\n", MIN(3, 7)); // 输出: 3
printf("ABS(-5) = %d\n", ABS(-5)); // 输出: 5
return 0;
}
详细用法
宏的括号规则
定义宏时必须注意括号的使用,避免优先级问题:
// 错误:缺少括号
#define SQUARE_BAD(x) x * x
int result = SQUARE_BAD(2 + 3); // 展开为: 2 + 3 * 2 + 3 = 11,而非25
// 正确:参数加括号
#define SQUARE(x) ((x) * (x))
int result = SQUARE(2 + 3); // 展开为: ((2 + 3) * (2 + 3)) = 25
// 正确:整个表达式也加括号
#define DOUBLE(x) ((x) + (x))
int result = DOUBLE(3) * 2; // 展开为: ((3) + (3)) * 2 = 12,而非9
宏的副作用问题
宏参数在每次使用时都会被求值,如果参数有副作用(如自增),结果可能不符合预期:
#include <stdio.h>
#define SQUARE(x) ((x) * (x))
int main(void) {
int a = 3;
// 副作用问题:a 被求值两次
int result = SQUARE(a++); // 展开为: ((a++) * (a++))
// 结果是未定义行为!a 可能被自增1次或2次
printf("result = %d, a = %d\n", result, a); // 不可预测
return 0;
}
使用 GCC 语句表达式避免副作用
#include <stdio.h>
// 使用 GCC 扩展的语句表达式,确保参数只求值一次
#define MAX(a, b) ({ \
__typeof__(a) _a = (a); \
__typeof__(b) _b = (b); \
_a > _b ? _a : _b; \
})
int main(void) {
int x = 5, y = 3;
// 安全:x++ 只求值一次
int result = MAX(x++, y);
printf("result = %d, x = %d\n", result, x); // result = 5, x = 6
return 0;
}
inline 关键字的语义
C99 中 inline 关键字的语义比较微妙:
// 头文件中:声明为 inline(不提供定义,仅建议内联)
inline int add(int a, int b);
// 头文件中:使用 static inline(每个编译单元有自己的副本,最常用)
static inline int add(int a, int b) {
return a + b;
}
// 源文件中:extern inline(提供外部定义,不内联)
extern inline int add(int a, int b);
实际开发中,static inline 是最常用、最安全的方式,因为它避免了链接时的多重定义问题。
多行宏的写法
#include <stdio.h>
#include <stdlib.h>
// 使用 do-while(0) 包装多行宏
#define SWAP(a, b, type) do { \
type _temp = (a); \
(a) = (b); \
(b) = _temp; \
} while (0)
// 带日志的分配宏
#define MALLOC(ptr, size) do { \
(ptr) = malloc(size); \
if (!(ptr)) { \
fprintf(stderr, "malloc 失败: %s:%d\n", \
__FILE__, __LINE__); \
exit(1); \
} \
} while (0)
int main(void) {
int x = 10, y = 20;
SWAP(x, y, int);
printf("x = %d, y = %d\n", x, y); // x = 20, y = 10
int *arr;
MALLOC(arr, 100 * sizeof(int));
arr[0] = 42;
printf("arr[0] = %d\n", arr[0]);
free(arr);
return 0;
}
字符串化和标记粘贴
#include <stdio.h>
// # 操作符:将参数转为字符串
#define TO_STRING(x) #x
// ## 操作符:将两个标记粘贴在一起
#define CONCAT(a, b) a##b
#define MAKE_FUNC(name) void func_##name(void) { printf(#name "\n"); }
// 使用示例
MAKE_FUNC(hello) // 生成 void func_hello(void) { printf("hello\n"); }
MAKE_FUNC(world) // 生成 void func_world(void) { printf("world\n"); }
int main(void) {
printf("%s\n", TO_STRING(hello world)); // 输出: hello world
int CONCAT(my, Var) = 42; // 等价于 int myVar = 42;
printf("myVar = %d\n", myVar);
func_hello(); // 输出: hello
func_world(); // 输出: world
return 0;
}
常见场景
场景一:类型泛型的最大值宏
#include <stdio.h>
// 使用 _Generic 实现类型安全的泛型 MAX
#define MAX(a, b) _Generic((a), \
int: max_int, \
long: max_long, \
double: max_double, \
default: max_long \
)((a), (b))
static inline int max_int(int a, int b) { return a > b ? a : b; }
static inline long max_long(long a, long b) { return a > b ? a : b; }
static inline double max_double(double a, double b) { return a > b ? a : b; }
int main(void) {
printf("MAX(3, 5) = %d\n", MAX(3, 5));
printf("MAX(3.14, 2.72) = %f\n", MAX(3.14, 2.72));
printf("MAX(100L, 200L) = %ld\n", MAX(100L, 200L));
return 0;
}
场景二:调试日志宏
#include <stdio.h>
// 调试级别
typedef enum { LOG_DEBUG, LOG_INFO, LOG_WARN, LOG_ERROR } LogLevel;
extern int g_log_level;
// 日志宏:编译时可关闭
#define LOG(level, fmt, ...) do { \
if (level >= g_log_level) { \
const char *level_str[] = {"DEBUG", "INFO", "WARN", "ERROR"}; \
fprintf(stderr, "[%s] %s:%d: " fmt "\n", \
level_str[level], __FILE__, __LINE__, \
##__VA_ARGS__); \
} \
} while (0)
int g_log_level = LOG_DEBUG;
int divide(int a, int b) {
if (b == 0) {
LOG(LOG_ERROR, "除零错误: a=%d, b=%d", a, b);
return 0;
}
LOG(LOG_DEBUG, "计算 %d / %d = %d", a, b, a / b);
return a / b;
}
int main(void) {
divide(10, 3);
divide(10, 0);
return 0;
}
场景三:容器类型宏
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 定义动态数组类型
#define DEFINE_VEC(type) \
typedef struct { \
type *data; \
size_t size; \
size_t capacity; \
} Vec_##type; \
\
static inline Vec_##type vec_##type##_create(void) { \
Vec_##type v = {NULL, 0, 0}; \
return v; \
} \
\
static inline void vec_##type##_push(Vec_##type *v, type val) { \
if (v->size >= v->capacity) { \
v->capacity = v->capacity ? v->capacity * 2 : 4; \
v->data = realloc(v->data, v->capacity * sizeof(type)); \
} \
v->data[v->size++] = val; \
} \
\
static inline type vec_##type##_get(Vec_##type *v, size_t i) { \
return v->data[i]; \
} \
\
static inline void vec_##type##_free(Vec_##type *v) { \
free(v->data); \
v->data = NULL; \
v->size = v->capacity = 0; \
}
// 为 int 类型生成向量
DEFINE_VEC(int)
int main(void) {
Vec_int nums = vec_int_create();
for (int i = 0; i < 10; i++) {
vec_int_push(&nums, i * i);
}
for (size_t i = 0; i < nums.size; i++) {
printf("nums[%zu] = %d\n", i, vec_int_get(&nums, i));
}
vec_int_free(&nums);
return 0;
}
注意事项
宏定义时的常见错误
// 错误1:宏定义末尾加分号
#define MAX(a, b) ((a) > (b) ? (a) : (b)); // 多余的分号!
if (cond)
MAX(x, y); // 展开后有两个分号,else 会匹配错误
// 错误2:多行宏不用 do-while(0)
#define INIT(a, b) a = 0; b = 0; // 在 if 语句中会出问题
if (cond)
INIT(x, y); // 只有 a = 0 在 if 内部
// 正确写法
#define INIT(a, b) do { a = 0; b = 0; } while (0)
inline 不保证内联
inline 只是对编译器的建议,编译器可能忽略它。对于复杂函数(包含循环、递归、switch等),编译器通常不会内联:
// 编译器可能不内联的函数
static inline int complex_func(int x) {
switch (x) { // switch 语句
case 1: return 10;
case 2: return 20;
default: return 0;
}
}
// 使用 __attribute__((always_inline)) 强制内联(GCC/Clang)
__attribute__((always_inline))
static inline int forced_inline(int x) {
return x * 2;
}
宏与作用域
宏不遵循C语言的作用域规则,它在预处理阶段进行文本替换,可能影响后续代码:
// 宏污染作用域
#define SIZE 100
void func(void) {
int SIZE = 10; // 编译错误:宏替换后变成 int 100 = 10;
}
// 解决方案:使用枚举或 const 变量替代宏常量
enum { BUFFER_SIZE = 100 }; // 遵循作用域
static const int MAX_CONN = 100; // 遵循作用域,有类型
进阶用法
X-Macro 模式
#include <stdio.h>
// 定义数据表(单一数据源)
#define ERROR_LIST \
X(NONE, 0, "无错误") \
X(INVALID, -1, "参数无效") \
X(NOMEM, -2, "内存不足") \
X(IO, -3, "I/O错误") \
X(NETWORK, -4, "网络错误")
// 生成枚举
typedef enum {
#define X(name, val, msg) ERR_##name = val,
ERROR_LIST
#undef X
} ErrorCode;
// 生成消息数组
static const char *error_messages[] = {
#define X(name, val, msg) [ERR_##name + 4] = msg,
ERROR_LIST
#undef X
};
// 生成字符串化函数
const char *error_name(ErrorCode e) {
switch (e) {
#define X(name, val, msg) case ERR_##name: return #name;
ERROR_LIST
#undef X
default: return "UNKNOWN";
}
}
int main(void) {
ErrorCode err = ERR_NOMEM;
printf("错误: %s (代码: %d, 消息: %s)\n",
error_name(err), err, error_messages[err + 4]);
return 0;
}
编译时断言宏
#include <stdio.h>
// C11 之前的编译时断言
#define STATIC_ASSERT(cond, msg) \
typedef char static_assert_##msg[(cond) ? 1 : -1]
// C11 及以后
#define COMPILE_ASSERT(cond, msg) _Static_assert(cond, msg)
// 使用示例
STATIC_ASSERT(sizeof(int) == 4, int_must_be_4_bytes);
COMPILE_ASSERT(sizeof(void *) >= 4, pointer_too_small);
int main(void) {
printf("编译时断言通过\n");
return 0;
}
使用宏实现接口/虚函数
#include <stdio.h>
#include <stdlib.h>
// 定义接口宏
#define DEFINE_INTERFACE(name) \
typedef struct name##_VTable name##_VTable; \
typedef struct name name
// 定义虚函数表宏
#define DEFINE_VTABLE(name, ...) \
struct name##_VTable { __VA_ARGS__ }
// 定义对象宏
#define DEFINE_OBJECT(name, ...) \
struct name { \
name##_VTable *vtable; \
__VA_ARGS__ \
}
// 调用虚函数
#define VCALL(obj, method, ...) (obj)->vtable->method((obj), ##__VA_ARGS__)
// 定义具体实现
typedef struct Shape Shape;
typedef struct ShapeVTable ShapeVTable;
struct ShapeVTable {
double (*area)(Shape *);
void (*describe)(Shape *);
};
struct Shape {
ShapeVTable *vtable;
char name[32];
};
// 圆形实现
typedef struct {
Shape base;
double radius;
} Circle;
double circle_area(Shape *s) {
Circle *c = (Circle *)s;
return 3.14159265 * c->radius * c->radius;
}
void circle_describe(Shape *s) {
Circle *c = (Circle *)s;
printf("圆形 %s, 半径=%.2f\n", s->name, c->radius);
}
static ShapeVTable circle_vtable = { circle_area, circle_describe };
// 矩形实现
typedef struct {
Shape base;
double width, height;
} Rectangle;
double rect_area(Shape *s) {
Rectangle *r = (Rectangle *)s;
return r->width * r->height;
}
void rect_describe(Shape *s) {
Rectangle *r = (Rectangle *)s;
printf("矩形 %s, 宽=%.2f, 高=%.2f\n", s->name, r->width, r->height);
}
static ShapeVTable rect_vtable = { rect_area, rect_describe };
int main(void) {
Circle c = { .base.vtable = &circle_vtable, .base.name = "圆A", .radius = 5.0 };
Rectangle r = { .base.vtable = &rect_vtable, .base.name = "矩B", .width = 4.0, .height = 6.0 };
Shape *shapes[] = { (Shape *)&c, (Shape *)&r };
for (int i = 0; i < 2; i++) {
VCALL(shapes[i], describe);
printf("面积: %.2f\n", VCALL(shapes[i], area));
}
return 0;
}