A doubly-linked list implementation designed specifically for the DMOD (Dynamic Modules) framework. dmlist provides a simple and efficient linked list data structure with module-aware memory management for embedded systems.
- Overview
- What is DMOD?
- What is dmlist?
- Architecture
- Features
- Building
- Testing
- Usage
- API Reference
- Examples
- Contributing
- License
dmlist is a doubly-linked list library that integrates seamlessly with the DMOD dynamic module system. It provides module-aware memory management through DMOD's allocation API, making it ideal for use in embedded systems where memory tracking and module lifecycle management are critical.
DMOD (Dynamic Modules) is a library that enables dynamic loading and unloading of modules in embedded systems at runtime. It allows you to:
- Dynamically load modules: Load functionality from
.dmffiles without recompiling - Manage dependencies: Automatically handle module dependencies
- Inter-module communication: Modules can communicate via a common API
- Resource management: Efficiently manage system resources
- Safe updates: Update individual modules without affecting the entire system
DMOD provides a modular architecture that makes embedded systems more flexible, maintainable, and easier to extend. For more information, visit the DMOD repository.
dmlist is a doubly-linked list implementation specifically designed to work with DMOD. It provides:
- Module-aware allocation: All memory allocations are tracked by module name through DMOD's memory API
- Doubly-linked structure: Supports efficient forward and backward traversal
- Generic data storage: Stores void pointers, allowing any data type to be stored
- Rich API: Supports common operations like push, pop, insert, remove, find, and iteration
- Automatic cleanup: When used with DMOD's memory management, lists can be cleaned up automatically when modules are unloaded
- Zero external dependencies: Only requires the DMOD framework
dmlist uses a doubly-linked list structure with head and tail pointers for efficient operations:
┌─────────────────────────────────────────────────┐
│ dmlist Context │
├─────────────────────────────────────────────────┤
│ - head: dmlist_node_t* ───────┐ │
│ - tail: dmlist_node_t* ───┐ │ │
│ - size: size_t │ │ │
│ - module_name: char[] │ │ │
└──────────────────────────────┼───┼──────────────┘
│ │
┌───────────┘ └───────────┐
▼ ▼
┌────────────┐ ┌────────────┐
│ Node 1 │◄───────►│ Node 2 │◄───►...
├────────────┤ ├────────────┤
│ data: void*│ │ data: void*│
│ next: * │────────►│ next: * │
│ prev: * │◄────────│ prev: * │
└────────────┘ └────────────┘
Each node contains:
- data: Pointer to the user data (void* for generic storage)
- next: Pointer to the next node in the list
- prev: Pointer to the previous node in the list
All memory allocations in dmlist go through DMOD's memory API (Dmod_Malloc and Dmod_Free). This allows:
- Module tracking: Every allocation is associated with the module that created the list
- Automatic cleanup: When a module is unloaded, all its list allocations can be freed automatically
- Memory profiling: Track memory usage per module
- Consistent allocation: Use the same allocator as the rest of the DMOD ecosystem
- ✅ Doubly-linked list: Efficient forward and backward traversal
- ✅ Module-aware allocations: All allocations tracked by DMOD module system
- ✅ Generic storage: Stores void pointers for any data type
- ✅ Push/Pop operations: Add/remove from front or back in O(1) time
- ✅ Insert/Remove at position: Support for arbitrary position operations
- ✅ Find operation: Search for elements with custom comparison function
- ✅ Iteration: Foreach with callback function support
- ✅ Size tracking: Constant time size queries
- ✅ Clear operation: Remove all elements efficiently
- ✅ Comprehensive logging: Integration with DMOD logging system
- ✅ Zero external dependencies: Only requires DMOD framework
- CMake: Version 3.10 or higher
- C Compiler: GCC or compatible
- Make: For Makefile-based builds (optional)
# Clone the repository
git clone https://github.com/choco-technologies/dmlist.git
cd dmlist
# Create build directory
mkdir build
cd build
# Configure
cmake ..
# Build
make
# Run tests
ctest --verboseYou can customize the build with these CMake options:
# Enable tests
cmake -DDMLIST_BUILD_TESTS=ON ..
# Enable code coverage
cmake -DENABLE_COVERAGE=ON ..
# Change DMOD mode
cmake -DDMOD_MODE=DMOD_EMBEDDED ..dmlist also supports traditional Makefile builds:
# Build the library
make
# The library will be created as libdmlist.aThe Makefile is automatically generated by CMake and includes necessary DMOD paths.
To have the dmlist API placed in the DMOD inputs section (.dmod.inputs linker section), you need to enable external registration. This is useful when you want dmlist to be part of the DMOD builtin modules.
Add the DMOD_EXTERNAL_REGISTRATION compile definition to your build configuration:
Using CMake:
# In your CMakeLists.txt
target_compile_definitions(dmod PRIVATE DMOD_EXTERNAL_REGISTRATION)Using command line:
cmake -DCMAKE_C_FLAGS="-DDMOD_EXTERNAL_REGISTRATION" ..The dmlist library already has DMOD_ENABLE_REGISTRATION defined at the top of src/dmlist.c. This is required to register the API functions in the .dmod.inputs section.
If you're creating your own builtin module following the dmlist pattern, ensure that you define DMOD_ENABLE_REGISTRATION before including any headers in your source file:
#define DMOD_ENABLE_REGISTRATION
#include "dmlist.h"
// ... rest of the implementationDMOD_EXTERNAL_REGISTRATIONtellsdmod_system.cnot to enable API registration internally, allowing external modules like dmlist to handle their own registrationDMOD_ENABLE_REGISTRATIONenables the actual registration of API functions in the.dmod.inputslinker section- This allows the DMOD system to discover and connect dmlist APIs at runtime
For more details, see the DMOD Builtin Module Creation documentation.
dmlist includes basic test suites:
- test_simple: Basic smoke tests
- Initialization
- Push/Pop operations
- Size queries
- Clear operation
- Destruction
# Run all tests
cd build
ctest
# Run with verbose output
ctest --verbose
# Run specific test
./tests/test_simple=== Simple DMLIST Test ===
Init: PASS
Is Empty: PASS
Push Back: PASS
Size: PASS
Front: PASS
Back: PASS
Pop Front: PASS
Pop Back: PASS
Clear: PASS
Destroy: PASS
All simple tests completed!
#include "dmlist.h"
#include <stdio.h>
int main(void) {
// 1. Create a list
dmlist_context_t* list = dmlist_create("main");
if (list == NULL) {
return -1;
}
// 2. Add elements
int value1 = 10;
int value2 = 20;
dmlist_push_back(list, &value1);
dmlist_push_back(list, &value2);
// 3. Access elements
int* front = (int*)dmlist_front(list);
printf("Front: %d\n", *front); // Output: Front: 10
// 4. Get list size
printf("Size: %zu\n", dmlist_size(list)); // Output: Size: 2
// 5. Remove elements
int* popped = (int*)dmlist_pop_front(list);
printf("Popped: %d\n", *popped); // Output: Popped: 10
// 6. Clean up
dmlist_destroy(list);
return 0;
}#include "dmlist.h"
#include <stdio.h>
#include <string.h>
void string_list_example(void) {
dmlist_context_t* list = dmlist_create("string_module");
// Add strings to the list
dmlist_push_back(list, "Hello");
dmlist_push_back(list, "World");
dmlist_push_back(list, "!");
// Print all strings
for (size_t i = 0; i < dmlist_size(list); i++) {
const char* str = (const char*)dmlist_get(list, i);
printf("%s ", str);
}
printf("\n"); // Output: Hello World !
dmlist_destroy(list);
}#include "dmlist.h"
#include <string.h>
// Comparison function for strings
int compare_strings(const void* a, const void* b) {
return strcmp((const char*)a, (const char*)b);
}
// Comparison function for integers
int compare_ints(const void* a, const void* b) {
int ia = *(const int*)a;
int ib = *(const int*)b;
return ia - ib;
}
void find_example(void) {
dmlist_context_t* list = dmlist_create("search_module");
// Add some values
int values[] = {10, 20, 30, 40, 50};
for (int i = 0; i < 5; i++) {
dmlist_push_back(list, &values[i]);
}
// Find a specific value
int search_val = 30;
int* found = (int*)dmlist_find(list, &search_val, compare_ints);
if (found != NULL) {
printf("Found: %d\n", *found); // Output: Found: 30
}
dmlist_destroy(list);
}#include "dmlist.h"
#include <stdio.h>
// Iterator callback function
bool print_int(void* data, void* user_data) {
int* value = (int*)data;
printf("%d ", *value);
return true; // Continue iteration
}
// Iterator with sum accumulator
bool sum_accumulator(void* data, void* user_data) {
int* value = (int*)data;
int* sum = (int*)user_data;
*sum += *value;
return true;
}
void iteration_example(void) {
dmlist_context_t* list = dmlist_create("iter_module");
// Add values
int values[] = {1, 2, 3, 4, 5};
for (int i = 0; i < 5; i++) {
dmlist_push_back(list, &values[i]);
}
// Print all values
printf("Values: ");
dmlist_foreach(list, print_int, NULL);
printf("\n"); // Output: Values: 1 2 3 4 5
// Calculate sum
int sum = 0;
dmlist_foreach(list, sum_accumulator, &sum);
printf("Sum: %d\n", sum); // Output: Sum: 15
dmlist_destroy(list);
}#include "dmlist.h"
#include <stdio.h>
void insert_example(void) {
dmlist_context_t* list = dmlist_create("insert_module");
int values[] = {1, 2, 4, 5};
// Build initial list: 1, 2, 4, 5
for (int i = 0; i < 4; i++) {
dmlist_push_back(list, &values[i]);
}
// Insert 3 at position 2 (between 2 and 4)
int val = 3;
dmlist_insert(list, 2, &val);
// Now list is: 1, 2, 3, 4, 5
printf("List after insert: ");
for (size_t i = 0; i < dmlist_size(list); i++) {
int* v = (int*)dmlist_get(list, i);
printf("%d ", *v);
}
printf("\n"); // Output: List after insert: 1 2 3 4 5
dmlist_destroy(list);
}#include "dmlist.h"
#include <string.h>
int compare_strings(const void* a, const void* b) {
return strcmp((const char*)a, (const char*)b);
}
void remove_example(void) {
dmlist_context_t* list = dmlist_create("remove_module");
// Add elements
dmlist_push_back(list, "apple");
dmlist_push_back(list, "banana");
dmlist_push_back(list, "cherry");
dmlist_push_back(list, "date");
// Remove "banana"
const char* to_remove = "banana";
bool removed = dmlist_remove(list, to_remove, compare_strings);
printf("Removed: %s\n", removed ? "yes" : "no"); // Output: Removed: yes
// Remove element at position 1
const char* removed_val = (const char*)dmlist_remove_at(list, 1);
printf("Removed at pos 1: %s\n", removed_val); // Output: Removed at pos 1: cherry
dmlist_destroy(list);
}#include "dmlist.h"
#include <stdio.h>
#include <string.h>
typedef struct {
char name[50];
int age;
float score;
} Student;
int compare_students_by_age(const void* a, const void* b) {
const Student* sa = (const Student*)a;
const Student* sb = (const Student*)b;
return sa->age - sb->age;
}
bool print_student(void* data, void* user_data) {
Student* s = (Student*)data;
printf(" %s (age: %d, score: %.1f)\n", s->name, s->age, s->score);
return true;
}
void struct_example(void) {
dmlist_context_t* list = dmlist_create("student_module");
// Create students
Student s1 = {"Alice", 20, 85.5};
Student s2 = {"Bob", 22, 90.0};
Student s3 = {"Charlie", 19, 88.5};
// Add to list
dmlist_push_back(list, &s1);
dmlist_push_back(list, &s2);
dmlist_push_back(list, &s3);
// Print all students
printf("All students:\n");
dmlist_foreach(list, print_student, NULL);
// Find a specific student
Student search = {"", 22, 0};
Student* found = (Student*)dmlist_find(list, &search, compare_students_by_age);
if (found != NULL) {
printf("\nFound student aged 22: %s\n", found->name);
}
dmlist_destroy(list);
}dmlist_context_t* dmlist_create(const char* module_name);Create a linked list context.
- Parameters:
module_name: Name of the module using the list (for memory tracking)
- Returns: Pointer to the list context, or
NULLif creation fails - Note: All memory allocations for this list will be tracked under
module_name
void dmlist_destroy(dmlist_context_t* ctx);Destroy a linked list and free all its nodes.
- Parameters:
ctx: Pointer to the list context
- Note: This frees all node structures, but does NOT free the data pointers stored in the nodes
size_t dmlist_size(dmlist_context_t* ctx);Get the number of elements in the list.
- Parameters:
ctx: Pointer to the list context
- Returns: Number of elements in the list
- Time Complexity: O(1)
bool dmlist_is_empty(dmlist_context_t* ctx);Check if the list is empty.
- Parameters:
ctx: Pointer to the list context
- Returns:
trueif the list is empty,falseotherwise - Time Complexity: O(1)
bool dmlist_push_front(dmlist_context_t* ctx, void* data);Add an element to the front of the list.
- Parameters:
ctx: Pointer to the list contextdata: Pointer to the data to add
- Returns:
trueif successful,falseotherwise - Time Complexity: O(1)
bool dmlist_push_back(dmlist_context_t* ctx, void* data);Add an element to the back of the list.
- Parameters:
ctx: Pointer to the list contextdata: Pointer to the data to add
- Returns:
trueif successful,falseotherwise - Time Complexity: O(1)
bool dmlist_insert(dmlist_context_t* ctx, size_t position, void* data);Insert an element at a specific position in the list.
- Parameters:
ctx: Pointer to the list contextposition: Position to insert at (0 = front)data: Pointer to the data to insert
- Returns:
trueif successful,falseotherwise - Time Complexity: O(n)
- Note: If position >= size, the element is added to the back
void* dmlist_pop_front(dmlist_context_t* ctx);Remove and return the element at the front of the list.
- Parameters:
ctx: Pointer to the list context
- Returns: Pointer to the data, or
NULLif the list is empty - Time Complexity: O(1)
void* dmlist_pop_back(dmlist_context_t* ctx);Remove and return the element at the back of the list.
- Parameters:
ctx: Pointer to the list context
- Returns: Pointer to the data, or
NULLif the list is empty - Time Complexity: O(1)
void* dmlist_remove_at(dmlist_context_t* ctx, size_t position);Remove an element at a specific position in the list.
- Parameters:
ctx: Pointer to the list contextposition: Position to remove (0 = front)
- Returns: Pointer to the removed data, or
NULLif position is out of bounds - Time Complexity: O(n)
bool dmlist_remove(dmlist_context_t* ctx, const void* data, dmlist_compare_func_t compare_func);Remove an element from the list.
- Parameters:
ctx: Pointer to the list contextdata: Pointer to the data to removecompare_func: Comparison function to use
- Returns:
trueif the element was found and removed,falseotherwise - Time Complexity: O(n)
void dmlist_clear(dmlist_context_t* ctx);Remove all elements from the list.
- Parameters:
ctx: Pointer to the list context
- Time Complexity: O(n)
- Note: This frees all node structures, but does NOT free the data pointers
void* dmlist_front(dmlist_context_t* ctx);Get the element at the front of the list without removing it.
- Parameters:
ctx: Pointer to the list context
- Returns: Pointer to the data, or
NULLif the list is empty - Time Complexity: O(1)
void* dmlist_back(dmlist_context_t* ctx);Get the element at the back of the list without removing it.
- Parameters:
ctx: Pointer to the list context
- Returns: Pointer to the data, or
NULLif the list is empty - Time Complexity: O(1)
void* dmlist_get(dmlist_context_t* ctx, size_t position);Get an element at a specific position in the list.
- Parameters:
ctx: Pointer to the list contextposition: Position to get (0 = front)
- Returns: Pointer to the data, or
NULLif position is out of bounds - Time Complexity: O(n)
void* dmlist_find(dmlist_context_t* ctx, const void* data, dmlist_compare_func_t compare_func);Find an element in the list.
- Parameters:
ctx: Pointer to the list contextdata: Pointer to the data to findcompare_func: Comparison function to use (returns 0 if equal)
- Returns: Pointer to the found data, or
NULLif not found - Time Complexity: O(n)
void dmlist_foreach(dmlist_context_t* ctx, dmlist_iterator_func_t iterator_func, void* user_data);Iterate over all elements in the list.
- Parameters:
ctx: Pointer to the list contextiterator_func: Callback function to call for each element (returnstrueto continue)user_data: User-provided data passed to the iterator function
- Time Complexity: O(n)
typedef bool (*dmlist_iterator_func_t)(void* data, void* user_data);Callback function type for iterating over list elements.
- Parameters:
data: Pointer to the data stored in the nodeuser_data: User-provided data
- Returns:
trueto continue iteration,falseto stop
typedef int (*dmlist_compare_func_t)(const void* data1, const void* data2);Callback function type for comparing list elements.
- Parameters:
data1: Pointer to the first data elementdata2: Pointer to the second data element
- Returns:
0if equal,<0if data1 < data2,>0if data1 > data2
#include "dmlist.h"
#include <stdio.h>
#include <string.h>
typedef struct {
int priority;
char description[100];
} Task;
int compare_priority(const void* a, const void* b) {
const Task* ta = (const Task*)a;
const Task* tb = (const Task*)b;
return ta->priority - tb->priority;
}
void task_queue_example(void) {
dmlist_context_t* queue = dmlist_create("task_queue");
// Add tasks
Task t1 = {1, "Low priority task"};
Task t2 = {5, "High priority task"};
Task t3 = {3, "Medium priority task"};
dmlist_push_back(queue, &t1);
dmlist_push_back(queue, &t2);
dmlist_push_back(queue, &t3);
// Process tasks
while (!dmlist_is_empty(queue)) {
Task* task = (Task*)dmlist_pop_front(queue);
printf("Processing: %s (priority: %d)\n", task->description, task->priority);
}
dmlist_destroy(queue);
}#include "dmlist.h"
#include <stdio.h>
#include <string.h>
#define MAX_HISTORY 10
typedef struct {
dmlist_context_t* list;
size_t max_size;
} History;
History* history_create(void) {
History* hist = (History*)Dmod_Malloc(sizeof(History), "history");
hist->list = dmlist_create("history");
hist->max_size = MAX_HISTORY;
return hist;
}
void history_add(History* hist, const char* command) {
// If at max size, remove oldest
if (dmlist_size(hist->list) >= hist->max_size) {
dmlist_pop_front(hist->list);
}
// Add new command
dmlist_push_back(hist->list, (void*)command);
}
bool print_history_item(void* data, void* user_data) {
int* index = (int*)user_data;
const char* cmd = (const char*)data;
printf("%d: %s\n", (*index)++, cmd);
return true;
}
void history_print(History* hist) {
int index = 0;
dmlist_foreach(hist->list, print_history_item, &index);
}
void history_destroy(History* hist) {
dmlist_destroy(hist->list);
Dmod_Free(hist);
}Contributions are welcome! Please feel free to submit issues, fork the repository, and create pull requests.
- Fork the repository
- Clone your fork:
git clone https://github.com/YOUR_USERNAME/dmlist.git - Create a feature branch:
git checkout -b feature/my-new-feature - Make your changes and add tests
- Run tests:
cd build && ctest - Commit your changes:
git commit -am 'Add some feature' - Push to the branch:
git push origin feature/my-new-feature - Submit a pull request
- Follow the existing code style
- Use meaningful variable and function names
- Add comments for complex logic
- Update documentation for API changes
This project is licensed under the MIT License - see the LICENSE file for details.
MIT License
Copyright (c) 2025 Choco-Technologies
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
- DMOD (Dynamic Modules) - The dynamic module loading framework
- Choco-Technologies team for creating and maintaining this project
- DMOD - Dynamic Module Loading Framework
- dmheap - DMOD Heap Memory Manager
- dmlog - DMOD Logging Library
- dmvfs - DMOD Virtual File System
For more information and support, please visit the dmlist repository or contact the Choco-Technologies team.