============================================================
Linked List
============================================================
Introduction
============================
Overview
-----------------------------
This document describes the **Linked List** component of the Easy Embedded Framework.
The linked list is a fundamental data structure that provides dynamic memory organization
and efficient insertion/removal operations.
What This Component Does
~~~~~~~~~~~~~~~~~~~~~~~~~
The linked list component provides a **doubly-linked circular list implementation**
inspired by the Linux kernel's list implementation. It enables:
- Dynamic collection of nodes without fixed size constraints
- Forward and backward traversal through the list
- Embedding list nodes directly into user-defined structures
- Generic container for organizing data of any type
What This Component Does NOT Do
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Does NOT provide automatic memory allocation (nodes must be pre-allocated or managed by user)
- Does NOT include sorting or searching algorithms
- Does NOT provide thread-safety or locking mechanisms
- Does NOT manage data payload directly (only manages the linking structure)
Component's Structure
============================
Architecture Overview
-----------------------------
The linked list component consists of:
1. **Core Data Structure**: ``struct Node`` - metadata holding next and previous pointers
2. **Helper Macros**: Convenience macros for common operations (iteration, insertion, extraction)
3. **Public API Functions**: Core operations like append, insert, unlink, and search
4. **Design Pattern**: Intrusive linked list (nodes embedded in user structures)
The intrusive design allows the list node to be embedded anywhere in a user-defined structure,
avoiding separate allocation for the list metadata.
Node Structure
~~~~~~~~~~~~~~
The node structure is minimal and efficient:
.. code-block:: c
struct Node {
struct Node * next; // Pointer to next node in the list
struct Node * prev; // Pointer to previous node in the list
};
Circular List Design
~~~~~~~~~~~~~~~~~~~~~
The linked list uses a **circular doubly-linked** design where:
- The list is represented by a head/sentinel node
- The head node's ``next`` points to the first data node
- The head node's ``prev`` points to the last data node
- An empty list has the head pointing to itself
- This eliminates special cases for list boundaries
Visual representation of a circular list with 3 nodes:
.. mermaid::
graph LR
Head["Head
(Sentinel Node)"]
Node1["Node 1"]
Node2["Node 2"]
Node3["Node 3"]
Head -->|next| Node1
Node1 -->|next| Node2
Node2 -->|next| Node3
Node3 -->|next| Head
Head -.->|prev| Node3
Node3 -.->|prev| Node2
Node2 -.->|prev| Node1
Node1 -.->|prev| Head
Intrusive Design Pattern
~~~~~~~~~~~~~~~~~~~~~~~~
Instead of wrapping data in a node, the node is embedded in the user's structure:
.. code-block:: c
// User-defined structure containing a list node
struct Task {
uint32_t task_id;
void (*handler)(void);
struct Node list_node; // Embedded node
};
// To get the parent structure from a node:
struct Task *task = EZ_LINKEDLIST_GET_PARENT_OF(node, list_node, struct Task);
This approach provides:
- **Memory Efficiency**: No extra allocation for list metadata
- **Flexibility**: One structure can participate in multiple lists (multiple embedded nodes)
- **Cache Locality**: Data and list metadata reside close together
Component's Behavior
============================
External Behavior
-----------------------------
The component provides the following operations:
**Initialization**
.. code-block:: c
void ezLinkedList_InitNode(struct Node* node);
Initializes a new node with both ``next`` and ``prev`` pointing to itself (empty single-node state).
**List Operations**
.. code-block:: c
// Append new_node after node in the list
bool ezLinkedList_AppendNode(struct Node *new_node, struct Node *node);
// Insert new_node at the head position
struct Node *ezLinkedList_InsertNewHead(struct Node *current_head,
struct Node *new_node);
// Remove the head node from the list
struct Node *ezLinkedList_UnlinkCurrentHead(struct Node *head);
**Query Operations**
.. code-block:: c
// Get the number of nodes in the list
uint16_t ezLinkedList_GetListSize(struct Node* list_head);
// Check if a node exists in the list
bool ezLinkedList_IsNodeInList(struct Node *head, struct Node *searched_node);
**Helper Macros**
.. code-block:: c
// Iterate through all nodes in a list
EZ_LINKEDLIST_FOR_EACH(node, head)
// Initialize a node (compile-time)
EZ_LINKEDLIST_INIT_NODE(name)
// Add node at head position
EZ_LINKEDLIST_ADD_HEAD(list_head, node)
// Add node at tail position
EZ_LINKEDLIST_ADD_TAIL(list_head, node)
// Unlink a node from the list
EZ_LINKEDLIST_UNLINK_NODE(node)
// Check if list is empty
IS_LIST_EMPTY(list_head)
// Get the parent structure from an embedded node
EZ_LINKEDLIST_GET_PARENT_OF(ptr, member, type)
State Diagram
~~~~~~~~~~~~~
A single node's state transitions:
.. mermaid::
stateDiagram-v2
[*] --> Initialized: ezLinkedList_InitNode()
Initialized --> InList: AppendNode()
InsertNewHead()
InList --> Initialized: UnlinkCurrentHead()
UNLINK_NODE()
Initialized --> [*]
Operation Sequences
~~~~~~~~~~~~~~~~~~~
**Appending a Node**
.. mermaid::
sequenceDiagram
participant New as New Node
participant Ref as Reference Node
participant Next as Next Node
Note over New: new_node->next
new_node->prev
Note over Ref: ref_node
Note over Next: next_node
New->>Ref: Get ref->next
activate Ref
Ref-->>New: Returns next_node
deactivate Ref
Note over New: 1. new_node->next = next_node
Note over New: 2. new_node->prev = ref_node
Note over Next: 3. next_node->prev = new_node
Note over Ref: 4. ref_node->next = new_node
**List Iteration**
.. mermaid::
flowchart TD
A["Start: node = head->next"] --> B{"node != head?"}
B -->|Yes| C["Process node"]
C --> D["node = node->next"]
D --> B
B -->|No| E["End"]
Internal Behavior
-----------------
Circular Structure Advantages
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The circular design eliminates special cases:
1. **No NULL checks for end of list**: The sentinel wraps around to the beginning
2. **Same operation for head and tail**: Both use the same append mechanism
3. **Efficient empty list check**: Simply compare ``head->next == head``
Append Operation Details
~~~~~~~~~~~~~~~~~~~~~~~~
The ``ezLinkedList_AppendNode()`` function inserts ``new_node`` after a given node:
.. code-block:: c
bool ezLinkedList_AppendNode(struct Node* new_node, struct Node* node)
{
if (new_node != NULL && node != NULL)
{
new_node->next = node->next; // 1. new_node points to node's next
new_node->prev = node; // 2. new_node's prev points to node
node->next->prev = new_node; // 3. old next node's prev points to new_node
node->next = new_node; // 4. node's next points to new_node
return true;
}
return false;
}
This operation is atomic and maintains list integrity.
Component's Data Type
============================
Node Structure Definition
--------------------------
.. code-block:: c
struct Node {
struct Node * next; /**< pointer to the next node in a linked list*/
struct Node * prev; /**< pointer to the previous node in a linked list*/
};
**Properties:**
- **Size**: 2 × pointer size (typically 16 bytes on 64-bit systems)
- **Alignment**: Pointer-aligned
- **Initialization**: Via ``ezLinkedList_InitNode()`` or ``EZ_LINKEDLIST_INIT_NODE()``
- **Lifecycle**: Created by user, managed through API functions
Type Safety Mechanisms
~~~~~~~~~~~~~~~~~~~~~~~
The intrusive design is type-safe through the ``EZ_LINKEDLIST_GET_PARENT_OF`` macro:
.. code-block:: c
#define OFFSET(type, member) \
((char*)&(((type*)0)->member) - (char*)((type*)0))
#define EZ_LINKEDLIST_GET_PARENT_OF(ptr, member, type) \
(type*)((char*)ptr - OFFSET(type, member))
This macro:
1. Calculates the byte offset of the member within the type
2. Subtracts this offset from the node pointer to get the parent structure
3. Casts to the correct type
4. Provides compile-time type checking
Configuration
--------------
The linked list is conditionally compiled based on:
.. code-block:: c
#if (EZ_LINKEDLIST == 1U)
// Linked list code is compiled
#endif
Enable linked list support in the target configuration header: ``ez_target_config.h``