嵌入式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 很多时候会让你代码更少、错误更少,偶尔你还是得把手伸进裸内存去修一修底层。
代码示例¶
查看完整可编译示例
#include <iostream>
#include <array>
#include <algorithm>
#include <cstring>
#include <iterator>
// std::array vs C数组的比较
void c_array_demo() {
std::cout << "=== C Array Demo ===\n\n";
// 声明和初始化
int arr1[5] = {1, 2, 3, 4, 5};
int arr2[] = {10, 20, 30}; // 自动推断大小
int arr3[10] = {0}; // 零初始化
std::cout << "arr1: ";
for (int i = 0; i < 5; ++i) {
std::cout << arr1[i] << " ";
}
std::cout << "\n";
// 数组大小
std::cout << "sizeof(arr1) = " << sizeof(arr1) << "\n";
std::cout << "Element count = " << sizeof(arr1) / sizeof(arr1[0]) << "\n";
// 数组退化(危险!)
int* ptr = arr1; // 退化为指针
std::cout << "ptr points to: " << ptr << "\n";
std::cout << "sizeof(ptr) = " << sizeof(ptr) << " (lost size info!)\n";
// 边界检查:无!
// arr1[10] = 99; // 未定义行为,越界访问
// 不能直接复制
int arr4[5];
// arr4 = arr1; // 编译错误!
std::memcpy(arr4, arr1, sizeof(arr1)); // 需要memcpy
// 不能直接比较
// if (arr4 == arr1) { } // 比较的是地址,不是内容
}
void std_array_demo() {
std::cout << "\n=== std::array Demo ===\n\n";
// 声明和初始化
std::array<int, 5> arr1 = {1, 2, 3, 4, 5};
std::array<int, 3> arr2 = {10, 20, 30};
std::array<int, 10> arr3{}; // 零初始化
std::cout << "arr1: ";
for (size_t i = 0; i < arr1.size(); ++i) {
std::cout << arr1[i] << " ";
}
std::cout << "\n";
// 数组大小
std::cout << "arr1.size() = " << arr1.size() << "\n";
std::cout << "arr1.max_size() = " << arr1.max_size() << "\n";
std::cout << "sizeof(arr1) = " << sizeof(arr1) << "\n";
std::cout << "empty? " << (arr1.empty() ? "yes" : "no") << "\n";
// 不退化,保持类型信息
auto& ref = arr1;
std::cout << "sizeof(ref) = " << sizeof(ref) << " (preserved!)\n";
// 边界检查(at方法)
try {
std::cout << "arr1.at(2) = " << arr1.at(2) << "\n";
std::cout << "arr1.at(10) = "; // 越界
std::cout << arr1.at(10) << "\n";
} catch (const std::out_of_range& e) {
std::cout << "caught exception: " << e.what() << "\n";
}
// 可以直接复制
std::array<int, 5> arr4 = arr1;
std::cout << "\narr4 (copy of arr1): ";
for (int v : arr4) {
std::cout << v << " ";
}
std::cout << "\n";
// 可以直接比较
std::array<int, 5> arr5 = {1, 2, 3, 4, 5};
std::cout << "arr4 == arr5? " << (arr4 == arr5 ? "yes" : "no") << "\n";
// 可以赋值
arr4 = {10, 20, 30, 40, 50};
std::cout << "After assignment, arr4: ";
for (int v : arr4) {
std::cout << v << " ";
}
std::cout << "\n";
}
void iteration_demo() {
std::cout << "\n=== Iteration Comparison ===\n\n";
// C数组
int c_arr[] = {1, 2, 3, 4, 5};
std::cout << "C array:\n";
std::cout << " Index loop: ";
for (size_t i = 0; i < sizeof(c_arr) / sizeof(c_arr[0]); ++i) {
std::cout << c_arr[i] << " ";
}
std::cout << "\n";
std::cout << " Range-based for (C++11): ";
for (int v : c_arr) {
std::cout << v << " ";
}
std::cout << "\n";
// std::array
std::array<int, 5> std_arr = {1, 2, 3, 4, 5};
std::cout << "\nstd::array:\n";
std::cout << " Index loop: ";
for (size_t i = 0; i < std_arr.size(); ++i) {
std::cout << std_arr[i] << " ";
}
std::cout << "\n";
std::cout << " Range-based for: ";
for (int v : std_arr) {
std::cout << v << " ";
}
std::cout << "\n";
std::cout << " Iterator: ";
for (auto it = std_arr.begin(); it != std_arr.end(); ++it) {
std::cout << *it << " ";
}
std::cout << "\n";
std::cout << " Reverse iterator: ";
for (auto it = std_arr.rbegin(); it != std_arr.rend(); ++it) {
std::cout << *it << " ";
}
std::cout << "\n";
std::cout << " std::for_each: ";
std::for_each(std_arr.begin(), std_arr.end(), [](int v) {
std::cout << v << " ";
});
std::cout << "\n";
}
void stl_integration_demo() {
std::cout << "\n=== STL Integration ===\n\n";
std::array<int, 10> arr = {5, 2, 8, 1, 9, 3, 7, 4, 6, 0};
std::cout << "Original: ";
for (int v : arr) {
std::cout << v << " ";
}
std::cout << "\n";
// 排序
std::sort(arr.begin(), arr.end());
std::cout << "Sorted: ";
for (int v : arr) {
std::cout << v << " ";
}
std::cout << "\n";
// 查找
auto it = std::find(arr.begin(), arr.end(), 7);
if (it != arr.end()) {
std::cout << "Found 7 at index " << (it - arr.begin()) << "\n";
}
// 二分查找
bool found = std::binary_search(arr.begin(), arr.end(), 5);
std::cout << "Binary search for 5: " << (found ? "found" : "not found") << "\n";
// 计数
std::array<int, 10> arr2 = {1, 2, 2, 3, 2, 4, 2, 5, 2, 6};
size_t count = std::count(arr2.begin(), arr2.end(), 2);
std::cout << "Count of 2 in arr2: " << count << "\n";
// 填充
std::array<int, 5> fill_arr;
fill_arr.fill(42);
std::cout << "Filled array: ";
for (int v : fill_arr) {
std::cout << v << " ";
}
std::cout << "\n";
// 交换
std::array<int, 5> a = {1, 2, 3, 4, 5};
std::array<int, 5> b = {10, 20, 30, 40, 50};
std::cout << "\nBefore swap:\n";
std::cout << " a: ";
for (int v : a) std::cout << v << " ";
std::cout << "\n b: ";
for (int v : b) std::cout << v << " ";
std::cout << "\n";
a.swap(b);
std::cout << "After swap:\n";
std::cout << " a: ";
for (int v : a) std::cout << v << " ";
std::cout << "\n b: ";
for (int v : b) std::cout << v << " ";
std::cout << "\n";
}
void data_access_demo() {
std::cout << "\n=== Data Access ===\n\n";
std::array<int, 5> arr = {10, 20, 30, 40, 50};
// data() 返回原始指针
int* ptr = arr.data();
std::cout << "Via data(): ";
for (size_t i = 0; i < arr.size(); ++i) {
std::cout << ptr[i] << " ";
}
std::cout << "\n";
// front() 和 back()
std::cout << "front() = " << arr.front() << "\n";
std::cout << "back() = " << arr.back() << "\n";
// 与C API互操作
std::array<char, 64> buf;
std::strcpy(buf.data(), "Hello, std::array!");
std::cout << "String: " << buf.data() << "\n";
// 使用memcpy
std::array<int, 5> src = {1, 2, 3, 4, 5};
std::array<int, 5> dst;
std::memcpy(dst.data(), src.data(), sizeof(src));
std::cout << "Copied: ";
for (int v : dst) {
std::cout << v << " ";
}
std::cout << "\n";
}
void constexpr_demo() {
std::cout << "\n=== Compile-Time Features ===\n\n";
// 编译期构造
constexpr std::array<int, 5> fib = [] {
std::array<int, 5> arr{};
arr[0] = 0;
arr[1] = 1;
for (size_t i = 2; i < arr.size(); ++i) {
arr[i] = arr[i-1] + arr[i-2];
}
return arr;
}();
std::cout << "Compile-time Fibonacci: ";
for (int v : fib) {
std::cout << v << " ";
}
std::cout << "\n";
// 编译期大小检查
static_assert(fib.size() == 5, "Size must be 5");
// 作为模板参数
template<size_t N>
void print_array_size(const std::array<int, N>&) {
std::cout << "Array size (template param): " << N << "\n";
}
std::array<int, 10> arr;
print_array_size(arr);
}
int main() {
c_array_demo();
std_array_demo();
iteration_demo();
stl_integration_demo();
data_access_demo();
constexpr_demo();
std::cout << "\n=== Key Takeaways ===\n";
std::cout << "1. std::array provides size info, doesn't decay to pointer\n";
std::cout << "2. Boundary checking with at() method\n";
std::cout << "3. Copyable, comparable, assignable\n";
std::cout << "4. Full STL algorithm support\n";
std::cout << "5. Zero overhead compared to C arrays\n";
std::cout << "6. Prefer std::array except in low-level startup code\n";
return 0;
}
#include <iostream>
#include <array>
#include <algorithm>
#include <cstring>
#include <cstdint>
// std::array 在实际嵌入式场景中的应用
// 示例1:状态机状态表
enum class State {
Idle,
Running,
Paused,
Error
};
enum class Event {
Start,
Stop,
Pause,
Resume,
Error
};
struct StateTransition {
State current;
Event event;
State next;
};
constexpr std::array<StateTransition, 8> state_table = {{
{State::Idle, Event::Start, State::Running},
{State::Running, Event::Stop, State::Idle},
{State::Running, Event::Pause, State::Paused},
{State::Paused, Event::Resume, State::Running},
{State::Paused, Event::Stop, State::Idle},
{State::Error, Event::Stop, State::Idle},
// 可以添加更多转换...
}};
State get_next_state(State current, Event event) {
auto it = std::find_if(state_table.begin(), state_table.end(),
[current, event](const StateTransition& t) {
return t.current == current && t.event == event;
});
return (it != state_table.end()) ? it->next : State::Error;
}
void state_machine_demo() {
std::cout << "=== State Machine with std::array ===\n\n";
State state = State::Idle;
auto print_state = [](State s) {
switch (s) {
case State::Idle: std::cout << "Idle"; break;
case State::Running: std::cout << "Running"; break;
case State::Paused: std::cout << "Paused"; break;
case State::Error: std::cout << "Error"; break;
}
};
std::array<Event, 6> events = {
Event::Start, Event::Pause, Event::Resume,
Event::Stop, Event::Start, Event::Error
};
for (Event e : events) {
print_state(state);
std::cout << " + ";
switch (e) {
case Event::Start: std::cout << "Start"; break;
case Event::Stop: std::cout << "Stop"; break;
case Event::Pause: std::cout << "Pause"; break;
case Event::Resume: std::cout << "Resume"; break;
case Event::Error: std::cout << "Error"; break;
}
std::cout << " -> ";
state = get_next_state(state, e);
print_state(state);
std::cout << "\n";
}
}
// 示例2:环形缓冲区
template<typename T, size_t N>
class RingBuffer {
std::array<T, N> buffer_;
size_t head_ = 0;
size_t tail_ = 0;
// 要求N是2的幂
static_assert((N & (N - 1)) == 0, "Size must be power of 2");
static constexpr size_t mask_ = N - 1;
public:
bool push(const T& value) {
size_t next = (head_ + 1) & mask_;
if (next == tail_) {
return false; // Full
}
buffer_[head_] = value;
head_ = next;
return true;
}
bool pop(T& out) {
if (head_ == tail_) {
return false; // Empty
}
out = buffer_[tail_];
tail_ = (tail_ + 1) & mask_;
return true;
}
bool empty() const { return head_ == tail_; }
bool full() const { return ((head_ + 1) & mask_) == tail_; }
size_t size() const { return (head_ - tail_) & mask_; }
const std::array<T, N>& data() const { return buffer_; }
};
void ring_buffer_demo() {
std::cout << "\n=== Ring Buffer with std::array ===\n\n";
RingBuffer<int, 8> rb;
// Push values
for (int i = 0; i < 7; ++i) {
bool ok = rb.push(i);
std::cout << "Push " << i << ": " << (ok ? "success" : "failed") << "\n";
}
std::cout << "Buffer size: " << rb.size() << "\n";
std::cout << "Buffer full: " << (rb.full() ? "yes" : "no") << "\n";
// Pop values
std::cout << "\nPopping values:\n";
int val;
while (rb.pop(val)) {
std::cout << " " << val << "\n";
}
std::cout << "Buffer empty: " << (rb.empty() ? "yes" : "no") << "\n";
}
// 示例3:硬件寄存器映射(演示用)
class RegisterMap {
std::array<uint32_t, 16> registers_;
public:
RegisterMap() { registers_.fill(0); }
// 读写寄存器
uint32_t read(size_t index) const {
if (index < registers_.size()) {
return registers_[index];
}
return 0;
}
void write(size_t index, uint32_t value) {
if (index < registers_.size()) {
registers_[index] = value;
}
}
// 位操作
void set_bit(size_t index, size_t bit) {
if (index < registers_.size() && bit < 32) {
registers_[index] |= (1U << bit);
}
}
void clear_bit(size_t index, size_t bit) {
if (index < registers_.size() && bit < 32) {
registers_[index] &= ~(1U << bit);
}
}
// 导出原始数据(用于DMA等)
uint32_t* data() { return registers_.data(); }
const uint32_t* data() const { return registers_.data(); }
};
void register_map_demo() {
std::cout << "\n=== Register Map with std::array ===\n\n";
RegisterMap regs;
// 写入一些寄存器
regs.write(0, 0x12345678);
regs.write(1, 0xABCDEF00);
regs.set_bit(2, 5);
regs.set_bit(2, 10);
// 读取并显示
for (size_t i = 0; i < 4; ++i) {
std::cout << "Reg[" << i << "] = 0x" << std::hex << regs.read(i) << std::dec << "\n";
}
// 与C API互操作
std::array<uint32_t, 16> copy;
std::memcpy(copy.data(), regs.data(), sizeof(uint32_t) * 16);
std::cout << "\nCopied via memcpy, first value: 0x" << std::hex << copy[0] << std::dec << "\n";
}
// 示例4:查表
template<typename T, size_t TABLE_SIZE>
class LookupTable {
std::array<T, TABLE_SIZE> table_;
public:
explicit LookupTable(const std::array<T, TABLE_SIZE>& table) : table_(table) {}
// 线性插值查表
T lookup(float index) const {
if (index <= 0) return table_[0];
if (index >= TABLE_SIZE - 1) return table_[TABLE_SIZE - 1];
size_t i = static_cast<size_t>(index);
float frac = index - i;
return table_[i] + frac * (table_[i + 1] - table_[i]);
}
// 直接查表
const T& operator[](size_t index) const {
return table_[index];
}
size_t size() const { return TABLE_SIZE; }
};
void lookup_table_demo() {
std::cout << "\n=== Lookup Table with std::array ===\n\n";
// 正弦表(简化版)
constexpr std::array<float, 16> sin_table = [] {
std::array<float, 16> table{};
for (size_t i = 0; i < 16; ++i) {
float angle = i * 3.14159f / 8.0f; // 0 to 2pi
table[i] = std::sin(angle);
}
return table;
}();
LookupTable<float, 16> sin_lut(sin_table);
std::cout << "Sine lookup:\n";
for (float f = 0.0f; f < 3.2f; f += 0.5f) {
float sin_val = sin_lut.lookup(f * 5.0f); // Scale to table index
std::cout << " sin(" << f << ") ≈ " << sin_val << "\n";
}
}
// 示例5:固定大小字符串缓冲区
template<size_t N>
class StaticString {
std::array<char, N> buffer_;
public:
StaticString() {
buffer_[0] = '\0';
}
StaticString(const char* str) {
set(str);
}
void set(const char* str) {
if (str) {
std::strncpy(buffer_.data(), str, N - 1);
buffer_[N - 1] = '\0';
} else {
buffer_[0] = '\0';
}
}
const char* c_str() const {
return buffer_.data();
}
size_t length() const {
return std::strlen(buffer_.data());
}
bool empty() const {
return buffer_[0] == '\0';
}
// 格式化(简化版)
int printf(const char* format, ...) {
va_list args;
va_start(args, format);
int result = std::vsnprintf(buffer_.data(), N, format, args);
va_end(args);
return result;
}
};
void static_string_demo() {
std::cout << "\n=== Static String with std::array ===\n\n";
StaticString<32> str1("Hello");
StaticString<64> str2;
std::cout << "str1: \"" << str1.c_str() << "\" (len=" << str1.length() << ")\n";
str2.set("World");
std::cout << "str2: \"" << str2.c_str() << "\"\n";
str2.printf("Value: %d, Hex: 0x%X", 42, 0xABCD);
std::cout << "Formatted: \"" << str2.c_str() << "\"\n";
}
// 示例6:传感器数据缓冲
struct SensorData {
uint32_t timestamp;
float value;
uint8_t status;
};
class SensorBuffer {
std::array<SensorData, 100> buffer_;
size_t count_ = 0;
public:
bool add(const SensorData& data) {
if (count_ >= buffer_.size()) {
return false;
}
buffer_[count_++] = data;
return true;
}
const SensorData* get_latest() const {
if (count_ == 0) return nullptr;
return &buffer_[count_ - 1];
}
const SensorData* get(size_t index) const {
if (index >= count_) return nullptr;
return &buffer_[index];
}
size_t count() const { return count_; }
void clear() { count_ = 0; }
// 统计
float average() const {
if (count_ == 0) return 0.0f;
float sum = 0.0f;
for (size_t i = 0; i < count_; ++i) {
sum += buffer_[i].value;
}
return sum / count_;
}
float min() const {
if (count_ == 0) return 0.0f;
float m = buffer_[0].value;
for (size_t i = 1; i < count_; ++i) {
if (buffer_[i].value < m) m = buffer_[i].value;
}
return m;
}
float max() const {
if (count_ == 0) return 0.0f;
float m = buffer_[0].value;
for (size_t i = 1; i < count_; ++i) {
if (buffer_[i].value > m) m = buffer_[i].value;
}
return m;
}
};
void sensor_buffer_demo() {
std::cout << "\n=== Sensor Buffer with std::array ===\n\n";
SensorBuffer buffer;
// 添加一些传感器数据
for (int i = 0; i < 10; ++i) {
SensorData data;
data.timestamp = i * 100;
data.value = 20.0f + i * 0.5f;
data.status = 0;
buffer.add(data);
}
std::cout << "Collected " << buffer.count() << " samples\n";
std::cout << "Average: " << buffer.average() << "\n";
std::cout << "Min: " << buffer.min() << "\n";
std::cout << "Max: " << buffer.max() << "\n";
const SensorData* latest = buffer.get_latest();
if (latest) {
std::cout << "Latest: " << latest->value << " at t=" << latest->timestamp << "\n";
}
}
int main() {
state_machine_demo();
ring_buffer_demo();
register_map_demo();
lookup_table_demo();
static_string_demo();
sensor_buffer_demo();
std::cout << "\n=== Key Takeaways ===\n";
std::cout << "1. std::array perfect for fixed-size collections\n";
std::cout << "2. State tables, ring buffers, lookup tables all benefit\n";
std::cout << "3. Zero overhead, full type safety\n";
std::cout << "4. Easy integration with C APIs via .data()\n";
std::cout << "5. Compile-time size enables template optimizations\n";
return 0;
}