Embedded Software Programming: How to Improve Code Portability?

I am Lao Wen, an embedded engineer who loves learning.Follow me, and let’s become better together!For experienced developers, writing code with an emphasis on portability is essential, not only for the convenience of others but primarily for oneself, to avoid creating pitfalls.Here, I will share some key points on enhancing the portability of embedded code.1. Layered Design: Isolate Platform-Dependent Code.Just like testability, portability must be addressed from the design phase. Generally, the top and bottom layers do not exhibit good portability.The top layer is the GUI, and most GUIs are not cross-platform, such as Win32 SDK and MFC. The bottom layer consists of operating system APIs, which are mostly proprietary.If code from these two layers is scattered throughout the software, the software’s portability will be severely compromised, which is self-evident. So how can we avoid this situation? The answer is layered design:The bottom layer uses the Adapter pattern to encapsulate the APIs of different operating systems into a unified interface. Whether to encapsulate as classes or functions depends on whether you are using C or C++. This may seem simple, but it is not (you will understand after reading the entire article); it will consume a lot of your time to write and test the code.Using existing libraries is a wise approach, as there are many such libraries, for example, the C library glib (the base class for GNOME) and the C++ library ACE (ADAPTIVE Communication Environment). Utilizing these libraries during the development of the first platform can significantly reduce the workload of porting.The top layer adopts the MVC model, separating the interface presentation from the internal logic code. Most of the code is placed in the internal logic, while the interface is only responsible for displaying and receiving input. Even if you need to switch to a different GUI, the workload will not be substantial.This is also one of the means to improve testability, along with other additional benefits. Therefore, even if you use cross-platform GUI design software like QT or GTK+, separating the interface presentation from the internal logic is still very useful.If you achieve the above two points, the program’s portability is basically guaranteed; the rest are just technical details.2. Familiarize Yourself with Target Platforms in Advance and Abstract Low-Level Functions Reasonably.This point is built on layered design. Most low-level functions, such as threads, synchronization mechanisms, and IPC mechanisms, have nearly one-to-one corresponding functions provided by different platforms, making it relatively simple to encapsulate these functions; implementing the Adapter is almost just physical labor.However, for some special applications, such as graphical components, take GTK+ as an example. The functionality based on X Window and that based on Win32 are vastly different; apart from basic concepts like windows and events, there is hardly any similarity. If you do not familiarize yourself with the characteristics of each platform in advance and carefully consider them during design, the abstractions you create may be nearly impossible to implement on another platform.3. Use Standard C/C++ Functions as Much as Possible.Most platforms implement functions defined by POSIX (Portable Operating System Interface), but these functions may perform worse than native functions and are not as convenient to use.However, it is best not to be tempted to use native functions; otherwise, you may end up hurting yourself. For example, use functions like fopen for file operations instead of CreateFile.4. Avoid Using Features Introduced in New C/C++ Standards.Not all compilers support these features; for instance, VC does not support variable argument macros required in C99, and its support for some template features is also incomplete. For safety, do not be too aggressive in this area.5. Avoid Using Features Not Clearly Specified in the C/C++ Standard.For example, if you have multiple dynamic libraries, each with global objects that have dependencies, you will eventually encounter problems. The order of construction for these global objects is not specified in the standard.What works correctly on one platform may crash inexplicably on another, ultimately requiring significant modifications to the program.6. Avoid Using Semi-Standard Functions.Some functions are widely available across most platforms and have become so common that they are treated as standard, such as atoi (converts a string to an integer), strdup (clones a string), and alloca (allocates memory on the stack). Better safe than sorry; unless you understand what you are doing, it is best to avoid them.7. Pay Attention to the Details of Standard Functions.You may not believe it, but even standard functions can exhibit surprising differences in their external behavior, disregarding internal implementations. Here are a few examples:(1) int accept(int s, struct sockaddr *addr, socklen_t *addrlen); addr/addrlen are intended as output parameters. If you are a C++ programmer, you are used to initializing all variables, so there should be no problem. However, if you are a C programmer and do not initialize them, the program may crash inexplicably, and you would never suspect it. This is fine under Win32 but can cause issues under Linux.(2) int snprintf(char *str, size_t size, const char *format, …); The second parameter size does not include the null character in Win32 but does in Linux. This one-character difference can cost you several hours.(3) int stat(const char *file_name, struct stat *buf); This function itself is fine, but the issue lies with the struct stat; st_ctime represents creation time in Win32 but last modification time in Linux.(4) FILE *fopen(const char *path, const char *mode); There are no issues when reading binary files. However, be cautious when reading text files; Win32 automatically preprocesses, resulting in a discrepancy between the content read and the actual file length, while Linux does not have this problem.8. Be Cautious with Standard Data Types.Many have suffered from the transition of int types from 16-bit to 32-bit, which is an old story. Let’s not discuss that here.Did you know that char can be signed on some systems and unsigned on others? Did you know that wchar_t is 16 bits on Win32 and 32 bits on Linux? Did you know that a signed 1-bit bitfield can take values of 0 and -1 instead of 0 and 1? These seemingly trivial details can be elusive, and one misstep can lead to trouble.9. Avoid Using Platform-Specific Features.For example, in Win32, a DLL can provide a DllMain function that the operating system’s loader automatically calls at specific times. While this feature is very useful, it is best to avoid it, as the target platform may not guarantee such functionality.10. Avoid Using Compiler-Specific Features.Modern compilers are very user-friendly and considerate, making some features very convenient to use.For instance, in VC, to implement thread-local storage, you do not need to call functions like TlsGetValue/TlsSetValue; you simply add __declspec(thread) before the variable. However, while pthread has similar functionality, it cannot be implemented in this way, making it non-portable to Linux. Similarly, gcc has many extensions that are not available in VC or other compilers.11. Be Aware of Platform Characteristics.For example:In Win32 DLLs, unless explicitly marked as export, other functions are not visible externally. In Linux, all non-static global variables and functions are visible externally. This can lead to issues with functions of the same name, which may take you days to resolve.(1) Directory separators: use ‘\’ in Win32 and ‘/’ in Linux.(2) Text file line endings: use ‘
’ in Win32 and ‘
’ in Linux, and ‘
’ in MacOS.
(3) Byte order (big-endian/little-endian): different hardware platforms may have different byte orders.(4) Byte alignment: on some platforms (like x86), misaligned bytes may only slow down performance, while on others (like ARM), it can lead to incorrect data reads without any warning. If issues arise, you may have no clue where to start.12. Be Aware of Resource Limitations on Different Platforms.You may recall the limitations of the number of files that could be opened simultaneously in DOS, which was in the dozens. Today, operating systems are much more powerful, but limitations still exist. For example, the default maximum value for shared memory in Linux is 4M. Being well aware of common resource limitations on the target platform can be very helpful, as some issues can be easily identified.Portability issues are not limited to the above points. On one hand, even problems encountered in the past may have been forgotten. On the other hand, there are many unknown issues that have never been encountered. This serves as a starting point; please feel free to add more.

Source:

https://blog.csdn.net/absurd/article/details/622055

-END-

Previous Recommendations: Click the image to read moreEmbedded Software Programming: How to Improve Code Portability?

When designing embedded software architecture, consider the issue of infrastructure construction!

Embedded Software Programming: How to Improve Code Portability?

Embedded engineers: I do not like writing documentation, nor do I like others not writing documentation!

Embedded Software Programming: How to Improve Code Portability?

Non-defensive programming: How to write maintainable embedded software code?

I am Lao Wen, an embedded engineer who loves learning.Follow me, and let’s become better together!

Leave a Comment