当前位置: 首页 > news >正文

嵌入式#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()✅ 适用,防止被宏干扰
http://www.dtcms.com/a/316041.html

相关文章:

  • 起落架大型结构件深孔测量探究 - 激光频率梳 3D 轮廓检测
  • 智慧会所:科技赋能,开启休闲新体验
  • 解决 Windows 下的“幽灵文件”——记一次与带空格的 .gitignore 文件的艰难斗争
  • PDF 文本提取技术深度对比:基于规则与基于模型的两种实现
  • 向量范数与矩阵范数
  • ThingsKit Edge是什么?
  • 论文阅读: Mobile Edge Intelligence for Large LanguageModels: A Contemporary Survey
  • 小杰数据结构——题库——拂衣便欲沧海去,但许明月随吾身
  • echarts在前后端分离项目中的实践与应用
  • mysql强制区分大小写进行查询用BINARY
  • CodeRush AI 助手进驻 Visual Studio:AiGen/AiFind 亮相(四)
  • GoLand 项目从 0 到 1:第五天 —— 角色权限中间件实现与事务控制
  • Java学习第一百零六部分——Lucene
  • python开发环境安装多系统完整版
  • laravel在cli模式下输出格式漂亮一些
  • python的高校班级管理系统
  • 【计组】数据的表示与运算
  • Linux动静态库的理解
  • 探秘MOBILITY China 2026,新能源汽车与智慧出行的未来盛宴
  • MinIO02-Docker安装
  • 11.Linux 防火墙管理
  • selenium自动化收集资料
  • MLP-Mixer: An all-MLP Architecture for Vision
  • 计算机基础:操作系统学习的基石
  • 【前端】Node.js 简易服务器搭建全指南:从基础到实践
  • 调试|谷歌浏览器调试长连接|调试SSE和websocket
  • Redis内存耗尽时的应对策略
  • Day115 SpringBoot整合Redis,RedisTemplate和注解两种方式的使用
  • SQL164 删除表
  • 输入12-21V输出5V 10A电源转换芯片方案