Information Hiding in C Using Incomplete Structs

Although the C programming language is procedural and lacks native class-based constructs, it can strictly enforce object-oriented information hiding and encapsulation using incomplete struct declarations. Often referred to as the "opaque pointer" or "Cheshire Cat" technique, this approach hides the internal representation of data from consuming code. This article explains how declaring structures without defining their members in public header files creates robust application programming interfaces (APIs), prevents unwanted state manipulation, and decouples interface from implementation in C.

The Mechanism of Incomplete Structs

An incomplete type in C is a type that describes an identifier but lacks the information needed to determine its size or internal member layout. While the compiler cannot instantiate an incomplete struct directly or access its fields, it can allocate memory for a pointer to that struct because all object pointers share a uniform platform-specific size.

By placing only the forward declaration of a struct in a public header file, the library author restricts external code to interacting exclusively with pointers to that type.

Implementing the Opaque Pointer Pattern

Implementing this design requires separating the public interface from the internal layout across header (.h) and source (.c) files.

1. The Public Interface (database.h)

The header file declares an incomplete struct type and the function prototypes that operate on it. It acts as the public contract.

#ifndef DATABASE_H
#define DATABASE_H

/* Incomplete struct declaration */
typedef struct Database Database;

/* Public API functions */
Database* db_create(const char *connection_string);
void db_connect(Database *db);
void db_disconnect(Database *db);
void db_destroy(Database *db);

#endif

Consumers include database.h, allowing them to declare variables of type Database*. However, attempting to perform operations like sizeof(Database) or accessing fields via db->field triggers a compilation error because the structure's contents remain undefined in this scope.

2. The Private Implementation (database.c)

The corresponding source file contains the concrete definition of the struct along with the memory management and implementation logic.

#include <stdlib.h>
#include <string.h>
#include "database.h"

/* Concrete struct definition hidden from the public interface */
struct Database {
    char *connection_string;
    int is_connected;
    int query_timeout;
};

Database* db_create(const char *connection_string) {
    Database *db = malloc(sizeof(struct Database));
    if (!db) return NULL;

    db->connection_string = strdup(connection_string);
    db->is_connected = 0;
    db->query_timeout = 30;
    return db;
}

void db_connect(Database *db) {
    if (db) {
        db->is_connected = 1;
    }
}

void db_disconnect(Database *db) {
    if (db) {
        db->is_connected = 0;
    }
}

void db_destroy(Database *db) {
    if (db) {
        free(db->connection_string);
        free(db);
    }
}

The fields connection_string, is_connected, and query_timeout are only visible within translation units that include this concrete definition.

Object-Oriented Principles Achieved

Using incomplete structs reproduces several foundational object-oriented benefits:

  • Strict Encapsulation: Client code cannot read or modify member variables directly. All mutations must pass through vetted interface functions, preserving internal invariants.
  • Decoupling and ABI Stability: The concrete struct can be modified—such as by adding, reordering, or removing fields—without altering the header file. Consequently, consuming code does not need to be recompiled when internal layout changes occur, preserving the Application Binary Interface (ABI).
  • Compile-Time Access Control: Unlike languages with keywords like private where metadata is still readable in headers, incomplete structs leverage the C compilation model to physically prevent the compiler from knowing the implementation details in client modules.