F´ 框架 ExternalStack 完全指南:使用外部存储的 LIFO 栈模板
2026/9/15 12:22:24 网站建设 项目流程

F´ 框架 ExternalStack 完全指南:使用外部存储的 LIFO 栈模板

【免费下载链接】fprimeF´ - A flight software and embedded systems framework项目地址: https://gitcode.com/GitHub_Trending/fpr/fprime

ExternalStack是 F´ 飞行软件与嵌入式系统框架中定义于Fw/DataStructures基础数据结构库的一个final类模板,它实现了一个使用外部存储的后进先出(LIFO)栈。本文以 ExternalStack.md 为骨架,结合其头文件实现与单元测试,系统讲解该类模板的模板参数、继承关系、全部构造函数、成员函数、静态辅助函数及测试验证,帮助你在 F´ 组件中正确地用它管理静态分配的内存与栈式数据。

1. 概览:什么是 ExternalStack

ExternalStack表示一个带有外部存储的栈:栈本身不持有元素数据的内存,而是通过内部维护的一个 ExternalArray 来引用调用者提供的后备存储(backing storage)。这种"外部存储"设计在嵌入式/飞行软件中非常实用——内存往往由上层系统静态分配或通过内存池统一管理,数据结构本身只负责组织与访问逻辑。

从 ExternalStack.hpp 的声明可以看出,它是一个模板类并被标记为final

template <typename T> class ExternalStack final : public StackBase<T> {

final关键字意味着它不能被继续继承,配合完整的接口实现,保证了栈语义的封闭与确定。

2. 类层次与依赖关系

ExternalStack<T>公开继承自StackBase,而StackBase又是 SizedContainer 的派生类(见 StackBase.hpp)。三者共同构成 F´ 栈数据结构家族的抽象骨架:

各层职责划分如下:

  • SizedContainer:最顶层的抽象容器接口,定义了clear()getSize()getCapacity()三个纯虚函数,并提供基于它们的isEmpty()isFull()便捷方法(见 SizedContainer.hpp)。它删除了拷贝构造与赋值运算符,将"如何复制"交给具体实现决定。
  • StackBase<T>:栈的抽象基类模板,将atpushpop声明为纯虚函数,并基于这些原语实现了非虚的peek(带默认索引 0 的窥视)与copyDataFrom(跨栈数据拷贝)方法(见 StackBase.hpp)。
  • ExternalStack<T>:栈接口的具体实现,内部组合一个ExternalArray<T>来持有元素。

这里有一个值得注意的细节:StackBase虽然删除了拷贝构造和operator=(见 StackBase.hpp),但ExternalStack通过浅拷贝语义自行实现了拷贝构造与赋值——复制的是对外部数组的引用(指针与容量),而非元素数据。这点将在第 5 节详细展开。

3. 模板参数

ExternalStack只有一个模板参数:

KindNamePurpose
typenameT栈中元素(item)的类型

T有一个静态约束:ExternalArray<T>static_assert(std::is_assignable<T&, T>::value, ...)要求T可赋值(见 ExternalArray.hpp),因为栈操作(pushpop)依赖对T的赋值。

4. 私有成员变量

ExternalStack内部只有两个私有成员(见 ExternalStack.hpp):

NameTypePurposeDefault Value
m_itemsExternalArray<T>存储栈元素的数组C++ 默认初始化({},即空指针 + 0 容量)
m_sizeFwSizeType栈中当前元素数量0

m_items是"外部存储"的载体:它自身只保存指向元素内存的指针m_elements与容量m_size(见 ExternalArray.hpp),并不拥有内存的所有权语义上的分配;m_size则记录当前栈顶的位置,与m_items的容量相互配合。FwSizeType是 F´ 框架定义的无符号大小类型。

5. 构造函数与析构函数

ExternalStack提供四个构造函数与一个默认析构函数,覆盖了"先构造后设存储""构造时绑定存储""无类型字节存储"与"拷贝"四种典型用法。

5.1 零参数构造函数

ExternalStack()

将所有成员初始化为默认值(见 ExternalStack.hpp):此时栈为空、容量为 0,不能直接进行push操作,需要先调用setStorage绑定后备存储。

示例:

ExternalStack<U32> stack; // 容量为 0,大小为 0

5.2 提供类型化后备存储的构造函数

ExternalStack(T* items, FwSizeType capacity)

items必须指向一个至少包含capacityT类型元素的原生数组。该构造函数执行:

  1. 调用setStorage(items, capacity)
  2. 其余成员变量初始化为默认值。

示例:

constexpr FwSizeType capacity = 10; U32 items[capacity]; ExternalStack<U32> stack(items, capacity);

5.3 提供无类型后备存储的构造函数

ExternalStack(ByteArray data, FwSizeType capacity)

当内存以字节形式(如内存池中的缓冲)提供时使用。data必须满足两个约束:

  • getByteArrayAlignment()返回的对齐值对齐;
  • 至少包含getByteArraySize(capacity)字节。

其实现为:调用setStorage(data, capacity),其余成员默认初始化。

示例:

constexpr FwSizeType capacity = 10; constexpr U8 alignment = ExternalStack<U32>::getByteArrayAlignment(); constexpr FwSizeType byteArraySize = ExternalStack<U32>::getByteArraySize(capacity); alignas(alignment) U8 bytes[byteArraySize]; ExternalStack<U32> stack(ByteArray(&bytes[0], sizeof bytes), capacity);

5.4 拷贝构造函数

ExternalStack(const ExternalStack<T>& stack)

执行*this = stack(见 ExternalStack.hpp)。与常规"深拷贝元素"不同,这里复制的是对外部数组的引用:目标栈将共享源栈的后备存储指针与容量,同时复制m_size

示例:

constexpr FwSizeType capacity = 3; U32 items[capacity]; // 调用提供后备存储的构造函数 ExternalStack<U32> q1(items, capacity); // 压入一个元素 U32 value = 42; (void) q1.push(value); // 调用拷贝构造函数 ExternalStack<U32> q2(q1); ASSERT_EQ(q2.getSize(), 1);

5.5 析构函数

~ExternalStack() override

定义为= default(见 ExternalStack.hpp)。它不释放后备存储,因为存储不属于栈所有。

6. 核心成员函数

6.1 operator=(拷贝赋值)

ExternalStack<T>& operator=(const ExternalStack<T>& stack)

语义与拷贝构造一致,算法为(见 ExternalStack.hpp):

  1. &stack != this
    • m_items = stack.m_itemsExternalArray的赋值会重新指向源数组的指针与容量);
    • m_size = stack.m_size
  2. 返回*this

