The time will come when we need features like:
Run time configuration
Removing compile time configurations of components
Flexibility to external behavior or hardware
Platform for a complete generation of products
...then it is time to discuss dynamic solutions, because it's not efficient (or even possible) to eliminate dynamic memory management completely out of the system while designing such features.
IEC 61508 framing (addendum)
Why this addendum? The original article is popular because it gives practical engineering guidance. This section adds the IEC 61508 context that turns the patterns into an assessable safety argument: assumptions, constraints, failure reactions, and verification expectations.
Safety contract (what you must state explicitly)
Before arguing that dynamic memory is acceptable, define the contract for your memory service. These points are typically needed in the software safety requirements or architecture:
No general-purpose heap (
malloc/free) is used directly by safety-related application code.All allocations come from pre-created, bounded pools backed by statically reserved memory.
Pools are fixed-size block pools (one size per pool) to avoid fragmentation.
The system defines where allocation is allowed (startup only vs. also during operation).
The system defines blocking rules: which calls may block, with what timeout, and how that interacts with deadlines.
Concurrency rules are explicit: which calls are allowed in tasks/ISRs, and what synchronization protects internal state.
Each detected memory anomaly has a defined reaction (diagnostics + safe state / fault containment), not undefined behavior.
IEC 61508 note: The assessor will not accept “we think it’s fine” as an argument. They will ask for (a) the above constraints, and (b) verification evidence that the implementation meets them.
Evidence you typically need
To make these patterns certification-friendly, plan for at least:
Unit tests that cover all failure modes (including negative tests / fault injection)
Stress tests (pool exhaustion, repeated allocate/free cycles, long-duration)
Timing evidence: bounded allocation/free time under worst-case conditions
Code review rules: no raw heap usage; required access protocol; error paths must be handled
These topics are referenced again in each section below.
We must avoid and detect programming errors
Let's go back to the IEC61508. The reason for the recommendation is the potential risk of multiple well known and hard to detect programming errors in relation to dynamic memory management:
Memory Fragmentation
No Memory Available
Missing Memory Free
Free Memory to Wrong Pool
Free Memory multiple Times
Using Memory after Free
Free Memory while Using
Some solutions allow only allocation of memory. These type of solutions are addressing the problems 3.) to 7.) in the list by avoiding the reuse of memory for multiple purpose. This is a working solution and often used for flexible memory usage in product variants during startup, while keeping the resulting memory layout static during operation.
When even this approach is not enougth, we need a more generic approach with an approach to avoid or detect all of the listed situations systematically.
1. Memory Fragmentation
The memory fragmentation is an effect, which may result when we perform multiple allocation and free cycles with different memory sizes.
In real time systems, we need determinism and constant execution times. With improved allocation algorithms like first fit algorithm, the execution timing depends heavily on previous memory activities. This is bad for of our system designs.
The established way to avoid memory fragmentation is working with one (or a collection of) fixed size memory block pools.
Good news: professional real time kernels provide you memory management with fixed size memory blocks. I think we should use these provided service of our preferred real time kernel. For this article we consider the principal function interface of the real time kernel.
For reference, a simple usage cycle is:
ptr = MemAlloc(pool);
/* use memory via ptr */
MemFree(pool, ptr);and somewhere during start up:
pool = Create(mem, size, num);IEC 61508 note (what to document and verify)
Constraint: Fixed-size pools only (no variable-size heap in safety-related code).
Evidence: Review findings + tests showing bounded allocation/free time under worst-case usage.
2. No Memory Available
All memory allocation functions are giving us feedback (e.g. a NULL pointer), if not enough memory is available for the current allocation request. I think it is straight forward to check this and take an appropriate action.
ptr = MemAlloc(pool);
if (ptr == NULL) {
MemErrOutOfMemory(pool);
}
/* use memory via ptr */
MemFree(pool, ptr);When using a real time kernel we can furthermore avoid the risk of a missing check and failure handling: we extend our memory allocation function with a blocking wait for free memory:
ptr = MemAllocWait(pool);
/* use memory via ptr */
MemFree(pool, ptr);Now we guarantee, that the returned pointer is always a valid allocated memory block. The mandatory safety task monitor will detect, if a task will not get enough memory within the defined deadline.
IEC 61508 note (what to document and verify)
Blocking rules: Define which calls may block, with what timeout, and how that interacts with deadlines.
Failure reaction: Define what happens on timeout (fault report + safe state, not silent retry).
Evidence: Scheduling/timing analysis or test evidence under load; fault injection tests for exhaustion.
3. Free Memory to Wrong Pool
If we are responsible to free the memory to the correct pool (e.g. the pool is an argument to free the memory), we could do this wrong.
This is not a big issue, because we can completely avoid this error by adding the required argument to an info area. For simple (and fast) access of this data, the info area is a part of the allocated memory.
This part is not usable for the application and located in front of the application memory block reference:
The usage sequence with this improvement is now:
ptr = MemAllocWait(pool);
/* use memory via ptr */
MemFree(ptr);IEC 61508 note (what to document and verify)
Constraint: Pool reference stored in header;
MemFree(ptr)uses header value.Evidence: Unit tests for pool mapping; negative tests (corrupt header → detected).
4. Missing Memory Free
A so-called 'memory leak' occurs when we allocate memory and after usage we miss to free the memory block. This error is not easy to detect, especially when allocation and free actions will take place in different functions (or tasks).
One method I found for detecting a memory leak, without knowing the application details, is a memory watchdog. This watchdog is working analogous to a typically used execution watchdog.
Let us assume, we define a watchdog time during the memory allocation. This forces us to trigger the watchdog within this period of time to keep the memory block valid.
We store the memory timing data in the already introduced info area.
For checking the watchdog time period of all allocated memory blocks we introduce a checking function. The pseudo code should give you an idea how we check the blocks.
void MemCheck(pool)
{
for each 'block' in 'pool' do:
if ( 'block::timeout' is greater than 0 ) then:
decrement 'block::timeout' by 1
if ( 'block::timeout' is equal to 0 ) then:
call MemErrTimeout (block)
}We call this function periodically as part of the system safety self tests. Our using sequence looks like the following lines.
ptr = MemAllocWait(timeout, pool);
MemUseTrigger(ptr);
/* use memory via ptr */
MemFree(ptr);Note: between the lines, we allow a maximum time period of the given timeout. This may occur, when an interrupt leads to a task switch between these lines.
In case we miss the watchdog time period with our use-trigger, we can define an appropriate action on this detection in a callback function:
void MemErrTimeout(ptr)
{
/* action on timeout: */
/* log diagnostic data */
/* initiate safe state */
}IEC 61508 note (what to document and verify)
Constraint: Watchdog times must be justified (some objects are intentionally long-lived).
Failure reaction: Define what happens on timeout (log + safe state).
Evidence: Tests that simulate missed trigger; documented safe reaction (
MemErrTimeout).
5. Free Memory multiple Times
When we free a memory block multiple times (by mistake) without a detection mechanism, we are running in strange behavior of the system. This kind of error is very hard to debug - and must be avoided.
With an additional redundancy value within the *info area*, we can handle this very efficiently. I prefer the complement value of the pool argument. We must set both values to a matching pair within the memory allocation function.
The memory free function can check this redundant information, put back the memory block into the addressed pool and has to corrupt the redundant information. A pseudo code could look like:
void MemFree(ptr)
{
if ( 'ptr::pool' is equal to complement of 'ptr::inv_pool' ) then:
release memory block 'ptr' to 'ptr::pool'
set 'ptr::inv_pool' to 'ptr::pool'
else:
call MemErrDoubleFree (ptr)
}At the end, we can define an appropriate action for this erroneous case in a callback function: MemErrDoubleFree(ptr).
IEC 61508 note (what to document and verify)
Constraint: Redundancy in header (e.g.,
inv_pool = ~pool).Evidence: Unit tests for double free; static analysis / code reviews showing all free paths go through service.
6. Using Memory after Free
This programming error sounds crazy, but in multi tasking environments this error may exist, in worst case undetected for a long time.
For detecting this situation, we can reuse our data redundancy in the info area. In this case we check, that the redundancy is not corrupted before using the memory block. To get the usage sequence as simple as possible, we can integrate the memory watchdog triggering:
void MemStartAccess(ptr)
{
if ( 'ptr::pool' is not equal to complement of 'ptr::inv_pool' ) then:
call MemErrUsedFree (ptr)
else
set 'ptr::timeout' to 'ptr::watchdog_time'
}At the end, we can define an appropriate action for this erroneous case in a callback function: MemErrUsedFree(ptr).
The usage sequence for that detection mechanism will need an additional function call before using the memory block.
ptr = MemAllocWait(timeout, pool);
MemStartAccess(ptr);
/* use memory block via ptr */
MemFree(ptr);IEC 61508 note (what to document and verify)
Constraint: Check redundancy on start of access.
Evidence: Tests that attempt access after free; coverage evidence that access protocol is used consistently.
7. Free Memory while Using
This programming error is in the same class of errors like the previous one. In multi tasking environment it is possible to free a memory block, which was used by the interrupted task. Coming back to the using task, we use memory that is already released.
When thinking about this problem, I found a solution with symmetric functions. We already have a MemStartAccess() function - so why not introduce a function at the end, too? (I like to have symmetric functions for this kind of use cases...)
This new function can inform us, that the memory block is not used anymore:
void MemEndAccess(ptr);With a corresponding blocking function for releasing the memory, we can avoid this programming error.
void MemFreeWait(ptr);This leads us to the final dynamic memory usage sequence in this article:
ptr = MemAllocWait(timeout, pool);
MemStartAccess(ptr);
/* use memory block via ptr */
MemEndAccess(ptr);
MemFreeWait(ptr);IEC 61508 note (what to document and verify)
Concurrency rules: Define which calls are allowed in tasks/ISRs, and what synchronization protects internal state.
Failure reaction: Define what happens if the protocol is violated (log + safe state).
Evidence: Stress tests with preemption; race-condition tests and code review.
Concurrency rules (must be explicit)
For IEC 61508 acceptability, specify:
Which API calls are allowed in ISRs (often: none; or a restricted subset).
Which calls may block (and in which task classes).
How header state is protected (scheduler lock, mutex, atomic operations).
This section is crucial because many memory bugs are concurrency bugs in disguise.
Safety reactions and diagnostic reporting
For each error callback (examples):
MemErrOutOfMemory(pool)MemErrTimeout(ptr)MemErrDoubleFree(ptr)MemErrUsedFree(ptr)
define:
What is logged (pool id, block id, task id, timestamp, call site if available).
What safety action is taken (transition to safe state, fault containment, restart policy).
What is allowed to continue (often: nothing in safety-related control loop).
IEC 61508 expects the reaction to be consistent with the hazard analysis and the safety requirements.
Verification plan (turn mechanisms into IEC 61508 evidence)
A practical verification checklist:
Unit tests for each failure mode (including negative tests)
Stress tests for pool exhaustion and repeated allocate/free cycles
Timing tests: allocation/free time distribution and worst-case in realistic scenarios
Fault injection: corrupt header fields, simulate missed triggers, simulate wrong call sequences
Code review rules: ban direct heap use; enforce Start/EndAccess usage; enforce error-handling paths
If applicable for your SIL and toolchain, add:
static analysis rules (no raw
malloc/free)runtime assertions compiled into test builds
Conclusion
In this article we discovered a dynamic memory management approach for use in IEC61508 systems.
We systematically avoid the programming errors:
memory fragmentation
free memory to wrong pool
no memory is available
free memory while using
and we are able to detect and perform individual reactions on the situations:
missing free memory
free memory multiple times
usage of memory after free
The price we pay for these safety features is:
additional memory space within each allocated memory block
deterministic amount of CPU run time
Nevertheless, I recommend using dynamic memory with caution for safety-critical systems. With the measures provided, however, the use is acceptable in my opinion – provided you state the constraints, define the failure reactions, and provide the required verification evidence.
