This type of question is asked most often by developers who want to be able to use dynamic memory allocation within a safety-related system without "undue" restrictions - which quite often seems to mean they are not prevented from dynamically allocating memory in amounts they choose, when they choose, and (possibly) releasing that memory when they choose.
I'll address that question (can dynamic memory allocation be used in a critical system without restrictions?) first. Then I'll come back to options involving accepting some restrictions on how (when, or if) dynamic memory allocation is used.
Within a "safety critical project", such a thing is generally not possible. Safety related systems generally have mandatory requirements concerned with mitigating or eliminating specified hazards. Failure to adequately mitigate or eliminate specified hazards (i.e. to meet the requirements) can result in harm - for example, death or injury of people. In such systems, it is generally necessary to determine, to some level of rigour, that the hazards are appropriately and reliably mitigated or eliminated. A consequence of this is typically a set of requirements related to determinism - the ability to determine, through appropriate analysis, that the system completes actions in a specified manner - where attributes like behaviour and timing are tightly specified.
If dynamic memory allocation is used without restriction, it is difficult to determine if parts of the system behave as required. Types of problems include;
- Fragmentation of unallocated memory. It is not possible to ensure that a request to allocate N contiguous bytes of memory will succeed, even if N bytes of memory are available. This is particularly true if there have previously been multiple allocations and deallocations in arbitrary order - even if N bytes of memory are available, they may not be in a contiguous parcel.
- Sufficiency. It is often difficult to provide an assurance that a critical memory allocation, which must succeed, does actually succeed.
- Appropriate release. It is difficult to prevent memory being released while it is still needed (resulting in potential to access memory that has been deallocated) or to ensure memory that is no longer need is actually released (e.g. prevent memory leaks).
- Timeliness. Attempts to mitigate the preceding problems mean that the time of an allocation or of a deallocation is variable, unpredictable, with potentially no upper bound. Examples of approaches to deal with these are defragmentation (to deal with problems of fragmentation) or garbage collection (to deal with problems with sufficiency and/or with appropriate release). These processes take time and other system resources. If they are done when attempting an an allocation, the time to allocate memory becomes unpredictable. If they are done on releasing memory, the time to release memory becomes unpredictable. If they are done at other times, the behaviour of other - potentially critical - code may become unpredictable (e.g. the world effectively freezes for the application).
All of these factors, and more, mean that unrestricted dynamic memory allocation does not work well within requirements for determinism of timing or resource usage of the system. Inherently, system requirements require some restrictions to be imposed and, depending on the system, enforced.
If restrictions on dynamic memory allocation are acceptable, there are options. Generally, these techniques require support both in terms of policy constraints and technical solutions to encourage (preferably enforce, in high criticality systems) compliance with those policies. Policy enforcement may be technical (e.g. automated and manual design and code reviews, tailored development environments, compliance testing, etc etc) or organisational (e.g. dismissing developers who willfully work around key policies).
Examples of techniques include;
- No dynamic allocation at all. i.e. static allocations only.
- Only use dynamic memory allocation during system initialisation. This requires the maximum amount of memory that needs to be allocated to be determined in advance. If memory allocation fails, treat it like any POST (power-on-self-test) failure.
- Allocate memory but never release it. This tends to avoid problems of fragmentation, but can make it more difficult to determine an upper bound on how much memory is needed by the system.
- Custom allocation. The system (or application) explicitly manages dynamic memory allocation, rather than using generic library functions (e.g. those associated with the programming language of choice). This usually means introducing a custom allocator and forbidding (or disabling) use of generic library functions for dynamic memory management. The custom allocator must be explicitly engineered with needs of the particular system in mind.
- Boxing in memory management. This is a particular type of custom allocation, where the application allocates a pool of memory, and functions request fixed amounts (or multiples of fixed amounts) from the pool. Because the pool is fixed by the application, the application to monitor how much memory from the pool is in use, and take actions to release memory if memory is exhausted. Allocations and deallocations from the pool can also be performed predictably (because some of the more general concerns with dynamic memory allocation are being managed). Critical systems may have multiple pools, each for exclusive use by specific sets of functions.
- Partitioning. Explicitly prevent non-critical functions from accessing memory pools that have been established for use by critical functions. This allows an assurance that critical functions can access memory they need, and also helps ensure that failure of a low-criticality function cannot trigger failure of a high criticality function. Partitioning may be performed within an application, or within a (appropriately certified) host operating system, or both .... depending on needs of the system.
Some of these approaches can be used to support each other.