示例:

constexpr FwSizeType capacity = 3; U32 items[capacity]; // 调用提供后备存储的构造函数 ExternalStack<U32> q1(items, capacity); // 压入一个元素 U32 value = 42; (void) q1.push(value); // 调用默认构造函数 ExternalStack q2; ASSERT_EQ(q2.getSize(), 0); // 调用拷贝赋值运算符 q2 = q1; ASSERT_EQ(q2.getSize(), 1);

6.2 clear

void clear() override

m_size置为 0(见 ExternalStack.hpp)。注意它只重置逻辑大小,清空或释放后备存储中的元素内容。

示例:

constexpr FwSizeType capacity = 10; U32 items[capacity]; ExternalStack<U32> stack(items, capacity); const auto status = stack.push(3); ASSERT_EQ(stack.getSize(), 1); stack.clear(); ASSERT_EQ(stack.getSize(), 0);

6.3 setStorage(类型化数据)

void setStorage(T* items, FwSizeType capacity)

items必须指向至少capacityT元素的原生数组。算法(见 ExternalStack.hpp):

  1. 调用m_items.setStorage(items, capacity)
  2. 调用this->clear()

在底层,ExternalArray::setStorage会先断言"非空数组时指针不能为空",并在传入指针与既有存储指针相同时避免释放存储(见 ExternalArray.hpp)。

示例:

constexpr FwSizeType capacity = 10; ExternalStack<U32> stack; // 先默认构造 U32 items[capacity]; stack.setStorage(items, capacity);

