12.01 Implementing Bounded FIFO Queues with Modulo Arithmetic for Non-Blocking ISRs
💻 1. Annotated Source Code
#ifndef QUEUE_H #define QUEUE_H class Queue { virtual void enqueue(int newEntry) = 0; virtual int dequeue() = 0; virtual int peekFront() const = 0; virtual bool isEmpty() const = 0; virtual void makeEmpty() = 0; }; #endif
#ifndef ARRAY_QUEUE_H #define ARRAY_QUEUE_H #include <iostream> #include "Queue.h" using namespace std; class ArrayQueue : public Queue { public: ArrayQueue(int s = 16) : MAX_SIZE(s) { front = 0; back = 0; numElements = 0; mArray = new int[MAX_SIZE]; }//end ctor virtual ~ArrayQueue() { delete[] mArray; }//end dtor void enqueue(int newEntry) override { if (numElements < MAX_SIZE - 1) { mArray[back] = newEntry; back = (back + 1) % MAX_SIZE; numElements++; } else { cout << "You cannot enqueue onto a full queue" << endl; } }//end enqueue int dequeue() override { if (!isEmpty()) { int data = mArray[front]; front = (front + 1) % MAX_SIZE; numElements--; return data; } else { cout << "You cannot dequeueon an empty queue." << endl; return 0; } }//end dequeue int peekFront() const override { if (!isEmpty()) { return mArray[front]; } else { cout << "Queue is empty. You cannot peek the front." << endl; return 0; } }//end peekFront bool isEmpty() const override { return numElements == 0; }//end isEmpty void makeEmpty() override { front = 0; back = 0; numElements = 0; }//end makeEmpty private: int* mArray; const int MAX_SIZE; int front; int back; int numElements; }; #endif
#include <iostream> #include "ArrayQueue.h" using namespace std; int main() { ArrayQueue queue; for (int i = 1; i <= 16; i++) { queue.enqueue(i * 100); }//end for queue.enqueue(1234); //should trigger an error while (!queue.isEmpty()) { cout << queue.dequeue() << endl; }//end while queue.dequeue(); //should trigger an error for (int i = 0; i < 20; i++) { cout << "Just enqueued " << (i * 10) << endl; queue.enqueue(i * 10); if (i % 3 == 0) { cout << "Just dequeued " << queue.dequeue() << endl; } } queue.enqueue(123); queue.enqueue(234); queue.enqueue(345); return 0; }
📐 2. Architecture & UML Class Model
📚 3. Core C++ Concepts Deep-Dive
1. Abstract Interface & Array Implementation
The Queue<T> interface defines enqueue, dequeue, peek, and isEmpty. ArrayQueue<T> implements these operations in fixed contiguous memory using circular indexing.
🔄 Circular Ring Buffer FIFO Architecture
2. Modulo Arithmetic Index Wrapping
Instead of shifting elements on dequeue ($O(N)$), the queue simply advances its front and rear indices using modulo arithmetic: (rear + 1) % capacity, achieving constant $O(1)$ enqueue and dequeue.
⚡ 4. Embedded Systems & Hardware Reality
1. Power-of-Two Bitmask Optimization
In high-frequency ISRs, the hardware division instruction (or software division routine on Cortex-M0) required for % capacity takes multiple clock cycles. Embedded engineers dimension ring buffers to powers of two (e.g. 64, 128, 256), replacing expensive modulo with a single-cycle bitwise AND: (index + 1) & (CAPACITY - 1)!
💡 5. Production-Ready Embedded Refactoring
#include <array> #include <cstdint> template <typename T, size_t PowerOfTwoCap = 64> class FastRingBuffer { static_assert((PowerOfTwoCap & (PowerOfTwoCap - 1)) == 0, "Capacity must be power of 2!"); public: bool enqueue(T val) { size_t nextHead = (head_ + 1) & MASK; if (nextHead == tail_) return false; // Full buffer_[head_] = val; head_ = nextHead; return true; } bool dequeue(T& out) { if (head_ == tail_) return false; // Empty out = buffer_[tail_]; tail_ = (tail_ + 1) & MASK; return true; } private: static constexpr size_t MASK = PowerOfTwoCap - 1; std::array<T, PowerOfTwoCap> buffer_; size_t head_ = 0, tail_ = 0; };
📝 Knowledge Verification Quiz
Test your understanding of the C++ concepts and embedded microcontroller trade-offs covered in this guide. Click any option for instant feedback.
index & (capacity - 1) produces identical results to index % capacity in a single fast clock cycle.