Question
What are the common ways to write object-oriented style code in C, especially when you need polymorphism?
In particular, how can C programs model ideas such as:
- objects with data and behavior
- encapsulation
- inheritance-like composition
- polymorphic behavior
Although C is not an object-oriented language, many codebases still use object-oriented design patterns. What practical techniques are used to achieve this in C?
Short Answer
By the end of this page, you will understand how C can emulate object-oriented programming using structs, functions, opaque types, and function pointers. You will learn how to represent objects, group data with behavior, hide implementation details, and build polymorphic interfaces in a way that is common in real C codebases.
Concept
C does not have built-in classes, inheritance, or virtual methods. However, it gives you low-level building blocks that let you model many object-oriented ideas manually.
The main techniques are:
structfor object state: astructholds the data for an object.- Functions for methods: regular functions operate on a pointer to that
struct. - Opaque pointers for encapsulation: users see only a forward declaration, not the internal fields.
- Function pointers for polymorphism: a table of function pointers can act like virtual methods.
- Composition for reuse: instead of inheritance, C often nests one struct inside another.
For example, in C you might represent a Shape with function pointers like area and draw. A Circle and a Rectangle can each provide their own implementations of those functions. Then code can work with any Shape through a common interface.
This matters because many systems programs, embedded projects, graphics libraries, kernels, and game engines are written in C but still need clean modular design. Object-oriented style in C helps organize code, reduce duplication, and support extensibility without requiring a different language.
Mental Model
Think of a C struct as a device, and function pointers as the buttons attached to it.
- The data fields inside the
structare the device's internal parts. - The functions are the actions the device can perform.
- A pointer to the struct is the actual device you are holding.
- A function pointer table is like a label that says, "When someone presses the
drawbutton, use this specific implementation."
So instead of a class with methods built into the language, you build the same idea yourself:
- data lives in a struct
- behavior lives in functions
- dynamic behavior is selected through function pointers
It is manual, but the design idea is very similar to object-oriented programming.
Syntax and Examples
In C, the most common object-oriented pattern looks like this:
#include <stdio.h>
typedef struct {
int x;
int y;
} Point;
void Point_move(Point *p, int dx, int dy) {
p->x += dx;
p->y += dy;
}
void Point_print(const Point *p) {
printf("Point(%d, %d)\n", p->x, p->y);
}
int main(void) {
Point p = {2, 3};
Point_print(&p);
Point_move(&p, 5, -1);
Point_print(&p);
return 0;
}
This gives you an object-like design:
Pointis the object dataPoint_moveandPoint_printare methods- the first parameter,
Point *p, plays the role ofthisin object-oriented languages
Step by Step Execution
Consider this small example:
#include <stdio.h>
typedef struct Animal Animal;
typedef struct {
void (*speak)(const Animal *self);
} AnimalVTable;
struct Animal {
const AnimalVTable *vtable;
};
typedef struct {
Animal base;
const char *name;
} Dog;
void Dog_speak(const Animal *self) {
const Dog *dog = (const Dog *)self;
printf("%s says woof!\n", dog->name);
}
const AnimalVTable dog_vtable = {
Dog_speak
};
int main(void) {
Dog d;
d.base.vtable = &dog_vtable;
d.name = "Rex";
Animal *a = (Animal *)&d;
a->vtable->speak(a);
return 0;
}
Step by step
AnimalVTabledefines one operation: .
Real World Use Cases
Object-oriented style in C is useful when code needs a clear interface but different implementations.
Common use cases
- Device drivers
- Different hardware devices expose the same operations such as
init,read, andwrite.
- Different hardware devices expose the same operations such as
- GUI or graphics systems
- Different widgets or shapes may all support
draw,resize, orhandle_event.
- Different widgets or shapes may all support
- Parsers and compilers
- Different node types in an abstract syntax tree can support operations like
print,evaluate, orfree.
- Different node types in an abstract syntax tree can support operations like
- Networking libraries
- Different transport types may implement a common send/receive interface.
- Embedded systems
- Sensors, actuators, and communication modules often share a small interface but have different implementations.
- Game engines
- Entities may share operations such as
updateandrenderwhile storing different data.
- Entities may share operations such as
Real Codebase Usage
In real C projects, object-oriented style is usually applied selectively, not everywhere.
Common patterns
1. Opaque structs for encapsulation
In a header:
typedef struct FileReader FileReader;
FileReader *FileReader_create(const char *path);
void FileReader_destroy(FileReader *reader);
int FileReader_read_line(FileReader *reader, char *buffer, int size);
In the .c file:
struct FileReader {
FILE *fp;
int line_count;
};
This hides implementation details from users.
2. Constructor/destructor-style functions
C has no constructors, so code often uses:
create/destroyinit/
Common Mistakes
1. Forgetting to initialize function pointers
Broken code:
Shape s;
s.vtable->area(&s); // undefined behavior
Why it fails:
s.vtablewas never assigned.
Fix:
s.vtable = &some_vtable;
2. Casting to the wrong derived type
Broken code:
const Rectangle *r = (const Rectangle *)self;
Why it fails:
- If
selfactually points to aCircle, the cast is invalid and leads to wrong memory access.
How to avoid it:
- Ensure the object's vtable matches the implementation being called.
- Keep object creation and vtable assignment tightly controlled.
3. Assuming C gives real inheritance
C does not understand inheritance rules. This pattern only works because you manually design memory layout and interfaces.
Avoid assumptions like:
- automatic method dispatch
- runtime type safety
- base-class destructors
You must implement these yourself.
Comparisons
| Concept | In C | In class-based OOP languages |
|---|---|---|
| Object data | struct | class instance fields |
| Method | regular function taking self pointer | member function / method |
| Encapsulation | opaque struct + header/source split | private / public |
| Polymorphism | function pointers / vtable | virtual methods / interfaces |
| Inheritance | manual composition or embedded base struct | built-in inheritance |
| Type safety | manual and limited | language enforced |
Function pointers vs direct function calls
Cheat Sheet
Quick reference
Object-like design in C
typedef struct {
int value;
} Counter;
void Counter_increment(Counter *c) {
c->value++;
}
- Use a
structfor data - Use functions for behavior
- Pass
StructName *selfas the first argument
Encapsulation
In header:
typedef struct MyType MyType;
MyType *MyType_create(void);
void MyType_destroy(MyType *obj);
In source:
struct MyType {
int hidden;
};
Polymorphism pattern
typedef ;
(*do_work)(Base *self);
} BaseVTable;
BaseVTable *vtable;
};
FAQ
How can C support object-oriented programming if it has no classes?
C does not support OOP directly, but you can build similar patterns using structs for data and functions for behavior.
How do you implement polymorphism in C?
The usual technique is to store function pointers in a struct, often called a vtable, and call behavior through that table.
Is inheritance possible in C?
Not as a language feature. You can simulate parts of it by embedding one struct inside another and designing a shared interface.
What is the C equivalent of a method?
A regular function that takes a pointer to the object as its first parameter.
How do you hide private fields in C?
Use an opaque struct: forward-declare the type in the header and define the full struct only in the source file.
Should all C code be written in an object-oriented style?
No. Many programs are clearer with simple procedural code. Use OOP-style patterns when you need modularity, encapsulation, or interchangeable implementations.
Are function pointers in C the same as virtual methods in C++?
They serve a similar purpose for dynamic dispatch, but in C you must build and manage the mechanism manually.
What is the biggest risk when emulating OOP in C?
Manual memory management and manual type handling. Mistakes can easily lead to undefined behavior.
Mini Project
Description
Build a small shape system in C that demonstrates object-oriented style design. You will create a common Shape interface and two implementations: Circle and Rectangle. Each shape will support the same operations, but each type will calculate its area differently. This mirrors how real C libraries use structs and function pointers to support interchangeable behavior.
Goal
Create a C program where different shape types can be stored and used through one shared interface, then print each shape and its area polymorphically.
Requirements
- Define a base
Shapetype with a vtable containingareaanddescribefunction pointers. - Create
CircleandRectanglestructs that includeShapeas a base field. - Implement type-specific
areaanddescribefunctions for each shape. - Store both shape types in an array of
Shape *and process them through the shared interface. - Print each shape description and computed area.
Keep learning
Related questions
Array-to-Pointer Conversion in C and C++ Explained
Learn what array-to-pointer conversion means in C and C++, how array decay works, and how it differs from a pointer to an array.
Building More Fault-Tolerant Embedded C++ Applications for Radiation-Prone ARM Systems
Learn practical C++ and compile-time techniques to reduce soft-error damage in embedded ARM systems exposed to radiation.
C Pointer to Array vs Array of Pointers: How to Read Complex Declarations
Learn the difference between pointer-to-array and array-of-pointers in C, plus a simple rule for reading complex declarations correctly.