嵌入式现代C++教程——std::array:编译期固定大小数组¶
你写嵌入式代码时,堆(heap)常常像个不可靠的室友:随时可能把屋子掀翻。std::array 就像那位稳重但不多话的朋友——编译期确定大小、占栈或静态存储、没有动态分配,性能可预测,语义清楚。我们的一个重点就是——他跟传统的C风格数组比起来,怎么样的事情。
什么是 std::array¶
std::array<T, N> 是一个封装了 C 风格数组的轻量类模板:大小 N 在编译期就确定,提供 STL 风格的接口(.size()、.begin()、.data()、operator[]、迭代器等),且通常不会比原始数组多多少运行开销。
为什么在嵌入式喜欢它¶
- 零动态分配:没有
new/malloc,适合无堆或受限内存环境。 - 可预测内存布局:编译期大小、连续存储,方便用于 DMA、裸指针接口。
- STL 友好:可以直接传给算法(
std::sort、std::fill)和容器适配器。 - constexpr 支持:可以用作编译期查表或常量数据。
- 类型安全与自文档化:
std::array<uint8_t, 128>明确表达意图,比uint8_t buf[128]更现代。
基本用法(代码示例)¶
#include <array>
#include <algorithm>
#include <cstdint>
#include <iostream>
int main() {
std::array<uint8_t, 8> buf{}; // value-initialized -> all zeros
buf[0] = 0xAA;
buf.at(1) = 0x55; // .at 会做边界检查(抛异常)
// 兼容 STL 算法
std::fill(buf.begin(), buf.end(), 0xFF);
// 传给 C API(不会隐式退化):使用 data()
// c_function(buf.data(), buf.size());
for (auto b : buf) std::cout << int(b) << ' ';
std::cout << '\n';
}
小提醒:.at() 在异常被禁用或不可用的裸机环境下不适合;用 operator[] 并保持索引正确。
与 C 数组、std::vector 的比较¶
- 和 C 数组:
std::array是包起来的类,支持.size()、迭代器、std::get、结构化绑定,且能作为对象被拷贝/赋值。底层仍是连续内存。 - 和 std::vector:
vector可动态调整大小(需堆),std::array无堆、大小固定、开销更小、语义更明确,嵌入式通常更倾向std::array。
常见技巧和细节(嵌入式角度)¶
1. 放静态区还是栈上?¶
- 小数组(几十、几百字节)可放在栈上。注意任务/ISR 的栈深度限制。
- 较大数组应放为
static或放在.bss,或放入只读闪存(constexpr数据)以节省 RAM。
示例:
2. 用于 DMA / 外设¶
因为 std::array 保证连续内存,你可以安全地传 arr.data() 给 DMA 或 HAL。但确保元素类型是 可复制且没有需要特殊构造的复杂类型(一般使用 POD 或 trivial 类型)。
3. 编译期表与 constexpr¶
std::array 可用于编译期常量查表(免运行时初始化):
#include <array>
constexpr std::array<int, 5> make_table() {
return {0, 1, 4, 9, 16};
}
constexpr auto table = make_table(); // 存在于只读段,可放进 flash
static_assert(table[3] == 9);
如果你需要在编译期生成一个更复杂的表,可以配合 std::index_sequence 做元编程(不赘述复杂实现,这里先给出思路:用 index_sequence 展开索引并在 constexpr 函数中产生元素`)。
4. 结构化绑定与 std::get¶
std::array 支持 std::get<0>(arr) 和结构化绑定(C++17):
5. 避免退化为指针的陷阱¶
C 风格数组在传参时会退化为指针,而 std::array 不会,你必须明确传 .data() 或 .size():
6. 与裸机异常策略的兼容性¶
某些嵌入式编译链把异常支持关掉,这会影响 .at()(抛异常)的使用。建议在无异常环境下只用 operator[] 并在编译期/开发期做边界检查工具。
高级话题:当元素不是 POD 时¶
std::array<T, N> 的元素可以是任意类型 T。但在嵌入式里常见注意点:
- 如果
T有复杂构造/析构,静态初始化(尤其零初始化)行为会不同,要保证构造成本被接受。 - 对于需要通过 DMA 读写的缓冲区,
T应该是 trivially copyable。
可以一试——把 std::array 用作编译期 CRC 表¶
#include <array>
#include <cstdint>
constexpr std::array<uint32_t, 256> make_crc_table() {
std::array<uint32_t, 256> t{};
for (size_t i = 0; i < 256; ++i) {
uint32_t crc = static_cast<uint32_t>(i);
for (int j = 0; j < 8; ++j)
crc = (crc & 1) ? (0xEDB88320u ^ (crc >> 1)) : (crc >> 1);
t[i] = crc;
}
return t;
}
constexpr auto crc_table = make_crc_table(); // 编译期计算,放到只读段(若编译器支持)
在支持的工具链上,这样可以把查表数据放进 flash,节省 RAM。
查看完整可编译示例
// basic_usage.cpp - std::array 基本用法演示
#include <array>
#include <algorithm>
#include <cstdint>
#include <iostream>
int main() {
// value-initialized -> all zeros
std::array<uint8_t, 8> buf{};
buf[0] = 0xAA;
// .at 会做边界检查(抛异常)- 裸机环境慎用
buf.at(1) = 0x55;
// 兼容 STL 算法
std::fill(buf.begin(), buf.end(), 0xFF);
// 范围 for 遍历
for (auto b : buf) std::cout << int(b) << ' ';
std::cout << '\n';
// 其他有用的成员函数
std::cout << "size: " << buf.size() << '\n';
std::cout << "front: " << int(buf.front()) << '\n';
std::cout << "back: " << int(buf.back()) << '\n';
// 填充为不同值
std::fill_n(buf.begin(), 4, 0x11);
std::fill_n(buf.begin() + 4, 4, 0x22);
for (auto b : buf) std::cout << std::hex << int(b) << ' ';
std::cout << std::dec << '\n';
return 0;
}
查看更多示例:静态存储、DMA、编译期查表等
// static_storage.cpp - 演示静态存储与栈存储的选择
#include <array>
#include <cstdint>
#include <iostream>
// 小数组 - 可以放在栈上
void stack_example() {
const int stack_size = 64;
std::array<uint8_t, stack_size> small_buf{};
small_buf[0] = 0x42;
std::cout << "Stack array at: " << static_cast<void*>(small_buf.data()) << '\n';
}
// 大数组 - 应放在静态区
static std::array<uint8_t, 1024> big_buf; // 在 .bss,程序启动后分配
// 只读数据 - 可以放 flash(使用 constexpr)
constexpr std::array<uint8_t, 16> readonly_data = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F
};
int main() {
stack_example();
std::cout << "Static array at: " << static_cast<void*>(big_buf.data()) << '\n';
std::cout << "Readonly array at: " << static_cast<const void*>(readonly_data.data()) << '\n';
// 静态数组在程序启动时自动零初始化
bool all_zero = true;
for (auto b : big_buf) {
if (b != 0) {
all_zero = false;
break;
}
}
std::cout << "Big array all zero: " << (all_zero ? "yes" : "no") << '\n';
return 0;
}
// dma_usage.cpp - 演示 std::array 在 DMA 场景的使用
#include <array>
#include <cstdint>
#include <iostream>
// 模拟 HAL 层的 DMA 传输函数
void hal_dma_transmit(const uint8_t* data, size_t length) {
std::cout << "DMA transmitting " << length << " bytes from " << static_cast<const void*>(data) << ":\n";
for (size_t i = 0; i < length; ++i) {
std::cout << std::hex << int(data[i]) << ' ';
if ((i + 1) % 16 == 0) std::cout << '\n';
}
std::cout << std::dec << "\n";
}
// 模拟 HAL 层的 DMA 接收函数
void hal_dma_receive(uint8_t* buffer, size_t length) {
std::cout << "DMA receiving " << length << " bytes to " << static_cast<void*>(buffer) << '\n';
// 模拟接收数据
for (size_t i = 0; i < length; ++i) {
buffer[i] = static_cast<uint8_t>(i * 2);
}
}
int main() {
// DMA 发送场景
std::array<uint8_t, 32> tx_buffer{};
for (size_t i = 0; i < tx_buffer.size(); ++i) {
tx_buffer[i] = static_cast<uint8_t>(i);
}
// std::array 保证连续内存,可以安全地传给 DMA
hal_dma_transmit(tx_buffer.data(), tx_buffer.size());
// DMA 接收场景
std::array<uint8_t, 64> rx_buffer{};
hal_dma_receive(rx_buffer.data(), rx_buffer.size());
// 验证接收的数据
std::cout << "First 8 bytes received: ";
for (size_t i = 0; i < 8; ++i) {
std::cout << std::hex << int(rx_buffer[i]) << ' ';
}
std::cout << std::dec << '\n';
// 注意:用于 DMA 的数组元素类型应该是 trivially copyable
static_assert(std::is_trivially_copyable_v<uint8_t>,
"DMA buffer type must be trivially copyable");
return 0;
}
// constexpr_table.cpp - 编译期查表示例
#include <array>
#include <iostream>
// 编译期生成平方表
constexpr std::array<int, 10> make_square_table() {
std::array<int, 10> table{};
for (size_t i = 0; i < table.size(); ++i) {
table[i] = static_cast<int>(i * i);
}
return table;
}
// 表在编译期计算,存储在只读段(若编译器支持)
constexpr auto square_table = make_square_table();
// 编译期断言 - 在编译期验证表内容
static_assert(square_table[0] == 0, "Square of 0 should be 0");
static_assert(square_table[5] == 25, "Square of 5 should be 25");
static_assert(square_table[9] == 81, "Square of 9 should be 81");
// 使用查表的函数(编译期优化后可能直接内联常量)
int get_square(int n) {
if (n >= 0 && n < 10) {
return square_table[n];
}
return -1; // 错误值
}
int main() {
std::cout << "Square table (computed at compile time):\n";
for (size_t i = 0; i < square_table.size(); ++i) {
std::cout << i << "^2 = " << square_table[i] << '\n';
}
std::cout << "\nLookup results:\n";
for (int i = 0; i < 10; ++i) {
std::cout << "get_square(" << i << ") = " << get_square(i) << '\n';
}
return 0;
}
// constexpr_crc_table.cpp - 编译期 CRC 表生成
#include <array>
#include <cstdint>
#include <iostream>
// 编译期生成 CRC32 查找表
constexpr std::array<uint32_t, 256> make_crc_table() {
std::array<uint32_t, 256> table{};
for (size_t i = 0; i < 256; ++i) {
uint32_t crc = static_cast<uint32_t>(i);
for (int j = 0; j < 8; ++j) {
crc = (crc & 1) ? (0xEDB88320u ^ (crc >> 1)) : (crc >> 1);
}
table[i] = crc;
}
return table;
}
// 表在编译期计算,存储在只读段
constexpr auto crc_table = make_crc_table();
// 编译期验证表中的几个关键值
static_assert(crc_table[0] == 0x00000000, "CRC table[0] mismatch");
static_assert(crc_table[1] == 0x77073096, "CRC table[1] mismatch");
static_assert(crc_table[255] == 0x2D02EF8D, "CRC table[255] mismatch");
// 计算 CRC32 校验和
uint32_t compute_crc(const uint8_t* data, size_t length) {
uint32_t crc = 0xFFFFFFFFu;
for (size_t i = 0; i < length; ++i) {
crc = crc_table[(crc ^ data[i]) & 0xFF] ^ (crc >> 8);
}
return crc ^ 0xFFFFFFFFu;
}
int main() {
std::cout << "CRC32 Table (first 16 entries, computed at compile time):\n";
for (size_t i = 0; i < 16; ++i) {
std::cout << "table[" << i << "] = 0x" << std::hex << crc_table[i] << std::dec << '\n';
}
// 计算 CRC32 示例
const char* test_data = "Hello, World!";
uint32_t crc = compute_crc(reinterpret_cast<const uint8_t*>(test_data), 13);
std::cout << "\nCRC32 of \"" << test_data << "\" = 0x" << std::hex << crc << std::dec << '\n';
return 0;
}
// structured_binding.cpp - 演示 std::get 和结构化绑定
#include <array>
#include <iostream>
#include <utility> // for std::tuple_size, std::tuple_element
int main() {
// 基本结构化绑定 (C++17)
std::array<int, 3> a = {1, 2, 3};
auto [x, y, z] = a;
std::cout << "x=" << x << ", y=" << y << ", z=" << z << '\n';
// std::get<I>(array) 访问
std::cout << "std::get<0>(a) = " << std::get<0>(a) << '\n';
std::cout << "std::get<1>(a) = " << std::get<1>(a) << '\n';
std::cout << "std::get<2>(a) = " << std::get<2>(a) << '\n';
// 编译期索引访问(模板元编程友好)
template<size_t I>
int get_element(const std::array<int, 3>& arr) {
return std::get<I>(arr);
}
std::cout << "get_element<0>(a) = " << get_element<0>(a) << '\n';
// tuple_size 和 tuple_element 支持
using ArrayType = std::array<int, 3>;
constexpr size_t size = std::tuple_size_v<ArrayType>;
using ElementType = std::tuple_element_t<0, ArrayType>;
std::cout << "Array size (via tuple_size): " << size << '\n';
std::cout << "Element type is int: " << std::is_same_v<ElementType, int> << '\n';
// 结构化绑定用于解构返回值
auto get_coordinates() -> std::array<double, 3> {
return {1.5, 2.5, 3.5};
}
auto [px, py, pz] = get_coordinates();
std::cout << "Point: (" << px << ", " << py << ", " << pz << ")\n";
return 0;
}
// c_api_compatible.cpp - 演示 std::array 与 C API 的兼容性
#include <array>
#include <cstdint>
#include <cstring>
#include <iostream>
// 模拟典型的 C API
extern "C" {
// 传统 C 风格 API
void c_api_process(uint8_t* buffer, size_t length);
// const 正确的 C API
void c_api_read_only(const uint8_t* data, size_t length);
}
void c_api_process(uint8_t* buffer, size_t length) {
std::cout << "C API processing " << length << " bytes:\n";
for (size_t i = 0; i < length; ++i) {
buffer[i] = static_cast<uint8_t>(buffer[i] * 2);
}
}
void c_api_read_only(const uint8_t* data, size_t length) {
std::cout << "C API reading " << length << " bytes:\n";
for (size_t i = 0; i < length; ++i) {
std::cout << std::hex << int(data[i]) << ' ';
if ((i + 1) % 16 == 0) std::cout << '\n';
}
std::cout << std::dec << '\n';
}
int main() {
// C 风格数组隐式退化为指针
uint8_t c_array[16] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
c_api_process(c_array, 16);
// std::array 不会隐式退化,必须使用 .data()
std::array<uint8_t, 16> cpp_array = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
// 正确方式:使用 .data() 和 .size()
c_api_process(cpp_array.data(), cpp_array.size());
// 只读访问:.data() 返回指向元素的指针
c_api_read_only(cpp_array.data(), cpp_array.size());
// 对比:std::vector 也使用相同模式
// std::vector<uint8_t> vec(16, 42);
// c_api_process(vec.data(), vec.size());
std::cout << "After processing:\n";
for (size_t i = 0; i < cpp_array.size(); ++i) {
std::cout << "cpp_array[" << i << "] = " << int(cpp_array[i]) << '\n';
}
return 0;
}