Unlike the OOP languages C++ and Objective-C, C does not include object-oriented features. However, since the language has become widely used and object-oriented pro­gram­ming has gained wide­spread pop­ular­ity, strategies for im­ple­ment­ing OOP in C have been created.

Is OOP in C possible?

The pro­gram­ming language C is not intended for object-oriented pro­gram­ming and is a prime example of the struc­tured pro­gram­ming style in im­per­at­ive pro­gram­ming. However, it is possible to replicate object-oriented ap­proaches in C. In fact, C has all the com­pon­ents needed for it and con­trib­uted to forming the basis for object-oriented pro­gram­ming in Python.

In­di­vidu­al abstract data types (ADT) can be defined with OOP. An ADT can be thought of as a set of possible values with functions operating on them. It is important that the ex­tern­ally visible interface and the internal im­ple­ment­a­tion are decoupled from each other. This ensures that the type’s objects behave according to de­scrip­tion.

Object-oriented languages such as Python, Java and C++ use the class concept to model abstract data types. Classes serve as a template for creating similar objects, which is also referred to as in­stan­ti­ation. C does not have classes, and these cannot be modelled within the language. However, there are several ap­proaches for im­ple­ment­ing OOP features in C.

How does OOP work in C?

Un­der­stand­ing how OOP works in C requires first asking, ‘What exactly is object-oriented pro­gram­ming (OOP)?’ OOP is a pro­gram­ming style that is commonly seen in the im­per­at­ive pro­gram­ming paradigm. This sets OOP apart from de­clar­at­ive pro­gram­ming and its spe­cial­isa­tion, func­tion­al pro­gram­ming.

The basic idea of object-oriented pro­gram­ming is to model objects and let them interact with each other. The program flow is a result of the objects in­ter­act­ing and is only fixed at runtime. OOP covers three char­ac­ter­ist­ics:

  1. Objects en­cap­su­late their internal state.
  2. Objects receive messages through their methods.
  3. The methods are assigned dy­nam­ic­ally at runtime.

An object in a pure OOP language such as Java is a self-contained unit. This includes a random complex data structure and methods (functions) that operate on it. The object’s internal state, which is rep­res­en­ted in the data it contains, can only be read and changed through the methods. The language feature ‘garbage collector’ is usually used for the objects’ memory man­age­ment.

Con­nect­ing a data structure and functions to objects is not available in C. The user must put together a man­age­able system of data struc­tures, type defin­i­tions, pointers and functions. The pro­gram­mer is re­spons­ible for al­loc­at­ing and releasing memory in C.

The resulting object-based C code doesn’t look quite like what you’re probably used to in other OOP languages. Nev­er­the­less, it does work. Below is an overview of the main OOP concepts with their equi­val­ents in C:

OOP concept Equi­val­ent in C
Class Struct type
Class instance Struct instance
Instance method Function that accepts pointers to Struct variable
this/self variable Pointer to Struct variable
In­stan­ti­ation Al­loc­a­tion and reference through pointer
new keyword Call malloc

How to model objects as data struc­tures

Let’s look at how an object’s data structure can be modelled in C in a way that is similar to OOP languages. C is a compact language that doesn’t work with many language con­structs. Structs are used to create random complex data struc­tures, with the name ‘struct’ being derived from the term ‘data structure’.

A struct in C defines a data structure that hasfields which are referred to as ‘members’. This type of construct is called a ‘record’ in other languages. A struct can be thought of as a row in a database table, like a composite with several fields and different types.

The syntax for a struct de­clar­a­tion in C is very simple:

struct struct_name;
C

We can also define the struct by spe­cify­ing the members’ name and type. Let’s consider a point in a two-di­men­sion­al space with x and y co­ordin­ates as an example. We’ll outline the struct defin­i­tion:

struct point {
    /*X-coordinate*/
    int x;
    /*Y-coordinate*/
    int y;
};
C

This is followed by the struct variable’s in­stan­ti­ation in con­ven­tion­al C code. We’ll create the variable and ini­tial­ise both fields with 0:

struct point origin = {0, 0};
C

Sub­sequently, the values in the fields can be read and reset. The member access is done using the syntax origin.x and origin.y which you may know from other languages:

/*Read struct member*/
origin.x == 0
/*Assign struct member*/
origin.y = 42
C

However, this violates the en­cap­su­la­tion re­quire­ment. The object’s internal state may only be accessed using methods defined for this purpose. This means that our approach is still missing something.

How to define types for creating objects

As mentioned before, C does not have class concepts. Instead, types can be defined with a typedef statement. We’ll give the data type a new name with typedef:

typedef <old-type-name> <new-type-name>
C

This allows us to define a point type for our point struct:

typedef struct point Point;
C

The com­bin­a­tion of typedef with a struct defin­i­tion is like a class defin­i­tion in Java:

typedef struct point {
    /*X-coordinate*/
    int x;
    /*Y-coordinate*/
    int y;
} Point;
C
Note

In the example, ‘point’ is the name of the struct and ‘Point’ is the name of the defined type.

This would be the cor­res­pond­ing class defin­i­tion in Java:

class Point {
    private int x;
    private int y;
};
Java

Using typedef allows us to create a point variable without using the struct keyword:

Point origin = {0, 0}
/*Instead of*/
struct point origin = {0, 0}
C

The internal state’s en­cap­su­la­tion is still missing.

How to en­cap­su­late the internal state

