Encapsulation in C

9 March 2023 • by terrablue

C's static keyword has two meanings. With function variables, it denotes variables maintaining their values across function invocation. When used in front of function definitions, it delimits the scope of the function to the file it's defined in. I'm concerned here with the second meaning, which provides a useful form of encapsulation in C.

However, what happens when you want to have a public facing function, but also use it in the file it's declared in?

When sharing code between files, you normally declare functions in header files which can be then used to inform other source files of the functions by including them. Consider the following example.

In some-header.h

int some_header_int_identity_fn(int some_param);

Then, in some-source.c

int some_header_int_identity_fn(int some_param) {
  return some_param;
}

Then, in another-source.c

#include "some-header.h"

int main() {
  return some_header_int_identity_fn(0);
}

This is all fine and dandy. But consider the case you'd be also using the some_header_int_identity_fn function within some-source.c itself. Namescoping within the file where the function is defined is pretty lengthy and awkward.

To solve this problem, you can use function pointer binding in C. Consider the following example from the Flog code base.

In string.h

size_t flog_string_length(char const string[]);

In string.c

size_t flog_string_length(char const string[]) {
  size_t length = 0;
  while (string[length] != 0) {
    length++;
  }
  return length;
}
static size_t (* get_length)(char const string[]) = flog_string_length;

What happens here is that in your source file, you bind flog_string_length to a local, static symbol. This allows you to use get_length inside string.c and flog_string_length outside of it (and potentially as part of an external API). It also means you could have different local get_length functions, bound to different definitions, in different files.

If you find, like me, the order of first declaring the externally visible function and then binding it to a local function kind of inverted, you can also do this in another way, the preferable way in Flog. To do this, you need to change the declaration in string.h to that of an extern function pointer.

In string.h

extern size_t (* flog_string_length)(char const string[]);

The extern keyword tells C that the flog_string_length will be resolved during linkage.

You then invert the binding in in string.c

static size_t get_length(char const string[]) {
  size_t length = 0;
  while (string[length] != 0) {
    length++;
  }
  return length;
}
size_t (* flog_string_length)(char const string[]) = get_length;

First, you're declaring a static (local) function get_length. This is your black box logic. Then, at some later point (or right away), you bind the get_length to the function pointer flog_string_length, making its code available externally. Personally I find this form better, even if it comes at the cost of a little bit more verbosity.

Using this technique has many advantages. You separate implementation from exposure, and have a one-liner dedicated to exposure. You can easily change the exposed name of a function without influencing its internal usage in the file it's defined it, and best of all, you can use short names that make sense in a local file scope, and long, namespaced names that make sense in an external scope.