嵌入式C++教程——std::array vs C 数组,你们知道嘛?¶
写嵌入式代码时,你大概率会在两种写法间犹豫:int buf[16]; 和 std::array<int, 16> buf;。如果你像我(或者像你之前的文章风格那样)既爱性能又爱优雅,你会想知道:哪种更“嵌入式友好”?
为什么 std::array 看上去像“C 数组穿了件外套”——但其实更聪明¶
从表面看,std::array<T, N> 底层就是包含一个 T elems[N] 的聚合类型:内存上元素是连续的,元素布局没有神秘的开销。所以在很多场景下,std::array 与裸数组的性能、内存占用是等价的。换句话说,你不会因为换成 std::array 而在运行时付出额外费用。
但 std::array 把数组包进了一个类型:它有值语义(可以拷贝、赋值)、有 .size()、有 .data()、有 begin()/end()、能和 STL 算法无缝对接、支持 constexpr(在现代编译器下),还能作为模板参数被更好地推断。最关键的是,它把“长度是类型的一部分”这一信息显式化,调用接口时更不容易丢失长度信息。
换句话说:std::array 是“更安全、更现代”的数组。
裸 C 数组的老实巴交与致命天真¶
裸数组的优点是“零抽象”,也就是你对内存完全可控:这在启动代码、驱动层、位于特定地址空间的缓冲区(比如映射到某个外设寄存器地址)非常重要。裸数组在 ABI、链接器、对齐方面不给你出难题——只要你知道自己在做什么,它非常可靠。
但裸数组也带来一堆常见的踩坑:它会在函数参数中退化为指针(因此 sizeof 在函数里会给出指针大小),不能直接拷贝赋值(b = a; 会编译不过),也没有任何边界或尺寸信息保护。嵌入式代码里,这些“方便的缺失”会让你经常写 memcpy、频繁查 N 是否写对、在审查时犯“忘记传长度”的低级错误。
一个真实场景:你把裸数组传给 C API 做 DMA,忘了告诉调用方长度,结果 DMA 越界写到你最珍贵的变量上。裸数组没有提醒你这类低概率高代价错误。
std::array 的优点:更安全、更可读、和现代 C++ 更友好¶
std::array 的日常优势可以总结为:语义清晰、接口友好、可与算法直接配合。例如,std::sort(a.begin(), a.end()) 或 std::span(a) 都是顺手可得的好处。std::array 可以 =, 复制,甚至作为函数返回值安全返回(不会退化),这在很多中层逻辑里能让代码更简洁、更少内存操作 bug。
在嵌入式上下文,这意味着测试代码、单元测试桩、缓冲区封装这些地方会更干净:你可以写成返回 std::array 的函数而不是整堆 memcpy。而且当编译器支持 constexpr 时,std::array 能在编译期构造常量表,代码既高效又安全。
那么什么时候应该继续用裸 C 数组?¶
std::array 很好,但并不是无敌。在下面几类场景,裸数组仍然是更合适的选择:
- 初始化阶段或早期引导代码(startup / crt0):在
main()之前,C++ 的全局构造规则和运行时支持可能会麻烦。裸数组在这类代码里更直白、更可靠,尤其是当你需要绝对确保没有任何构造器或运行时代码介入时。 - 放在特定链接段 / 放到固定地址:像中断向量表、设备映射缓冲区、bootloader 的表格等,往往需要在链接脚本里精确声明对象位置和字节序。裸数组更直接映射到期望的内存布局,减少不必要的抽象。
- 严格的 ABI 或与外部 C API 的互操作,且你需要写裸指针:虽然
std::array有.data(),但在一些非常讲究二进制兼容性的场景中,审计时用裸数组更直观(尤其是老代码基)。 - 极端资源受限且要避免编译器生成任何额外元信息:这类情况稀有,但存在于某些超嵌入式或者内核最底层代码中。
所以怎么说?¶
裸数组是简洁、可靠的工具,适合最接近硬件的那一层;std::array 是更现代、更安全、更贴合 C++ 思想的容器,适合业务逻辑、算法层以及绝大多数嵌入式应用代码。把二者当作工具箱里的两把刀:修理芯片引脚用军刀(裸数组),写协议解析和缓冲逻辑用精密小刀(std::array)。
最后一句鸡汤式建议:当你能把数组尺寸写成 std::array<T, N> 的模板参数时,就写成 std::array;当你必须在链接脚本或最早期的引导代码里精确控制每个字节时,回到裸数组,别害羞。嵌入式开发不是为了“保持纯粹”,而是为了按实际需要用对工具——std::array 很多时候会让你代码更少、错误更少,偶尔你还是得把手伸进裸内存去修一修底层。