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.