前置知识: C

内联函数与宏

00:00
3 min Intermediate 2026/6/14

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;
}

知识检测

学习进度

-- 已学文档
--% 知识覆盖率

学习推荐

专注模式