The C Programming Language: Myths and Reality (1)

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

Posted by Lelanthran
2023-06-28

Context

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.

Myth: Struct fields cannot be hidden

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().

Reality bytes

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().

And … Ta Da!

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.

But, there’s more …

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.

You got it, I’ll stop now

And that’s how you get encapsulation and isolation with strong guarantees in C. Don’t believe everything you read on the internet.4


Posted by Lelanthran
2023-06-28

  1. I’m being sarcastic, so relax.↩︎

  2. Some would argue it doesn’t have any class either.↩︎

  3. See swig.↩︎

  4. Except for this blog, of course. I’m obviously a paragon of honesty and wisdom, except for when I’m not.↩︎