Managed vs. Unmanaged C: How to Increase the Security and Reliability of Your Embedded Applications
April 10, 2023
Blog
In a recent article, embedded systems consultant Jacob Beningo raised the question of whether it's time to retire the C programming language due to its limitations surrounding complexity and potential for error and the shortage of C developers. In response, Andrei Gorine argued that C's compact footprint makes it more competitive than resource-heavy modern languages like Java, C# and Go.
Additionally, C's ability to control application behavior and memory directly makes it the only viable option for systems that require close access to hardware resources like interrupts and peripherals. This is especially true for embedded devices, as their foundational operating systems, drivers, and related APIs also tend to be written in C.
Nevertheless, this highly flexible level of control can cause issues with runtime behavior. Whether the C language is eventually retired or not, developers need tools today to ensure that application security and reliability are not compromised through human error or intent as embedded devices become increasingly connected. Developers need to manage C just like higher-level languages to reinforce the security and reliability of the application.
Managed C Vs. Unmanaged C
One of the key differences between the C language and modern languages such as Java, JavaScript, and Python, is that the latter implementations are managed. This means the code is executed by a runtime environment that ensures its proper management and execution within memory boundaries, security principles, and optimization.
By contrast, the C language is unmanaged, which means the programmer must administer memory allocation and deallocation manually. This makes the code more susceptible to security vulnerabilities, such as buffer overflow and memory leaks. The tradeoff is that C code is generally faster and more efficient than managed code but requires more effort to write and maintain.
Here is a summary of the main differences between managed C and unmanaged C code:
Managed Code |
Unmanaged Code |
Executed by the common language infrastructure (runtime) |
Compiled and executed by the processor |
Memory buffer overflow cannot occur |
Memory buffer overflow can occur |
Provides memory protection and reduces the risk of memory leaks |
Memory is not protected, and improper use of allocation functions and pointers can lead to memory corruption and system crashes
|
Provides services such as exception handling and memory cleanup |
Does not provide those services |
Can easily be ported to different platforms as the runtime environment abstracts the hardware and operating system specifics |
Cannot be easily ported, requiring that engineering teams frequently restart from scratch |
The runtime environment enforces security features, such as type safety and access control |
Developers are responsible for writing safe and secure code, which can increase the risk of errors |
Combining the Best of Both Worlds: C Applications with the Reliability of Those Written in a Higher-Level Language
Software containers make it possible for developers to effectively manage C applications in a runtime environment, although memory allocation still requires manual management. This is because containerization provides a secure and isolated space for each app, reducing the likelihood of system crashes due to memory errors.
Dynamic linking in Managed C allows new packages to be installed without requiring that developers recompile the entire program, saving development time. This feature, traditionally associated with Linux, is now available through the use of small software containers and offers enhanced portability and security while enabling hot code replacement.
MICROEJ VEE is one example of a tiny software container that provides a sandboxing mechanism for Managed C, Java, and JavaScript applications. All components are managed by the same runtime environment, ensuring safe and secure coexistence (see Figure 1). With this approach, engineers can reuse components confident that they will work reliably and securely.
Figure 1: At right is managed code running on top of a MICROEJ VEE application container (source: MicroEJ)
The use of secure software containers to isolate each application provides a significant advantage by allowing multiple applications written in different programming languages to coexist seamlessly. By combining managed C with secure containers, this approach bridges the gap between embedded and enterprise development. Managed C provides the flexibility of C while eliminating the need for developers to manage secure memory, multi-task synchronization, and perform other low-level tasks.
This approach also facilitates the integration of legacy code and existing software stacks in C. This is especially useful for implementing protocols written in embedded C, including cloud connectors, Matter, LWM2M, and MQTT. It is also additive to the MISRA C guidelines, as it overcomes low-level robustness and security issues. Finally, a container like MICROEJ VEE allows dynamic linking, which means that managed C can be dynamically linked and unlinked same as code written in any other language supported by the VEE container.
Teaming Up Managed and Unmanaged C for the Best Outcome
Using secure software containers does not mean that all C code will need to be managed. In virtual execution environments like MICROEJ VEE, unmanaged C code (also called native code) still has a role to play. All the low-level drivers and board support packages, interfacing directly with the hardware and CPU registers, should remain in unmanaged in C to best take advantage of C’s close proximity to the hardware.
By contrast, for more complex application tasks, using managed code is more convenient and efficient. Managed code improves productivity and reduces errors thanks to the separation of concerns and it makes the code more portable.
Managed and unmanaged code bases can still interact, in a controlled way, through abstraction layers and native interfaces. Referred to as Simple Native Interface or SNI, it allows any managed code to call native functions, pass parameters, get return values and manipulate shared memory in a dedicated space.
This split with higher-level stacks in managed code and low-level hardware interfaces in unmanaged code provides the ideal combination.
Managed C: The C Language of the Future
In complex and constantly changing development environments, it is essential that developers have access to cutting-edge software and language solutions that leverage the latest hardware innovations. Although C is the leading language in embedded development, there is still a need to manage its runtime behavior using tools and processes that can unlock all the benefits of managed code to accelerate innovation.
By streamlining development processes and offloading security and reliability to the runtime, managed C enables developers to improve the robustness, security, and portability of their applications while keeping a familiar programming language. As technology continues to evolve, developers who embrace managed C code solutions will be better equipped to create innovative, secure, and reliable connected products.