Object-Oriented Programming in C (Introduction)

Hello everyone, today we will discuss a topic that is both popular and somewhat <span>"non-mainstream"</span> in the embedded community, Object-Oriented Programming (OO) in C.

C has long been the preferred language in the embedded field due to its efficiency, flexibility, and closeness to hardware. However, as embedded systems become increasingly complex, pure procedural programming sometimes struggles to meet the challenges of code organization, reuse, and maintenance.

The principles of Object-Oriented Programming (<span>OO</span>), such as encapsulation, inheritance, and polymorphism, are widely applied in languages like <span>C++</span>, <span>Java</span>, and can effectively enhance software quality.

So the question arises: since C is not inherently object-oriented, can we implement OO principles in C, or even construct object-oriented structures? The answer is yes!

This series of articles will guide you through how to elegantly implement object-oriented programming in C. Before diving into coding, we need to understand a few basic concepts of Object-Oriented Programming (OO).

1. Basic Concepts of Object-Oriented Programming

Components of Object-Oriented Programming:

  • Class: A class is a structural template for organizing data, defining both the data and the methods that operate on that data.

  • Object: An object is an instance of a data structure (class).

  • Relationship: The associations between classes (objects), where classes (objects) interact to achieve corresponding logic.

Relationships include: association, aggregation, composition, dependency, inheritance (generalization, implementation), etc.

Three Characteristics of Object-Oriented Thinking:

  • Encapsulation: Binding data and methods that operate on the data together, hiding internal implementation details, and enhancing module independence.

  • Inheritance: Inheritance is the relationship between classes, where subclasses inherit properties and methods from parent classes, enabling code reuse and reducing redundancy.

  • Polymorphism: Allows the use of a unified interface to handle different types of objects, enhancing code flexibility and scalability.

However, the core idea of object-oriented programming is “Abstraction,” which focuses on the external behavior of objects while ignoring internal complexities, thereby reducing cognitive load. From abstraction, we derive the five basic principles of design (SOLID) and further summarize common GOF design patterns.

2. Mathematical Model of Object-Oriented Programming

The mathematical model of object-oriented programming can be summarized as follows:

  1. Object Creation Formula:

    O = C.construct(P)

    Where O is the object, C is the class, and P is the parameters.

  2. Object Access Formula:

    V = O.M(P)

    Where V is the access result, O is the object, M is the method, and P is the parameters.

  3. Object Modification Formula:

    M = O.M(P)

    Where M is the modification result, O is the object, M is the method, and P is the parameters.

  4. Object Destruction Formula:

    D = O.destroy()

    Where D is the destruction result, O is the object, and destroy is the destruction method.

3. Limitations of OO in C

From the previous two sections, it can be seen that C has limitations in syntax regarding Object-Oriented Programming (OO). These limitations manifest as follows:

Concept Limitations Compensation
1. Class Missing <span>class</span> keyword <span>struct</span> keyword to define data structures
2. Object Can build objects on the stack but lacks <span>new</span> keyword for heap allocation <span>malloc</span> keyword, <span>(T*)</span> type casting
3. Relationship Missing inheritance syntax Composition replaces inheritance
4. Encapsulation Defined data lacks privacy protection cannot define functions Data: separate definition from implementation Methods: function pointers
5. Inheritance Missing inheritance syntax Composition replaces inheritance
6. Polymorphism Missing <span>override</span> keyword Function pointers
7. Constructor Cannot explicitly define constructors cannot explicitly define constructor parameters Global functions variable argument lists
8. Access Method Cannot hide <span>this</span> pointer Explicitly pass <span>this</span> pointer
9. Destructor Cannot explicitly define destructors Global functions
10. RAII Missing mechanism support None

These “limitations” create challenges for implementing strict OO in C, but they also compel us to explore specific programming patterns and techniques to compensate.

4. Foundations of OO in C

C does not provide syntactic sugar for OO, but it offers building blocks for implementing OO principles.

  • Structs:

    This is the foundation for implementing “<span>objects</span>” in C. Structs can combine related data fields (object properties).

  • Function Pointers:

    This is key to implementing “<span>methods</span>” or “<span>behaviors</span>” in C. Function addresses can be stored in variables for dynamic invocation.

4.1 Foundations and Constraints of OO in C

Feature Advantages Limitations
Struct Members Flexible memory layout control Cannot enforce encapsulation
Function Pointers Dynamic binding for polymorphism Increased complexity in initialization
Composition over Inheritance Reduces coupling Requires manual management of hierarchy

4.2 Access Methods in OO C

In C, there is no direct method call syntax like <span>object.method()</span><span><span>. Common access methods include two types:</span></span>

  • Functional Access: Pass the object as a parameter to the processing function.

// Example: Functional access of a simple "Runnable" object
struct RunnableData {    int state;    // ... other data};
// Processing function, taking object data as the first parametervoid runnable_step(struct RunnableData* runnable, void* param) {    // Execute logic based on runnable->state    runnable->state++;    printf("Running step, state: %d\n", runnable->state);}
// Example callstruct RunnableData runnable = {0};
runnable_step(&runnable, NULL);

Characteristics:

Data and functions are defined separately, requiring explicit function specification and object passing during calls.

  • “Object-style” Access (via Function Pointers): Store function pointers as struct members to simulate method calls.

// Example: Object-style access using function pointers
struct Runnable {    int state;    // ... other data    // Function pointer member, pointing to the object's "behavior"    void (*step)(struct Runnable* self, void* param);    // Note: Typically, the object's own pointer is passed as the first parameter, similar to C++'s this pointer};
// Implementation functionvoid _runnable_step_impl(struct Runnable* self, void* param) {     // Execute logic based on self->state     self->state++;     printf("Running step (OO style), state: %d\n", self->state);}
// Initialization function (creating the "object")void runnable_init(struct Runnable* runnable) {    runnable->state = 0;    runnable->step = _runnable_step_impl; // Assign implementation function address to function pointer}
// Example callstruct Runnable runnable_oo;
runnable_init(&runnable_oo); // Call through the function pointer in the struct, resembling object method calls
runnable_oo.step(&runnable_oo, NULL);

Characteristics:

Encapsulates data and operations (via function pointers) within the same struct, allowing access to functions through struct members, aligning more closely with OO intuitions.

Requires correct function pointer setup during object creation or initialization.

Comparison of Access Methods:

  • Functional access is closer to traditional C style, simple and intuitive.

  • Object-style access is more aligned with OO style, offering better encapsulation and laying the groundwork for implementing features like polymorphism.

5. Future of OO in C

It is precisely because of these characteristics of C structs that we encounter a series of specific problems and challenges in pursuing object-oriented practices in C.

The upcoming articles will delve into how to address these issues in C, exploring specific implementation patterns and techniques:

  • Problem 1: How to implement class definitions and encapsulation?

  • Problem 2: How to separate class data from access functions?

  • Problem 3: How to implement polymorphism exhibited by class inheritance?

  • Problem 4: How to achieve interface segregation for classes?

  • Problem 5: How to implement constructors and destructors for classes?

This series will focus more on exploring and utilizing “object-style” implementation methods and the principles and patterns behind them. The core is to apply OO “thoughts” and “design principles,” rather than forcibly simulating the syntax of C++ or Java.

Leave a Comment