Insights on Embedded Software Design

I remember when I first started working, a senior developer told me that if you are not familiar with longjmp and setjmp, you shouldn’t call yourself a C language expert. At that time, I was skeptical, but to advance my skills, I spent some time learning how to use longjmp and setjmp.

Later, I realized that it is not just about jumping around; it is a sophisticated exception handling mechanism that can be quite useful in certain situations.

To showcase my skills, I used it a few times in my programs. Gradually, I discovered that the benefits of such techniques come at a cost, disrupting the structured design of the program, making it difficult to read, especially for beginners.

I finally understood that this technique is merely a seasoning; using it a few times in rare situations can simplify problem handling. If you rely on seasoning as a staple, you will end up with a malnourished program.

In fact, being familiar with longjmp and setjmp does not correlate with being a C language expert. However, if I could adapt the words of that senior developer, I would say that if you are not familiar with function pointers, you shouldn’t call yourself a C language expert. Why do I say this? Are function pointers that complicated?

Of course not; anyone with a bit of programming knowledge, regardless of whether they understand C, can grasp the concept of function pointers in C within ten minutes.

The reason is that the difficulty lies not in the concept and syntax of function pointers themselves, but in when and where to use them. Function pointers are not just a syntactical issue; more importantly, they are a design consideration.

A true expert should not only understand the syntactical techniques but also the design methodologies. Can one be considered an expert without understanding design? Do you doubt my exaggeration? Let’s first look at how function pointers relate to various design methodologies:

Related to Layered Design

Layered design is no longer a new concept; its benefits are well-known, with a clear advantage being the simplification of complexity and isolation of changes. In a layered design, each layer only needs to concern itself with its own matters, reducing the system’s complexity. The interaction between layers is limited to a narrow interface; as long as the interface remains unchanged, changes in one layer will not affect others, thus isolating changes.

The general principle of layering is that the upper layer can directly call functions from the lower layer, while the lower layer cannot directly call functions from the upper layer. This statement sounds simple, but in reality, the lower layer often needs to call functions from the upper layer. For example, when copying a file, a function to copy files is called from the interface layer.

The interface layer is the upper layer, and the file copy function is the lower layer, so it is natural for the upper layer to call the lower layer. However, if you want to update a progress bar while copying the file, problems arise.

On one hand, only the file copy function knows the progress of the copy, but it cannot update the interface’s progress bar. On the other hand, the interface knows how to update the progress bar, but it does not know the copy progress.

What to do? A common approach is for the interface to set a callback function for the file copy function, which calls this callback function at appropriate times to notify the interface to update the status.

Related to Abstraction

Abstraction is one of the most important concepts in object-oriented programming and is where its power lies. Object-oriented programming is merely a philosophy; it is well-known that object-oriented programming can also be implemented in C. This is not just a trend but a practical method. If you doubt this, you can look at open-source code like GTK+ and the Linux kernel.

Interfaces are the highest form of abstraction. In the Linux kernel, the concept of interfaces is ubiquitous, such as the Virtual File System (VFS), which defines an interface for file systems. As long as you adhere to this interface specification, you can develop your own file system.

Device drivers are even more so; different device drivers have their own sets of interface specifications. When developing your own device driver, you only need to follow the corresponding interface specifications. How are interfaces represented in C? It’s simple: a set of function pointers.

Related to Separating Interface from Implementation

Programming against interfaces rather than implementations is the first design principle of the “Design Patterns”. The goal of separating interfaces from implementations is to isolate changes. Software is subject to change; if we cannot isolate the changing elements, it leads to a situation where one change affects everything, which is costly and undesirable.

Since C can implement object-oriented programming, it can naturally utilize design patterns to separate interfaces from implementations. Patterns like the Bridge, Strategy, State, and Proxy patterns all require function pointers to implement in C.

Related to the principle of loose coupling. Compared to object-oriented programming, procedural programming appears weak because it does not intuitively map real-world models to computers.

Procedural programming emphasizes layered control, while object-oriented programming emphasizes collaboration between objects. In the real world, objects are rarely in a hierarchical relationship; they are mostly in a peer relationship. This means that interactions between objects are often bidirectional, which increases coupling between them.

Coupling itself is not wrong; in fact, coupling is essential. Without coupling, there can be no collaboration, and objects cannot form a whole, rendering them ineffective. The key is that coupling should be appropriate; under the premise of achieving the intended functionality, coupling should be as loose as possible. This way, changes in one part of the system will have minimal impact on other parts.

Function pointers are the best tool for decoupling object relationships. The Signal mechanism (such as boost’s signal and glib’s signal) is a typical example. An object’s state may change (or trigger some events), while other objects care about its changes. Once that object changes, other objects need to perform corresponding operations.

If that object directly calls functions of other objects, the functionality is achieved, but the coupling between objects is too tight. How can we minimize this coupling? The signal mechanism is a good solution.

Its principle is roughly as follows: other objects that care about the changes of that object actively register a callback function with that object. Once that object changes, it calls these callback functions to notify other objects. The functionality is still achieved, but the coupling between them is reduced.

In C, solving these problems without using function pointers would be very difficult. In programming, if you have never thought of using function pointers, it is hard to imagine you are a C language expert.

Author Contact: Li Xianjing <xianjimli at hotmail dot com>https://blog.csdn.net/absurd/article/details/6474063

Copyright belongs to the original author or platform, for learning reference only. If there is any infringement, please contact for deletion~

You might also like:

Embedded Miscellaneous Weekly | Issue 7

Essentials | What are Processor Microarchitecture and Instruction Set?

Sharing an Embedded Software Tool List!

Sharing a Compact and Useful Code Comparison Tool

A Multi-task Management OS Implemented in Over 300 Lines of Code

Sharing Several Practical Shell Scripts in Embedded Systems!

Reply 1024 in the WeChat public account chat interface to obtain embedded resources; reply m to view article summaries.

ClickRead the original text to see more shares.

Leave a Comment