How to Write Maintainable Embedded Programming Code?

1 Object-Oriented C

Object-oriented languages are closer to human thinking patterns, significantly reducing code complexity while enhancing code readability and maintainability. Traditional C code can also be designed to be readable, maintainable, and of lower complexity. This article will illustrate this through a practical example.

2 Basic Knowledge

2.1 Structures

In addition to providing basic data types, C also allows users to define custom data types through structures. In C, you can use structures to represent any entity. Structures are the prototype of classes in object-oriented languages. For example:

typedef struct{     float x;     float y;  }Point;

This defines a point in a two-dimensional coordinate system, with two fields: x coordinate and y coordinate.The fields in a structure are called members of the structure. The data types in a structure can be simple data types, other structures, or even nested structures. For instance, a standard linked list structure can be defined as follows:

typedef struct node{     void *data; // Data pointer    int dataLength; // Data length    struct node *next; // Pointer to the next node }Node;

As you can see, the type of the next pointer in the node structure is also of node type.

2.2 Function Pointers

Pointers are the soul of the C language, making it more flexible and powerful than other languages. Therefore, mastering pointers is essential when learning C. A function pointer is a pointer that points to the starting address of a function in memory. Through function pointers, you can pass functions as parameters to other functions and call them at appropriate times, enabling features like asynchronous communication.For example, the signal registration function in UNIX/Linux systems has the following prototype:

void (*signal(int signo, void (*func)(int))) (int);

When using it, you need to define a signal handler externally and then register the handler with the process using signal(sigNo, handler). When the signal occurs, the process can call back the signal handler.

2.3 Using Function Pointers as Structure Members

As mentioned earlier, structure members can be simple data structures, other structures, or pointers. When a function pointer is used as a member of a structure, and these functions are only used to operate on the data within that structure, it can form an independent entity that contains both data and operations on that data, naturally leading to the concept of a class.

3 Features of Object-Oriented Languages

Generally, inheritance, encapsulation, and polymorphism are considered the three essential features that object-oriented languages must support. It is through these three features that the advantages of object-oriented programming over procedural programming can be demonstrated.Due to the promotion by language developers or various other reasons, the idea of object-oriented programming is superficially realized through the language. However, in reality, object-oriented programming is a software design philosophy that can be completely independent of specific implementations.Nevertheless, it is undeniable that these so-called pure object-oriented languages have much better code readability and alignment with human natural thinking compared to procedural languages.

3.1 Language-Level Object Orientation

When describing an object, we generally need to describe some of its attributes. For example, a box is an entity that has six faces, color, weight, whether it is empty, etc., and can hold items and allow items to be taken out.In object-oriented languages, such objects are typically abstracted into a class:

class Box{     color color;     int weight;     boolean empty;         put(something);     something get();  }

When operating on the box, you can perform actions like:

Box.put(cake);  Box.get(); // Retrieve something from the box.

In procedural languages, operations on the box are usually done by passing the entity to a global function. For example, operating on Box might look like this:

Put(Box, cake); // Place a cake in the box Get(Box); // Retrieve something from the box

Clearly, the first code form is more logical, so most object-oriented languages provide support for such language-level details, greatly enhancing code readability and understandability.C, as a flexible and simple language, allows us to implement such elegant code forms using the simple mechanisms it provides.

4 Object-Oriented Programming in C

As mentioned earlier, object-oriented programming is a software design philosophy that is independent of the language. In this section, I will use a linked list as an example to illustrate how to design object-oriented style code in C.

4.1 Defining Interfaces

Interfaces are an important concept in object-oriented languages. An interface only commits to what functionalities the implementing entity can provide without exposing the implementation details. The benefit of this is that the implementer can adjust the implementation without touching the code of the interface users.Let’s look at the interface definition for a linked list:

#ifndef _ILIST_H  #define   _ILIST_H   // Define the node structure in the linked list typedef struct node{     void *data;     struct node *next;  }Node;   // Define the linked list structure typedef struct list{     struct list *_this;     Node *head;     int size;     void (*insert)(void *node); // Function pointer    void (*drop)(void *node);     void (*clear)();     int (*getSize)();     void* (*get)(int index);     void (*print)();  }List;   void insert(void *node);  void drop(void *node);  void clear();  int getSize();  void* get(int index);  void print();   #endif   /* _ILIST_H */

In the IList interface, it is clear that for a list entity (which is an object), operations such as insert, drop, clear, getSize, get(index), and print can be performed.

4.2 Implementing the Interface

Constructor:

Node *node = NULL;  List *list = NULL;   void insert(void *node);  void drop(void *node);  void clear();  int getSize();  void print();  void* get(int index);   List *ListConstruction(){     list = (List*)malloc(sizeof(List));     node = (Node*)malloc(sizeof(Node));     list->head = node;     list->insert = insert; // Register the insert function implementation on the list entity    list->drop = drop;     list->clear = clear;     list->size = 0;     list->getSize = getSize;     list->get = get;     list->print = print;     list->_this = list; // Use _this pointer to save the list itself     return (List*)list;  }

It is important to note the _this pointer here, which ensures that external operations on the list map to operations on _this, thereby simplifying the code.

// Insert a node into a list object void insert(void *node){     Node *current = (Node*)malloc(sizeof(Node));         current->data = node;     current->next = list->_this->head->next;     list->_this->head->next = current;     (list->_this->size)++;  }   // Delete a specified node void drop(void *node){     Node *t = list->_this->head;     Node *d = NULL;     int i = 0;     for(i; i < list->_this->size; i++){         d = list->_this->head->next;         if(d->data == ((Node*)node)->data){             list->_this->head->next = d->next;             free(d);             (list->_this->size)--;             break;         }else{             list->_this->head = list->_this->head->next;         }     }     list->_this->head = t;  }

Other implementation codes can be found in the download section; due to space limitations, they will not be listed here.

5 Testing

5.1 Test Code

Now, all the previous work is aimed at ensuring that the API exposed to users is as simple and elegant as possible. It is time for testing:

int main(int argc, char** argv) {     List *list = (List*)ListConstruction(); // Construct a new linked list     // Insert some values for testing    list->insert("Apple");     list->insert("Borland");     list->insert("Cisco");     list->insert("Dell");     list->insert("Electrolux");     list->insert("FireFox");     list->insert("Google");         list->print(); // Print the entire list                printf("list size = %d\n", list->getSize());         Node node;     node.data = "Electrolux";     node.next = NULL;      list->drop(&node); // Delete a node        node.data = "Cisco";     node.next = NULL;     list->drop(&node); // Delete another node        list->print(); // Print again    printf("list size = %d\n", list->getSize());     list->clear(); // Clear the list     return 0;  }

Running result:How to Write Maintainable Embedded Programming Code?

6 Conclusion

The UNIX platform, where C language was born, advocates a design philosophy of simplicity, allowing users to connect these simple tools like building blocks to create powerful, complete applications.It can be said that C inherits this well; it is very concise and powerful. However, since C was developed early when object-oriented ideas were not mature, a large number of procedural C applications emerged, leading to the misconception that C is a procedural language. In fact, C only provides simple, powerful, and general capabilities; how you want to assemble these capabilities is entirely up to you.

Leave a Comment