6.4 setStorage(无类型数据)

void setStorage(ByteArray data, FwSizeType capacity)

data需满足与 5.3 相同的对齐与字节数约束。算法为:调用m_items.setStorage(data, capacity),再调用this->clear()

在底层,ExternalArray::setStorage的字节版本会依次断言:字节指针非空、指针按alignof(T)对齐、size <= data.size / sizeof(T)(采用除法形式避免size * sizeof(T)溢出FwSizeType),随后就地 placement-new 构造每个元素(见 ExternalArray.hpp),确保每个元素都是合法对象可供后续赋值。

示例:

constexpr FwSizeType capacity = 10; constexpr U8 alignment = ExternalStack<U32>::getByteArrayAlignment(); constexpr FwSizeType byteArraySize = ExternalStack<U32>::getByteArraySize(capacity); alignas(alignment) U8 bytes[byteArraySize]; ExternalStack<U32> stack; // 先默认构造 stack.setStorage(ByteArray(&bytes[0], sizeof bytes), capacity);

6.5 push

Success push(const T& e) override

将元素压入栈顶。算法(见 ExternalStack.hpp):

  1. status = Success::FAILURE
  2. m_size < getCapacity()
    • m_items[m_size] = e
    • 递增m_size
    • status = Success::SUCCESS
  3. 返回status

栈满时push返回Success::FAILURE而不是触发断言,这是嵌入式友好接口的典型设计——调用方可以安全地根据返回值处理溢出。

示例:

constexpr FwSizeType capacity = 3; U32 items[capacity]; ExternalStack<U32> stack(items, capacity); ASSERT_EQ(stack.getSize(), 0); auto status = stack.push(42); ASSERT_EQ(status, Success::SUCCESS); ASSERT_EQ(stack.getSize(), 1);

6.6 pop

Success pop(T& e) override

从栈顶弹出元素。算法(见 ExternalStack.hpp):

  1. status = Success::FAILURE
  2. m_size > 0
    • e = this->at(0)(取最右侧、即最新元素);
    • 递减m_size
    • status = Success::SUCCESS
  3. 返回status

对空栈调用pop同样返回FAILURE,不会崩溃。

示例:

constexpr FwSizeType capacity = 3; U32 items[capacity]; ExternalStack<U32> stack(items, capacity); U32 val; auto status = stack.pop(val); ASSERT_EQ(status, Success::FAILURE); // 空栈 status = stack.push(42); ASSERT_EQ(status, Success::SUCCESS); status = stack.pop(val); ASSERT_EQ(status, Success::SUCCESS); ASSERT_EQ(val, 42);

6.7 at

const T& at(FwSizeType index) const override

按索引访问元素。索引语义在 StackBase 中明确规定:索引 0 是栈最右侧(最新压入)的元素,索引递增方向从右往左(见 ExternalStack.hpp)。算法:

  1. 断言index < m_size(越界直接触发FW_ASSERT);
  2. 返回m_items[m_size - 1 - index]

示例:

constexpr FwSizeType capacity = 3; U32 items[capacity]; ExternalStack<U32> stack(items, capacity); const auto status = stack.push(3); ASSERT_EQ(status, Success::SUCCESS); ASSERT_EQ(stack.at(0), 3); ASSERT_DEATH(stack.at(1), "Assert"); // 越界断言

6.8 getSize

FwSizeType getSize() const override

返回当前栈中元素数量m_size

示例:

constexpr FwSizeType capacity = 10; U32 items[capacity]; ExternalStack<U32> stack(items, capacity); auto size = stack.getSize(); ASSERT_EQ(size, 0); const auto status = stack.push(3); ASSERT_EQ(status, Success::SUCCESS); size = stack.getSize(); ASSERT_EQ(size, 1);

6.9 getCapacity

FwSizeType getCapacity() const override

返回栈的容量,即m_items.getSize()(见 ExternalStack.hpp)。

示例:

constexpr FwSizeType capacity = 10; U32 items[capacity]; ExternalStack<U32> stack(items, capacity); ASSERT_EQ(stack.getCapacity(), capacity);

6.10 继承自基类的便捷方法

除上述覆写外,ExternalStack还直接继承了两个由StackBase实现的方法:

  • peek(T& e, FwSizeType index = 0) const:窥视指定索引元素而不弹出;索引越界时返回Success::FAILURE(见 StackBase.hpp)。
  • copyDataFrom(const StackBase<T>& stack):将另一栈的元素拷贝到当前栈,取两者 size/capacity 的较小者,逐元素push并断言成功(见 StackBase.hpp)。

7. 公共静态函数

7.1 getByteArrayAlignment

static constexpr U8 getByteArrayAlignment()

返回ExternalArray<T>::getByteArrayAlignment(),即alignof(T)(见 ExternalArray.hpp)。用于计算无类型后备存储所需的对齐。

7.2 getByteArraySize

static constexpr FwSizeType getByteArraySize(FwSizeType capacity)

返回ExternalArray<T>::getByteArraySize(capacity),即capacity * sizeof(T)(见 ExternalArray.hpp)。用于计算无类型后备存储所需的字节数。两个函数均为constexpr,可在编译期用于静态数组声明(见 5.3 的示例)。

8. 测试验证:行为规格的机器可读化

ExternalStack的行为不仅由文档描述,还有完整的单元测试在 test/ut/ExternalStackTest.cpp 中固化为可执行断言:

  • 构造测试ZeroArgConstructor验证默认构造容量/大小均为 0;TypedStorageConstructorUntypedStorageConstructor分别验证两种后备存储构造函数正确接管了外部数组指针、容量正确且初始大小 0(见 ExternalStackTest.cpp)。
  • 拷贝语义测试CopyConstructorCopyAssignmentOperator验证拷贝后getSize() == 1且共享同一后备存储指针;CopyDataFrom则验证size1 < capacity2size1 == capacity2size1 > capacity2三种跨容量拷贝场景(见 ExternalStackTest.cpp)。
  • 场景测试:基于 STest 规则/场景框架分别覆盖atclearpeekpopEmptypopOKpushFullpushOK,并提供一个执行 1000 次随机操作的Random场景(见 ExternalStackTest.cpp)。

测试辅助类 ExternalStackTester.hpp 通过friend声明访问了ExternalStack的私有成员m_items(见 ExternalStack.hpp),从而可以断言内部ExternalArray的具体状态。此外,内部存储版本Stack<T, C>的测试 test/ut/StackTest.cpp 同样基于相同语义,可与ExternalStack的测试互为印证。

9. 使用场景与注意事项

典型使用场景:在 F´ 的 active/queued 组件内部用ExternalStack保存临时待处理项,后备存储来自静态数组或内存池缓冲区。F´ 官方文档 sdd.md 同时指出,Fw/DataStructures中的数据结构都是**顺序型(非线程安全)**的;在多线程上下文中使用时,必须借助外部并发控制——最常见做法是将数据结构作为 active/queued 组件的成员,用组件队列来串行化对其的访问。

关键注意事项

  1. 存储所有权ExternalStack不拥有后备存储,析构、clear均不释放内存;释放内存的责任始终在调用方。
  2. 拷贝是引用共享:拷贝构造/赋值复制的是对外部数组的引用而非元素,两个栈会指向同一后备存储,修改一侧会影响另一侧;StackBase中删除拷贝构造的注释也明确写着"行为取决于实现"。
  3. 越界即断言at越界会触发FW_ASSERT(在单元测试中对应ASSERT_DEATH);而push满栈、pop空栈则通过返回值Success::FAILURE优雅处理,使用时务必检查返回值。
  4. 字节存储的约束:无类型存储必须满足对齐与最小字节数两个硬约束,违反会在setStorage阶段触发断言。

结合 ExternalArray 的完整实现,ExternalStack构成了 F´ 基础数据结构库中"栈 + 外部内存"的参考实现,是理解整个 Fw/DataStructures 库"内部存储/外部存储"双轨设计的最佳入口之一。

【免费下载链接】fprimeF´ - A flight software and embedded systems framework项目地址: https://gitcode.com/GitHub_Trending/fpr/fprime

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询