What is compile-time encapsulation in C?

A possible real-world scenario where this would occur is when a database library, written in the days when hard-disk space was very limited, used a single byte to store the 'year' field of a date (e.g. 11-NOV-1973 would have 73 for the year). But, when the Year 2000 came along, this would no longer be sufficient, and the year had then to be stored as a short (16-bit) integer. The relevant (much simplified) header for this library could be this:

// dbEntry.h
typedef struct _dbEntry dbEntry;

dbEntry* CreateDBE(int day, int month, int year, int otherData);
void DeleteDBE(dbEntry* entry);
int GetYear(dbEntry* entry);

And a 'client' program would be:

#include <stdio.h>
#include "dbEntry.h"

int main()
{
    int dataBlob = 42;
    dbEntry* test = CreateDBE(17, 11, 2019, dataBlob);
    //...
    int year = GetYear(test);
    printf("Year = %d\n", year);
    //...
    DeleteDBE(test);
    return 0;
}

The 'original' implementation:

#include <stdlib.h>
#include "dbEntry.h"

struct _dbEntry {
    unsigned char d;
    unsigned char m;
    unsigned char y;    // Fails at Y2K!
    int dummyData;
};

dbEntry* CreateDBE(int day, int month, int year, int otherData)
{
    dbEntry* local = malloc(sizeof(dbEntry));
    local->d = (unsigned char)(day);
    local->m = (unsigned char)(month);
    local->y = (unsigned char)(year % 100);
    local->dummyData = otherData;
    return local;
}

void DeleteDBE(dbEntry* entry)
{
    free(entry);
}

int GetYear(dbEntry* entry)
{
    return (int)(entry->y);
}

Then, at the approach of Y2K, this implementation file would be changed as follows (everything else being left untouched):

struct _dbEntry {
    unsigned char d;
    unsigned char m;
    unsigned short y;   // Can now differentiate 1969 from 2069
    int dummyData;
};

dbEntry* CreateDBE(int day, int month, int year, int otherData)
{
    dbEntry* local = malloc(sizeof(dbEntry));
    local->d = (unsigned char)(day);
    local->m = (unsigned char)(month);
    local->y = (unsigned short)(year);
    local->dummyData = otherData;
    return local;
}

When the client needs to be updated to use the new (Y2K-safe) version, no code changes would be required. In fact, you may not even have to re-compile: simply re-linking to the updated object library (if that's what it is) could be sufficient.


Note: The following list will be non-exhaustive. Edits are welcome!

The applicable scenarios include:

  • Multi-module applications where you don't want recompilation for some reason.
  • Structures used in libraries where you don't want to force the users of the library to recompile each time you change a (published) structure.
  • Structures that contain different elements on the different platforms the module works on.

The most known structure of this kind is FILE. You just call fopen() and get a pointer if successful. This pointer is then handed over to each other function that works on files. But you don't know - and you don't want to know - the details, like contained elements and the size.

Tags:

C