Objects display their internal state in their data structure. In other OOP languages, such as Java, the keywords ‘private’, ‘protected’, etc. are used to restrict access to object data. This prevents un­au­thor­ised access and ensures that the interface and im­ple­ment­a­tion are separated.

To implement OOP in C, a different mechanism is used. A forward de­clar­a­tion in the header file serves as an interface, resulting in an ‘In­com­plete type’:

/*In C header file*/
struct point;
/*Incomplete type*/
typedef struct point Point;
C

The point struct’s im­ple­ment­a­tion is in a separate C source code file. This embeds the header using include macro. This approach prevents the creation of static variables of the point type. It is still possible to use pointers from this type. Objects are dy­nam­ic­ally created data struc­tures, so they are ref­er­enced with pointers anyway. Pointers to struct instances cor­res­pond roughly to the object ref­er­ences used in Java.

How to replace methods with functions

In OOP languages such as Java and Python, objects include the functions that operate on them in addition to their data. These are called methods and instance methods. We use functions that take a pointer to a struct instance when OOP code is written in C:

/*Pointer to `Point` struct*/
Point * point;
C

C does not have classes. This makes it im­possible to group functions belonging to a type under a common name. Instead, we provide the function names with a prefix con­tain­ing the type’s name. The cor­res­pond­ing function sig­na­tures are declared in the C header file:

/*In C header file*/
/*Function to move update a point's coordinates*/
void Point_move(Point * point, int new_x, int new_y);
C

It is necessary to implement the function in the C source code file:

/*In C source file*/
void Point_move(Point * point, int new_x, int new_y) {
    point->x = new_x;
    point->y = new_y;
};
C

The approach has sim­il­ar­it­ies to Python methods, which are normal functions that take self as the first parameter. Fur­ther­more, the pointer to a struct instance is roughly equi­val­ent to the variable in Java or JavaS­cript. However, the pointer is passed ex­pli­citly when the C function is called in this case:

/*Call function with pointer argument*/
Point_move(point, 42, 51);
C

The point object is available in the method as a variable in the Java function call:

// Call instance method from outside of class
point.move(42, 51)
// Call instance method from within class
this.move(42, 51)
Java

Methods can be called as functions with an explicit self-argument in Python:

# Call instance method from outside or from within class
self.move(42, 51)
# Function call from within class
move(self, 42, 51)
Python

How to in­stan­ti­ate objects

One of C’s defining char­ac­ter­ist­ics is manual memory man­age­ment. Pro­gram­mers must allocate memory for data struc­tures. This is not required in object-oriented and dynamic languages such as Java and Python. In Java, the new keyword is used to in­stan­ti­ate an object. Memory is allocated auto­mat­ic­ally in the back­ground:

// Create new Point instance
Point point = new Point();
Java

We define a special con­struct­or function for in­stan­ti­ation when we write OOP code in C. This allocates memory for our struct instance, ini­tial­ises it and returns a pointer to it:

Point * Point_new(int x, int y) {
    /*Allocate memory and cast to pointer type*/
    Point *point = (Point*) malloc(sizeof(Point));
    /*Initialize members*/
    Point_init(point, x, y);
    // return pointer
    return point;
};
C

Our example decouples the struct members’ ini­tial­isa­tion from the in­stan­ti­ation. A function with the point prefix is used again:

void Point_init(Point * point, int x, int y) {
    point->x = x;
    point->y = y;
};
C

How can a C project be rewritten in an object-oriented manner?

Rewriting an existing project in C using OOP tech­niques is re­com­men­ded only in ex­cep­tion­al cases. The following ap­proaches would be more worth­while:

  1. Rewrite project in a language like C with OOP features and use the existing C code base as a spe­cific­a­tion.
  2. Rewrite parts of the project in an OOP language and keep specific C com­pon­ents.

The second approach should be the most efficient provided the C code base is clean. It is common practice to implement per­form­ance-critical program parts in C and access them from other languages. There probably isn’t another language better suited to this than C. But which languages are suitable for re­build­ing an existing C project using OOP prin­ciples?

Object-oriented languages like C

There is a wide selection of languages like C with built-in object ori­ent­a­tion. C++ is probably the most well-known. However, the language is no­tori­ously complex, which has led many to move away from it in recent years. C code is re­l­at­ively easy to in­cor­por­ate into C++ due to major sim­il­ar­it­ies in the basic language con­structs.

Objective-C is more light­weight than C++. The C dialect, which is based on the original OOP language Smalltalk, was primarily used for pro­gram­ming ap­plic­a­tions on Mac and early iOS operating systems. It was later followed by Apple’s de­vel­op­ment of its own Swift language. Functions written in C can be called from both languages.

Object-oriented languages based on C

There are other OOP pro­gram­ming languages that are suitable for rewriting a C project but are not related to C’s syntax. Standard ap­proaches for including C code exist for Python, Rust, and Java.

Python bindings allow for the inclusion of C code. Python data types may have to be trans­lated into the cor­res­pond­ing ctypes. The C Foreign Function Interface (CFFI) also automates type trans­la­tion.

Rust also supports calling C functions. The external keyword can be used to define a Foreign Function Interface (FFI). Rust functions that access external functions must be declared unsafe:

external "C" {
    fn abs(input: i32) -> i32;
}
fn main() {
    unsafe {
        println!("Absolute value of -3 according to C: {}", abs(-3));
    }
}
Rust
Go to Main Menu