Article by Ayman Alheraki on January 11 2026 10:36 AM
Smart pointers are a powerful programming concept commonly associated with languages like C++. However, with the evolution of the C language and modern programming practices, it is possible to implement similar functionality in C. This article will explore the concept of smart pointers, how they can be implemented in modern C, and provide detailed examples to demonstrate their usage.
Smart pointers are objects that manage the lifetime of dynamically allocated memory. They automatically deallocate memory when it is no longer needed, preventing memory leaks and reducing the risk of dangling pointers. In C++, smart pointers like std::unique_ptr and std::shared_ptr are part of the standard library. In C, we can achieve similar functionality using structs, function pointers, and modern programming techniques.
Automatic Memory Management: Smart pointers automatically free memory when it is no longer in use, reducing the risk of memory leaks.
Resource Safety: They ensure that resources are properly released, even in the presence of errors or exceptions (simulated in C).
Simplified Code: Smart pointers reduce the need for manual memory management, making code cleaner and easier to maintain.
In C, we can implement smart pointers using structs and function pointers. Below, we will implement two types of smart pointers:
Unique Pointer: Ensures that only one pointer owns the resource.
Shared Pointer: Allows multiple pointers to share ownership of the resource.
A unique pointer ensures that only one pointer owns the resource. When the pointer goes out of scope, the resource is automatically freed.
Implementation:
// Define the unique pointer structtypedef struct { void* ptr; void (*deleter)(void*);} UniquePtr;
// Function to create a unique pointerUniquePtr unique_ptr_create(void* ptr, void (*deleter)(void*)) { UniquePtr up; up.ptr = ptr; up.deleter = deleter; return up;}
// Function to release the unique pointervoid unique_ptr_release(UniquePtr* up) { if (up->ptr && up->deleter) { up->deleter(up->ptr); up->ptr = NULL; }}
// Example deleter for integersvoid int_deleter(void* ptr) { free(ptr); printf("Memory freed for integer.\n");}
int main() { // Create a unique pointer for an integer int* data = (int*)malloc(sizeof(int)); *data = 42; UniquePtr up = unique_ptr_create(data, int_deleter);
// Use the pointer printf("Value: %d\n", *(int*)up.ptr);
// Release the pointer (automatically frees memory) unique_ptr_release(&up);
return 0;}Explanation:
The UniquePtr struct contains a pointer (ptr) and a function pointer (deleter) to handle memory deallocation.
The unique_ptr_create function initializes the unique pointer.
The unique_ptr_release function ensures that the resource is freed when the pointer is no longer needed.
A shared pointer allows multiple pointers to share ownership of a resource. The resource is freed only when the last pointer referencing it is destroyed.
Implementation:
// Define the shared pointer structtypedef struct { void* ptr; int* ref_count; void (*deleter)(void*);} SharedPtr;
// Function to create a shared pointerSharedPtr shared_ptr_create(void* ptr, void (*deleter)(void*)) { SharedPtr sp; sp.ptr = ptr; sp.ref_count = (int*)malloc(sizeof(int)); *(sp.ref_count) = 1; // Initialize reference count to 1 sp.deleter = deleter; return sp;}
// Function to copy a shared pointer (increases reference count)SharedPtr shared_ptr_copy(const SharedPtr* sp) { SharedPtr new_sp = *sp; (*(new_sp.ref_count))++; return new_sp;}
// Function to release a shared pointer (decreases reference count and frees memory if needed)void shared_ptr_release(SharedPtr* sp) { (*(sp->ref_count))--; if (*(sp->ref_count) == 0) { if (sp->ptr && sp->deleter) { sp->deleter(sp->ptr); printf("Memory freed for shared resource.\n"); } free(sp->ref_count); sp->ptr = NULL; sp->ref_count = NULL; }}
// Example deleter for integersvoid int_deleter(void* ptr) { free(ptr);}
int main() { // Create a shared pointer for an integer int* data = (int*)malloc(sizeof(int)); *data = 42; SharedPtr sp1 = shared_ptr_create(data, int_deleter);
// Create a copy of the shared pointer SharedPtr sp2 = shared_ptr_copy(&sp1);
// Use the pointers printf("Value (sp1): %d\n", *(int*)sp1.ptr); printf("Value (sp2): %d\n", *(int*)sp2.ptr);
// Release the pointers shared_ptr_release(&sp1); shared_ptr_release(&sp2);
return 0;}Explanation:
The SharedPtr struct contains a pointer (ptr), a reference count (ref_count), and a function pointer (deleter).
The shared_ptr_create function initializes the shared pointer and sets the reference count to 1.
The shared_ptr_copy function increases the reference count when a new shared pointer is created.
The shared_ptr_release function decreases the reference count and frees the resource when the count reaches 0.
Memory Safety: Prevents memory leaks and dangling pointers.
Resource Management: Ensures resources are properly released.
Code Clarity: Reduces the need for manual memory management, making code easier to read and maintain.
No Language Support: Unlike C++, C does not have built-in support for smart pointers, so they must be implemented manually.
Performance Overhead: Reference counting in shared pointers introduces a small performance overhead.
Complexity: Implementing smart pointers in C requires careful handling of memory and reference counts.
Smart pointers are a powerful tool for managing dynamic memory in C. By implementing unique and shared pointers using structs and function pointers, you can achieve automatic memory management and improve the safety and clarity of your code. While C does not have built-in support for smart pointers, the techniques demonstrated in this article provide a solid foundation for using them effectively.