How to write excellent C code?
Excellent code is undoubtedly the crystallization of various programming techniques and design philosophies, so it requires step-by-step accumulation. Here I share this article on building the ‘library’ concept, and everyone can experience it.
What if device programs were perfectly library-based?
1. All engineers would spend minimal effort when porting or creating the device driver.
2. As the number of users increases, it becomes tested and stable, turning into truly public code.
3. The library’s external interface (function names and parameter declarations) remains unchanged. When all commonly used devices are library-based, it brings another benefit: the time spent on porting, creating, modifying, and maintaining the application layer will drastically decrease.
Seamless cross-platform porting of the application layer is not a myth. When all peripheral devices it relies on are library-based across different platforms, implementing the application layer is as straightforward as writing Java code.
4. A library also ensures the security of the company’s core code; library code is only handled by core engineers, so losing the application layer program is inconsequential.
5. Newcomers can get up to speed faster with library-based projects, thanks to the library’s documentation and the ability to focus on application development without worrying about low-level details.
6. Providing clients with the ability for secondary development means you can hand over the library for hardware and peripheral drivers, allowing them to do further development.
7. Library-based communication protocols will make communication system products more secure, at least preventing damage from departing engineers, such as in RFID transactions.
So, isn’t it cool to organize code using a library approach? At that time, these were the design goals of libraries; what effects can actually be achieved in the hands of engineers depends on their skill level~
Of course, some engineers might think that libraries allow them to escape tedious low-level driver work and engage in higher-level tasks.
Conditions Required for Successful Library Creation
1. Only .h and .lib files are provided to clients.
2. There are no defines in any .h files; compilation conditions are merely a joke for the .lib files.
3. There are no extern variables in any .h files; if there are, it means the system can only create one such device. For instance, if there is an extern variable in a buzzer driver, it means the entire system only allows one buzzer.
4. Comprehensive and detailed usage documentation is provided, which can refer to the format of Keil’s hlp documentation.
5. Simple demo programs using the .h files for reference.
6. ‘Dynamic linking’ of library code; in simple terms, unused interface function code will not be included in the final binary by the linker.
7. Additionally, strive for platform independence; it should not depend on any registers or other platform-specific elements.
To achieve the above goals, libraries typically have the following characteristics:
1. Pointer to structures.
2. A large number of callback function pointers.
4. The library source code’s .c files will be split into more .c files according to the interface functions to minimize code space during linking.
Of course, everything has its pros and cons; if libraries were that simple and easy, wouldn’t everyone be able to use them effortlessly?
1. It may slow down device speed slightly due to additional layers of indirect addressing. However, for 32-bit machines, the convenience it brings is still acceptable.
2. It may consume slightly more code space, but trust me, for a whole medium to large system, it will reduce code volume rather than increase it, as there is a lot of redundant code in large systems. From my personal experience, the reduction is substantial, almost to an unbelievable degree.
Early 8-bit machines, especially on the 51 platform, could not implement a perfect library well, at least not a cross-model low-level device driver library.
In recent years, with the rise of 32-bit machines, libraries have gradually gained favor among more and more engineers. The fundamental reason is that the stack of the 51 architecture is statically compiled, and the stack for local variables and parameter passing is also static, making functions non-reentrant. In contrast, most 32-bit machines use stack-based parameter passing. Of course, the slow speed of the 51 is also one of the important reasons.
If you have friends familiar with object-oriented languages or Linux drivers, you probably understand what a good library looks like. A library is like a class in object-oriented programming, and the code for Linux low-level drivers is a world of function pointers and structure pointers.
The essence of C lies in pointers, and it is perfectly interpreted within.