Summary of function pointer and software design experience in C language

  • 2020-06-12 10:08:54
  • OfStack

Function Pointers and software design

I remember when I started working, a master told me that longjmp and setjmp were not familiar with each other, so don't call yourself a master of C language. I was half convinced that it took me a while to learn longjmp and setjmp in order to become a better player. It turns out that it's not just a matter of jumping around, but an advanced exception handling mechanism that can be useful in some situations.

In order to show my skill, I also used it several times in my own program. Gradually discovering the benefits of this technique comes at a cost, breaking the structured design of the program and making it difficult to read, especially for beginners. Finally understand that this technique is just a seasoning, used a few times in a few cases, can simplify the problem. If you eat seasoning for dinner, you put the cart before the horse and write a program that is malnourished.

In fact, familiarity with longjmp and setjmp is not cause-and-effect. But, to paraphrase the master, don't call yourself a master of C if you're not familiar with function Pointers. Why is that? Are function Pointers that complicated? Of course not. Anyone with any programming knowledge, whether they understand C or not, in 10 minutes, I think they will understand what function Pointers are in C.

The reason is that it's not the concept of a function pointer or the syntax itself that's hard, but when and where to use it. Function Pointers are not just a matter of syntax, but more importantly a design category. The real master should not only know the skill of grammar, but also know the method of design. Can you be a master if you don't know how to design? Suspect me of exaggerating? Let's first look at what design methods function Pointers relate to:

It has to do with layered design. Layered design is not a new concept. The benefits of layered design are well known, and the obvious benefits are simplified complexity and isolation of changes. The layered design allows each layer to only care about its own things, which reduces the complexity of the system. The interaction between layers is limited to a very narrow interface. As long as the interface remains unchanged, the changes of one layer will not affect other layers, which isolates the changes.

The general principle of layering is that the upper layer can directly call the functions of the lower layer, while the lower layer cannot directly call the functions of the upper layer. To put it simply, in reality, the lower layer often calls the upper function in reverse. For example, when you copy a file, you call a copy function at the interface level. Interface layer is the upper layer, copy file function is the lower layer, the upper layer calls the lower layer, of course. But if you want to update the progress bar when copying files, the problem arises. In aspect 1, only the copy function knows the progress of the copy, but it cannot update the progress bar of the interface. On the other hand, the interface knows how to update the progress bar, but it doesn't know the progress of the copy. How to do? A common practice is for the interface to set up a callback function to the copy file function, which calls the callback function when appropriate to inform the interface of the status update.

It has to do with abstraction. Abstract is one of the most important concepts in object orientation and one of the most powerful aspects of object orientation. Object orientation is just one idea. As we all know, you can implement object oriented programming with C language 1. This is not a fashion statement, but a practical one. If you doubt this, check out the open source code of GTK+, linux kernel, etc.

Interfaces are the highest level of abstraction. In linux kernel, the concept of interfaces is everywhere, like the virtual file system (VFS), which defines the interface of a file system. As long as you follow the specification of this interface, you can develop a file system to hang on. This is especially true for device drivers, which have their own set of interface specifications. When you develop your own devices and drivers, just follow the interface specifications. How is an interface represented in C? It's simply a set of function Pointers.

Related to the separation of interface and implementation. Programming to interfaces, not implementations, is the first design guideline of Design Patterns. The goal of separating the interface from the implementation is to isolate change. Software is changeable, and if you can't isolate the changed things, it will cause the whole body to move. The cost is huge. This is something that no one wants to see.

Since the C language enables object-oriented programming, it is natural to use design patterns to separate interfaces from implementations. For example, bridge mode, policy mode, state mode, proxy mode, etc., in C, nothing is implemented without using function Pointers.

It has to do with the loose coupling principle. One of the reasons why process orientation pales in comparison to object orientation is that it does not intuitively map real models to computers like object orientation 1 does. Process orientation refers to layers of control, while object orientation emphasizes the division of labor and cooperation among objects. Objects in the real world are less in hierarchical relationships and more in reciprocal relationships. That is, the interaction between objects tends to be bidirectional. This strengthens coupling between objects.

Coupling itself is not wrong, in fact coupling is essential, without coupling there is no collaboration, objects can not form a whole, nothing can be done. The key is that coupling should be appropriate and as loose as possible under the premise of implementing the intended function. In this way, changes in part 1 of the system will have little impact on other parts.

Function Pointers are the best way to decouple objects. The Signal mechanism (such as signal in boost and signal in glib) is a typical example where the state of an object itself may be changing (or triggering an event) while other objects care about it. Once the object has changed, the other objects should perform corresponding operations.

If the object calls the function of another object directly, the function is done, but the coupling between the objects is too tight. The signal mechanism is a good way to minimize this coupling. Here's how it works: Other objects that are concerned about changes to the object actively register a callback function to the object. These callbacks are called to notify other objects once a change has occurred to the object. Functionality is also implemented, but the coupling between them is reduced.

In the C language, it would be very difficult to solve these problems without using function Pointers. If you've never thought of using function Pointers in programming, it's hard to imagine you're a master of the C language.

conclusion


Related articles: