嵌入式#define __assert __assert写法解析

🔍 为什么有些代码中会写 #define __assert __assert

#define __assert __assert 是一种用来在预处理器阶段“锁定符号名”的技巧,它不做宏替换,但可以阻止其他代码(特别是头文件)重新定义该宏,从而保护你定义的函数符号不被污染。

在嵌入式系统或底层库(如 NuttX、newlib、glibc)中,我们经常会看到这种看似“自相矛盾”的代码:

#define _assert _assert
#define __assert __assert

第一次看到可能会觉得莫名其妙:“这不是什么都没做吗?”
其实这是一种非常实用的预处理器技巧,它有明确目的,且在多种场景下能解决潜在的问题


🧠 这种写法的作用是什么?

简短总结:

#define __assert __assert 是为了防止 __assert 被错误地宏替换,从而保留它作为“函数符号”的身份。


🧨 背景问题:宏污染导致函数失效

比如你定义了一个自定义的 __assert 函数,用于处理断言失败:

void __assert(const char *file, int line, const char *expr) {
    printf("Assertion failed: %s at %s:%d\n", expr, file, line);
    exit(1);
}

你希望这样调用它:

__assert("main.c", 42, "x != 0");

但是 —— 某些标准库或者头文件可能偷偷定义了:

#define __assert(expr) ((expr) ? (void)0 : abort())

结果你写的 __assert("main.c", 42, "x != 0") 被预处理器展开成:

(("main.c", 42, "x != 0") ? (void)0 : abort())  // ❌ 错误!

直接导致编译失败或者运行时逻辑错误!


✅ 解决方法:使用“自定义保护宏”

通过加入:

#define __assert __assert

你告诉预处理器:

  • “我已经定义了 __assert,你不要再给我重新定义它。”
  • 后续如果某个头文件试图再次定义 #define __assert(...)编译器会报重复定义 warning 或 error,从而阻止宏污染。

这招本质上是预处理器层的“符号占位保护”


🧪 示例对比(附代码)

❌ 没有保护,编译失败

#include <stdlib.h>
#include <stdio.h>

#define __assert(expr) ((expr) ? (void)0 : abort())  // 模拟污染

void __assert(const char *file, int line, const char *expr) {
    printf("Assert failed: %s at %s:%d\n", expr, file, line);
    exit(1);
}

int main() {
    __assert("main.c", 42, "x != 0");  // ❌ 被宏展开,编译失败
    return 0;
}

✅ 有保护,编译成功

#include <stdlib.h>
#include <stdio.h>

#define __assert __assert           // ✅ 关键一行,保护函数名不被宏污染
#define __assert(expr) ((expr) ? (void)0 : abort())  // 模拟污染

void __assert(const char *file, int line, const char *expr) {
    printf("Assert failed: %s at %s:%d\n", expr, file, line);
    exit(1);
}

int main() {
    __assert("main.c", 42, "x != 0");  // ✅ 正常调用你自己的函数
    return 0;
}

📦 使用场景

这种写法常见于:

  • 嵌入式 RTOS(如 NuttX、Zephyr)
  • libc 实现(如 newlib、glibc)
  • 内核代码(Linux Kernel 中也有类似写法)
  • 多人协作的系统级项目,为了避免符号名污染

📌 总结

问题#define __assert __assert 的好处
避免头文件宏污染保证调用的是你定义的函数而不是错误的宏
避免冲突定义如果后面再定义 __assert(...),会触发编译器警告
保持链接符号清晰确保 __assert 是函数符号,便于链接与覆盖
强符号 / 弱符号机制保留函数定义供链接器识别(如 weak 函数)

📢 一句话总结:

#define __assert __assert 是一种低成本、预处理阶段就能阻止宏污染的技巧,常用于底层和嵌入式开发中,是维护代码健壮性的重要写法。


你这个反馈非常好,说明你真正尝试了代码,并遇到了实际编译问题。


🧨 你看到的报错,说明了一个关键点:

#define __assert __assert 不能和 #define __assert(...) 共存

这是 C 语言预处理器的行为决定的:

  • #define __assert __assert:定义了一个无参数宏(object-like macro)
  • #define __assert(expr):定义了一个带参数的宏(function-like macro)

这两个是冲突的,标准 C 预处理器会报错:同一个宏不能既是函数式又是对象式的。

所以,你这里的报错是完全符合预期行为的,也正说明了:

加了 #define __assert __assert 后,后续任何试图重新定义 __assert 的行为,都会被编译器阻止。


✅ 正确理解这种写法的用途不是让你“之后还能定义宏”,而是:

用于阻止“别人”定义宏污染你已有的函数名

在实际项目中,正确使用方式是:

  1. 你希望使用 __assert 作为函数

  2. 你担心某些头文件(如 <assert.h>、newlib、glibc)会偷偷定义 #define __assert(...)

  3. 你就在它们之前写上:

    #define __assert __assert
    
  4. 后续那些试图再定义宏的地方,会触发宏重定义错误,从而保护你自己代码的行为


✅ 正确用法示例

我们换个角度来写:你自己的函数希望叫 __assert,而你想阻止头文件把它变成宏。那你必须先保护,再包含头文件。

✔️ 正确写法:

#define __assert __assert     // 提前声明:我要保留 __assert 为函数

#include <assert.h>           // 假设这里会污染 __assert 宏

#undef assert                 // 可选:你可以改掉 assert 也行

#include <stdio.h>
#include <stdlib.h>

void __assert(const char *file, int line, const char *expr) {
    printf("Custom assert: %s at %s:%d\n", expr, file, line);
    exit(1);
}

int main() {
    __assert("main.c", 42, "x != 0");
    return 0;
}

🔧 编译方式:

gcc main.c -o main

🧩 再总结一下这个技巧的本质

场景是否适用
你要自己写一个叫 __assert() 的函数✅ 适用
你不希望 assert.h 或别的库把它 define 掉✅ 适用
你想之后再自己 define 宏 __assert(...)❌ 不适用,会报错
你要覆盖 newlib/glibc 的 weak __assert()✅ 适用,防止被宏干扰
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值