Encapsulation in C
C is a procedural language and lacks native object-oriented classes. However, we can enforce encapsulation and modularity using the Abstract Data Type (ADT) pattern. This pattern prevents client code from reading or writing a data structure’s fields directly, shielding implementation details behind a clean public interface.
Example: The Incomplete Type Pattern
To hide a struct’s layout, we declare the struct as an incomplete type in the public header and define it fully in the private implementation file.
1. Public Header (book.h)
The header exposes only a pointer to the incomplete structure type:
#pragma once
// Incomplete type declaration
struct book;
// Opaque pointer type alias
typedef struct book* book_t;
// Public interface functions
book_t book_create(const char* title, int pages);
int book_get_pages(book_t self);
void book_destroy(book_t self);
2. Private Implementation (book.c)
The layout of the structure is defined only in the implementation file. Client code compiling against book.h cannot dereference book_t because the compiler does not know its size or members at compile time.
#include "book.h"
#include <stdlib.h>
#include <string.h>
struct book {
char title[32];
int pages;
};
book_t book_create(const char* title, int pages) {
book_t self = malloc(sizeof(struct book));
if (self != NULL) {
strncpy(self->title, title, sizeof(self->title) - 1);
self->pages = pages;
}
return self;
}
int book_get_pages(book_t self) {
return self->pages;
}
void book_destroy(book_t self) {
free(self);
}
UML to C Mapping Rules
When translating UML class diagrams into C source code, apply the following visibility and relationship rules:
Visibility Mapping
- Public Attributes/Methods (
+): Declared in the.hfile. - Private Attributes/Methods (
-): Declared inside the struct definition in the.cfile. Helper functions are declared asstaticinside the.cfile so they are invisible to the linker.
Association and Multiplicity
- Multiplicity 0..1 (Optional Association): Mapped to a single pointer member.
- Multiplicity 0..N (Collection): Mapped to a fixed-size array of pointers along with a count tracking the active size.
#define MAX_AUTHORS 5
struct book {
char title[32];
struct author* authors[MAX_AUTHORS]; // Array of pointers (0..N)
int author_count;
};
Lifecycles: Composition vs. Aggregation
- Composition (Filled Diamond): Represents strict ownership. The parent struct owns the child objects. The parent’s destructor must destroy all children.
- Aggregation (Empty Diamond): Represents a temporary relationship. The child objects exist independently of the parent. The parent’s destructor simply frees its own container, leaving the children intact.
In a system where a Library has a composition relationship with Books, what must the library destructor do?
Define Opaque Pointer Type
#pragma once struct author_st; // Create the opaque pointer type mapping to struct author_st typedef struct author_st* ;
References & Further Reading
- Kernighan, B. W., & Ritchie, D. M. (1988). The C Programming Language (2nd ed.). Prentice Hall. Section 6.7 (Typedef) and Section 6.2 (Structures and Functions).
- VIA University College: Embedded Software 1 (ESW1) Lecture Notes - Section 5 (Abstract Data Types).