The Internet is the first thing that humanity has built that humanity doesn’t understand, the largest experiment in anarchy that we have ever had.
-- Eric Schmidt
All too often someone, somewhere, on some forum … will lament the lack of encapsulation and isolation in the C programming language. This happens with such regularity that I now feel compelled to address the myth once and forever. This means I can post links to this page for any future assertion of that nature without having to retype the explanation in everytime someone is wrong on the internet
Just to be clear, C is an old language lacking many, many, many modern features. One of the features it does not lack is encapsulation and isolation.
Lets look at a class definition that has private
members; after all, that’s where the isolation comes from, right? If all the fields were public, then it’d be just like C but with inheritance.1
class StringBuilder {
private String payload;
public void Append(String snippet) {
payload = payload + snippet;
} };
Any attempt by a caller to access the field payload
results in a compilation error. Pretty nifty. You can see why this can be useful.
The C programming language doesn’t have classes2, but it does have structs
, which look like this:
struct StringBuilder {
char *payload;
};
void Append(struct StringBuilder *obj, const char *snippet);
There are no access modifiers; all fields in a struct are public. This is why there is always someone complaining that C doesn’t provide encapsulation or isolation: everything is visible in a struct, to everyone, all the time, even the callers of the function Append()
.
The complaint isn’t necessarily true. While you can indeed dump everything into a single source file and call it a day, the more common option is to split things into different files and modules.
When code is in different source files, they are are encapsulated into a module. Each “module” in C consists of an interface file which callers can use to call functions which exist in the implementation file.
The interface file, called the header file (with a file extension of .h
) is a contract that tells the user of the module what functions and types are needed to use the implementation, frequently simply called the source file. After compilation, you might have a compiled implementation, and thus may not even have access to the source code of Append()
.
The calling programs have to use the header file in order to make legal (under C) calls to Append()
; the implementation might only be available in compiled form, after all, and not source code form.
So, in the header you do:
typedef struct StringBuilder StringBuilder;
void Append(StringBuilder *obj, const char *snippet);
In the implementation you do:
struct StringBuilder {
char *payload;
};
void Append(StringBuilder *obj, const char *snippet)
{
... }
And … that’s it!
Now any code using a struct of type StringBuilder
can use it all they want to build up a string, but they can never see the fields within it.
Hell, they cannot even malloc()
their own StringBuilder
instance, because even the size of the StringBuilder
is hidden. They have to use creation and deletion functions provided in the implementation as specified in the interface.
So now you have a way to create an instance of an object with all its fields hidden from any caller. You have also forced all callers to stop messing about with the fields in your object - all access to the object is guarded by the functions in the implementation (as specified in the header).
You don’t have inheritance, but you do have one very important characteristic: you can create objects from this class, and use them, from within Python.
Or PHP.
Ruby, even.
It’ll work with most Lisp implementations.
You can call it from Java.
In fact, I don’t believe there is a single programming language in common use which cannot use this object. In many cases the programmer from the other language won’t even have to do much work to use this class.3]
My Makefiles
already have rules to automatically generate the interface so that the C code I write in this manner is callable from within Android applications.
And that’s how you get encapsulation and isolation with strong guarantees in C. Don’t believe everything you read on the internet.4