unilink provides comprehensive memory safety features to ensure robust and secure applications. This document describes the memory safety architecture, features, and best practices.
Table of Contents
- Overview
- Safe Data Handling
- Thread-Safe State Management
- Memory Tracking
- AddressSanitizer Support
- Best Practices
Overview
Memory Safety Guarantees
| Feature | Description | Performance Impact |
| Bounds Checking | All buffer access validated | Minimal (<1%) |
| Type Safety | Safe conversions prevent UB | Zero (compile-time) |
| Leak Detection | Track allocations/deallocations | Zero (Release builds) |
| Thread Safety | All state management thread-safe | Minimal (~2-5%) |
| Memory Pools | Reduce fragmentation | Positive (+30% for small buffers) |
Safety Levels
unilink provides multiple levels of memory safety:
flowchart TD
L1[Level 1: Compile-Time Safety<br/>- Type safety<br/>- RAII resource management] --> L2
L2[Level 2: Runtime Safety (Release)<br/>- Bounds checking<br/>- Safe conversions<br/>- Thread synchronization] --> L3
L3[Level 3: Debug Safety (Debug)<br/>- Memory tracking<br/>- Allocation tracking<br/>- Leak detection] --> L4
L4[Level 4: Sanitizers (Optional)<br/>- AddressSanitizer<br/>- ThreadSanitizer<br/>- UndefinedBehaviorSanitizer]
Safe Data Handling
SafeDataBuffer
Immutable, type-safe buffer wrapper around existing data:
SafeDataBuffer from_vec(std::vector<uint8_t>{1, 2, 3});
SafeDataBuffer from_str(std::string("hello"));
auto span = from_vec.as_span();
auto byte = from_vec.at(1);
auto unchecked = from_vec.data()[0];
Features
1. Construction Validation
- Rejects null pointers when size > 0
- Rejects buffers >100MB
- Copies incoming data into owned storage
2. Safe Type Conversions
Utility functions prevent undefined behavior:
using namespace unilink::common::safe_convert;
const uint8_t* data = ...;
size_t size = ...;
std::string input = "Hello";
size_t len = input.size();
int value = safe_cast<int>(long_value);
std::string uint8_to_string(const uint8_t *data, size_t size)
Safely convert uint8_t* to const char* for string operations.
std::vector< uint8_t > string_to_uint8(const char *data, size_t size)
Safely convert const char* to const uint8_t* for binary operations.
3. Memory Validation
Comprehensive validation of data integrity:
if (buffer.is_valid()) {
process_data(buffer.data(), buffer.size());
} else {
handle_error();
}
buffer.validate();
Mutation model: Data is populated at construction; no in-place write/append helpers exist. Use clear()/resize()/reserve() only when you control the backing data size.
Safe Span (C++17 Compatible)
Lightweight, non-owning view of contiguous data:
void process_data(safe_span<const uint8_t> data) {
for (size_t i = 0; i < data.size(); i++) {
uint8_t byte = data[i];
process_byte(byte);
}
auto subset = data.subspan(1, 2);
}
std::vector<uint8_t> buffer = {1, 2, 3, 4, 5};
process_data(safe_span<const uint8_t>(buffer));
Features:
- No ownership (lightweight)
at()/subspan() are checked; operator[] is unchecked
- C++17 compatible (pre-C++20
std::span)
- Zero overhead in release builds
Thread-Safe State Management
ThreadSafeState
Read-write lock based state management:
enum class ConnectionState {
};
ThreadSafeState<ConnectionState> state(ConnectionState::Closed);
state.set_state(ConnectionState::Connecting);
ConnectionState current = state.get_state();
bool updated = state.compare_and_set(
ConnectionState::Connecting,
ConnectionState::Connected
);
AtomicState
Lock-free atomic state operations:
AtomicState<int> counter(0);
counter.fetch_add(1);
int expected = 10;
bool success = counter.compare_exchange_strong(expected, 20);
Use when:
- High contention scenarios
- Low latency required
- Simple atomic types (int, bool, etc.)
ThreadSafeCounter
Thread-safe counter with atomic operations:
ThreadSafeCounter counter;
counter.increment();
counter.decrement();
size_t value = counter.get();
ThreadSafeFlag
Condition variable supported flags:
ThreadSafeFlag ready_flag;
ready_flag.wait();
std::cout << "Ready!" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
ready_flag.set();
if (ready_flag.is_set()) {
}
Thread Safety Summary
| Primitive | Lock-Free | Blocking | Use Case |
| ThreadSafeState | No | No | Complex state management |
| AtomicState | Yes | No | Simple atomic types |
| ThreadSafeCounter | Yes | No | Counters, statistics |
| ThreadSafeFlag | No | Yes | Synchronization, signals |
Memory Tracking
Built-in Memory Tracking
Enable for debugging and development:
cmake -S . -B build \
-DCMAKE_BUILD_TYPE=Debug \
-DUNILINK_ENABLE_MEMORY_TRACKING=ON
Features
1. Allocation Tracking
Monitor all memory allocations and deallocations:
auto* data = new uint8_t[1024];
delete[] data;
MemoryTracker::Stats stats = MemoryTracker::instance().get_stats();
std::cout << "Total allocations: " << stats.total_allocations << std::endl;
std::cout << "Total deallocations: " << stats.total_deallocations << std::endl;
std::cout << "Current usage: " << stats.current_usage << " bytes" << std::endl;
2. Leak Detection
Identify potential memory leaks:
MemoryTracker::Stats stats = MemoryTracker::instance().get_stats();
if (stats.total_allocations != stats.total_deallocations) {
size_t leaked = stats.total_allocations - stats.total_deallocations;
std::cerr << "⚠️ Memory leak detected: "
<< leaked << " allocations not freed" << std::endl;
MemoryTracker::instance().print_report();
}
Output example:
=== Memory Tracking Report ===
Total allocations: 1250
Total deallocations: 1248
Leaked allocations: 2
Current memory usage: 2048 bytes
Peak memory usage: 4096 bytes
3. Performance Monitoring
Track memory usage patterns:
size_t peak = MemoryTracker::instance().get_peak_usage();
std::cout << "Peak memory: " << peak << " bytes" << std::endl;
size_t alloc_count = MemoryTracker::instance().get_allocation_count();
std::cout << "Allocations: " << alloc_count << std::endl;
4. Debug Reports
Detailed memory usage reports:
MemoryTracker::instance().print_report();
std::string report = MemoryTracker::instance().get_report_string();
Zero Overhead in Release
Memory tracking has zero overhead in Release builds:
AddressSanitizer Support
Enable AddressSanitizer
cmake -S . -B build \
-DCMAKE_BUILD_TYPE=Debug \
-DUNILINK_ENABLE_SANITIZERS=ON
cmake --build build -j
What ASan Detects
AddressSanitizer detects:
- ✅ Use-after-free
- ✅ Heap buffer overflow
- ✅ Stack buffer overflow
- ✅ Use-after-return
- ✅ Memory leaks
- ✅ Double-free
- ✅ Invalid free
Running with ASan
# Run application
./my_app
# Run tests
cd build
ctest --output-on-failure
ASan output example:
=================================================================
==12345==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000050
WRITE of size 4 at 0x602000000050 thread T0
#0 0x7f8a4b5c6d42 in main
#1 0x7f8a4b3c7082 in __libc_start_main
Performance Impact
- Runtime overhead: ~2-3x slowdown
- Memory overhead: ~2x memory usage
- Only for testing - not for production
Best Practices
1. Buffer Management
✅ DO
std::vector<uint8_t> raw(data, data + len);
SafeDataBuffer buffer(raw);
process_buffer(buffer);
void process(safe_span<const uint8_t> data) {
}
❌ DON'T
uint8_t* buf = new uint8_t[size];
memcpy(buf, data, len);
uint8_t* ptr = buffer + offset;
2. Type Conversions
✅ DO
❌ DON'T
std::string str = reinterpret_cast<const char*>(data);
uint8_t* bytes = const_cast<uint8_t*>(str.data());
3. Thread Safety
✅ DO
ThreadSafeState<State> state;
ThreadSafeCounter counter;
client->send(data);
❌ DON'T
4. Memory Tracking
✅ DO
cmake -DCMAKE_BUILD_TYPE=Debug -DUNILINK_ENABLE_MEMORY_TRACKING=ON
auto stats = MemoryTracker::instance().get_stats();
assert(stats.total_allocations == stats.total_deallocations);
❌ DON'T
5. Sanitizers
✅ DO
cmake -DCMAKE_BUILD_TYPE=Debug -DUNILINK_ENABLE_SANITIZERS=ON
ctest --output-on-failure
❌ DON'T
Memory Safety Benefits
Prevents Common Vulnerabilities
| Vulnerability | Traditional C++ | unilink |
| Buffer Overflow | Possible | Prevented (bounds checked) |
| Use-After-Free | Possible | Detected (ASan) |
| Memory Leak | Possible | Detected (tracking) |
| Data Race | Possible | Prevented (thread-safe) |
| Type Confusion | Possible | Prevented (type-safe) |
Performance
| Feature | Debug Overhead | Release Overhead |
| Bounds Checking | ~5% | <1% |
| Memory Tracking | ~10% | 0% (disabled) |
| Thread Safety | ~5% | ~2-5% |
| Memory Pools | 0% | Negative (faster!) |
| AddressSanitizer | ~200-300% | N/A (not used) |
Testing Memory Safety
Unit Tests
# Run memory safety tests
./build/test/run_memory_safety_tests
Tests cover:
- Buffer bounds checking
- Memory leak detection
- Safe type conversions
- Thread safety
- Memory pool correctness
Integration Tests
# Run with AddressSanitizer
cmake -S . -B build -DUNILINK_ENABLE_SANITIZERS=ON
cmake --build build
cd build && ctest
Continuous Integration
All memory safety features are tested in CI/CD:
- ✅ Memory tracking enabled
- ✅ AddressSanitizer enabled
- ✅ ThreadSanitizer enabled (selected tests)
- ✅ Valgrind memcheck
See ../guides/testing.md "Testing Guide" for details.
Next Steps
- Runtime Behavior - Threading and execution model
- System Overview - High-level architecture
- ../guides/testing.md "Testing Guide" - Memory safety testing
- ../guides/best_practices.md "Best Practices" - Safe coding patterns