In the embedded software domain, where C and C++ dominate, Python is often perceived as not being a serious language for the job. What’s behind this perception? Is it valid? This article challenges Python’s sentence as unfit for embedded systems by exploring code verbosity, memory management, and virtual environments.
As Steve McConnell describes in his seminal text Code Complete, there is a strong correlation between the number of lines of code in a project and the number of bugs. Python is particularly concise, able to express logic in as little as 20% of the lines of code required to write the equivalent logic in C. Take the simple example below of console text input and printing; Python gets the task done with little ceremony. In addition to less bugs, concise code is also more readable, reducing the chances of new bugs being introduced due to misinterpretation of the developer’s original intent.
C’s pointers and memory management functions provide unique low-level control, which is especially powerful when using resource-constrained devices. This low-level control however comes at a price. Debugging memory leaks is the bane of the C programmer’s work, a tedious but important task to ensure that memory is correctly returned to the system. An overlooked pointer assignment or a clumsy factory function call can easily lead to a memory leak, potentially crashing the system if not cleaned up. The addition of exceptions in C++ exacerbates this issue as multiple return paths are introduced to functions, making it harder to ensure correct and single destruction of heap-allocated memory. C++11 offers a neat solution in the form of smart pointers, providing a poor-man’s garbage collector focused on a particular resource. Python goes a step further by providing full garbage collection by default. Garbage collection ensures safe management of memory but at the price of speed and deterministic execution. The CPython interpreter runs approximately 100 times slower than C/C++ and inconsistent garbage collector timing can affect real-time behavior.
Out of bounds checking
Incorrectly accessing memory, such as off-by-one errors, can lead to segmentation faults and buffer overflows. C doesn’t provide a language solution to this, relying on the developer to manually restrict array bounds with tricks such as sizeof or employ a static analysis tool. C++ provides a solution to array bounds checking by introducing the at() method to STL containers, which throws an exception if memory is incorrectly accessed. Additionally, C++11 provides the range-based for-loop as a means of automatically iterating over a container without manually indexing.
Python again goes a step further by performing automatic out-of-bounds checking at run time by default, even when accessing built-in arrays using square bracket notation. In a similar fashion to the use of garbage collection to address memory leaks, automatic out-of-bounds checking adds robustness but at the price of speed and deterministic execution.
One of the main drivers behind Python’s surge in popularity is its package system. The Python Package Index (PyPI) provides access to over one hundred thousand packages in a common format, all of which can be installed via a common package manager such as pip. A byproduct of Python’s package system is the ability to create and manage virtual environments. Employing a virtual environment using modules such as venv and virtualenv provides an efficient means of managing and ensuring package dependencies. From a robustness point of view, a virtual environment reduces the risk of code breaking when executed in another environment, for example when deployed on an edge device following development on a desktop machine. By using virtual environment tools, the edge device environment can easily be created to match the desktop environment in which the code was developed and tested.
Python’s trump card is its brevity. Being a uniquely concise language, Python requires few lines of code compared to C/C++ which, as history has shown, leads to less bugs. Regarding memory management, for allocation, destruction and access, Python’s garbage collector and run time out-of-bounds checker improve memory robustness but at the price of speed and deterministic execution. Enabled by Python’s package system, virtual environments offer an easy way to ensure that dependencies are maintained when code is deployed. Overall, despite its critics in the embedded system domain, Python has a number of robustness advantages over C/C++, as long as the resulting hit on speed and determinism is not prohibitive.