Memory Management Functions
#include <keychain/keychain_headers.h>
Overview
Memory management functions provide consistent buffer allocation, deallocation, and string handling across the Keychain Core C API to prevent memory leaks and ensure proper resource cleanup.
Buffer Management Functions
kc_common_delete_buffer()
unsigned int kc_common_delete_buffer(char** buffer_ptr)
Frees a string buffer allocated by Keychain functions and sets the pointer to NULL.
Parameters:
-
buffer_ptr
- Pointer to string pointer (set to NULL after freeing)
Returns: Error code (0 = success)
Example:
char* hash_result = NULL;
unsigned int hash_size = 0;
// Function allocates memory for hash_result
unsigned int result = kc_gateway_hash(&hash_result, &hash_size, "data", 4);
if (result == 0) {
printf("Hash: %s\n", hash_result);
// Always free allocated memory
kc_common_delete_buffer(&hash_result);
// hash_result is now NULL
}
kc_common_delete_buffer_array()
unsigned int kc_common_delete_buffer_array(
char*** buffer_array_ptr,
unsigned int array_size
)
Frees an array of string buffers and the array itself.
Parameters:
-
buffer_array_ptr
- Pointer to string array pointer (set to NULL after freeing) -
array_size
- Number of strings in the array
Returns: Error code (0 = success)
Example:
char** persona_names = NULL;
unsigned int name_count = 0;
// Function allocates array of strings
unsigned int result = kc_gateway_get_persona_names(gateway, &persona_names, &name_count);
if (result == 0) {
for (unsigned int i = 0; i < name_count; i++) {
printf("Persona: %s\n", persona_names[i]);
}
// Free entire array
kc_common_delete_buffer_array(&persona_names, name_count);
// persona_names is now NULL
}
kc_common_delete_ptr_array_shallow()
unsigned int kc_common_delete_ptr_array_shallow(void*** ptr_array_ptr)
Frees an array of pointers without freeing the pointed-to objects (shallow delete).
Parameters:
-
ptr_array_ptr
- Pointer to pointer array (set to NULL after freeing)
Returns: Error code (0 = success)
Example:
kc_persona_t** personas = NULL;
unsigned int persona_count = 0;
// Get array of persona pointers
unsigned int result = kc_gateway_retrieve_personas(gateway, &personas, &persona_count);
if (result == 0) {
// Use personas...
// Free individual persona objects first
for (unsigned int i = 0; i < persona_count; i++) {
kc_persona_destruct(&personas[i]);
}
// Then free the array itself
kc_common_delete_ptr_array_shallow((void***)&personas);
}
kc_common_delete_ptr_array_deep()
unsigned int kc_common_delete_ptr_array_deep(
void*** ptr_array_ptr,
unsigned int array_size,
void (*destructor_func)(void**)
)
Frees an array of pointers and calls a destructor function for each pointed-to object (deep delete).
Parameters:
-
ptr_array_ptr
- Pointer to pointer array (set to NULL after freeing) -
array_size
- Number of pointers in the array -
destructor_func
- Function to call for each object
Returns: Error code (0 = success)
Binary Data Management
kc_common_allocate_buffer()
unsigned int kc_common_allocate_buffer(
char** out_buffer,
unsigned int buffer_size
)
Allocates a buffer of specified size for use with Keychain functions.
Parameters:
-
out_buffer
- Pointer to buffer pointer (output) -
buffer_size
- Size of buffer to allocate
Returns: Error code (0 = success)
Example:
char* work_buffer = NULL;
unsigned int buffer_size = 1024;
unsigned int result = kc_common_allocate_buffer(&work_buffer, buffer_size);
if (result == 0) {
// Use buffer for operations
memset(work_buffer, 0, buffer_size);
// Always clean up
kc_common_delete_buffer(&work_buffer);
}
kc_common_copy_buffer()
unsigned int kc_common_copy_buffer(
const char* source_buffer,
unsigned int source_size,
char** out_dest_buffer,
unsigned int* out_dest_size
)
Creates a copy of a buffer with proper memory allocation.
Parameters:
-
source_buffer
- Source data to copy -
source_size
- Size of source data -
out_dest_buffer
- Pointer to destination buffer pointer (output, caller must free) -
out_dest_size
- Pointer to destination size (output)
Returns: Error code (0 = success)
String Utilities
kc_common_string_length()
unsigned int kc_common_string_length(
const char* string_buffer,
unsigned int* out_length
)
Safely calculates the length of a null-terminated string.
Parameters:
-
string_buffer
- String to measure -
out_length
- Pointer to length result (output)
Returns: Error code (0 = success)
kc_common_string_copy()
unsigned int kc_common_string_copy(
const char* source_string,
char** out_dest_string,
unsigned int* out_dest_length
)
Creates a copy of a null-terminated string with proper allocation.
Parameters:
-
source_string
- Source string to copy -
out_dest_string
- Pointer to destination string pointer (output, caller must free) -
out_dest_length
- Pointer to destination length (output)
Returns: Error code (0 = success)
kc_common_string_concatenate()
unsigned int kc_common_string_concatenate(
const char* string1,
const char* string2,
char** out_result_string,
unsigned int* out_result_length
)
Concatenates two strings with proper memory allocation.
Parameters:
-
string1
- First string -
string2
- Second string -
out_result_string
- Pointer to result string pointer (output, caller must free) -
out_result_length
- Pointer to result length (output)
Returns: Error code (0 = success)
Usage Patterns
Basic Memory Management Pattern
// Standard pattern for functions that allocate memory
void demonstrate_basic_memory_pattern() {
char* allocated_string = NULL;
unsigned int string_size = 0;
// Call function that allocates memory
unsigned int result = kc_gateway_hash(&allocated_string, &string_size, "test", 4);
if (result == 0) {
// Use the allocated string
printf("Result: %s (size: %u)\n", allocated_string, string_size);
// Always free when done
kc_common_delete_buffer(&allocated_string);
// allocated_string is now NULL
} else {
// Handle error - no memory was allocated
printf("Function failed with error: %u\n", result);
}
}
Array Management Pattern
// Pattern for managing arrays of strings
void demonstrate_array_management() {
char** string_array = NULL;
unsigned int array_count = 0;
// Function that returns array of strings
unsigned int result = kc_gateway_get_contact_names(gateway, &string_array, &array_count);
if (result == 0) {
// Process each string in the array
for (unsigned int i = 0; i < array_count; i++) {
printf("Contact %u: %s\n", i, string_array[i]);
}
// Free the entire array (including individual strings)
kc_common_delete_buffer_array(&string_array, array_count);
// string_array is now NULL
}
}
Object Array Management Pattern
// Pattern for managing arrays of objects
void demonstrate_object_array_management() {
kc_persona_t** persona_array = NULL;
unsigned int persona_count = 0;
// Get array of persona objects
unsigned int result = kc_gateway_retrieve_personas(gateway, &persona_array, &persona_count);
if (result == 0) {
// Use the persona objects
for (unsigned int i = 0; i < persona_count; i++) {
char* persona_name = NULL;
unsigned int name_size = 0;
kc_persona_get_name(persona_array[i], &persona_name, &name_size);
printf("Persona %u: %s\n", i, persona_name);
kc_common_delete_buffer(&persona_name);
}
// Clean up: destroy each object first
for (unsigned int i = 0; i < persona_count; i++) {
kc_persona_destruct(&persona_array[i]);
}
// Then free the array itself
kc_common_delete_ptr_array_shallow((void***)&persona_array);
// persona_array is now NULL
}
}
Safe String Operations
// Safe string handling with proper cleanup
void demonstrate_safe_string_operations() {
const char* input1 = "Hello, ";
const char* input2 = "World!";
char* concatenated = NULL;
unsigned int concat_length = 0;
// Concatenate strings safely
unsigned int result = kc_common_string_concatenate(
input1, input2, &concatenated, &concat_length
);
if (result == 0) {
printf("Concatenated: %s (length: %u)\n", concatenated, concat_length);
// Create a copy
char* copied_string = NULL;
unsigned int copied_length = 0;
result = kc_common_string_copy(concatenated, &copied_string, &copied_length);
if (result == 0) {
printf("Copy: %s\n", copied_string);
kc_common_delete_buffer(&copied_string);
}
kc_common_delete_buffer(&concatenated);
}
}
Error-Safe Cleanup Pattern
// Robust cleanup pattern that handles errors
void demonstrate_error_safe_cleanup() {
char* buffer1 = NULL;
char* buffer2 = NULL;
char** array = NULL;
kc_persona_t* persona = NULL;
unsigned int array_count = 0;
// Multiple allocations that might fail
unsigned int result1 = kc_gateway_hash(&buffer1, NULL, "data1", 5);
unsigned int result2 = kc_gateway_hash(&buffer2, NULL, "data2", 5);
unsigned int result3 = kc_gateway_get_persona_names(gateway, &array, &array_count);
unsigned int result4 = kc_gateway_create_persona(gateway, &persona, "test", 4, "test", 4, KC_SECURITY_LEVEL_MEDIUM, 1);
// Check if operations succeeded
if (result1 == 0 && result2 == 0 && result3 == 0 && result4 == 0) {
// Use allocated resources
printf("All allocations successful\n");
printf("Hash1: %s\n", buffer1);
printf("Hash2: %s\n", buffer2);
printf("Persona count: %u\n", array_count);
}
// Cleanup - always done regardless of success/failure
// Check each pointer before freeing
if (buffer1) {
kc_common_delete_buffer(&buffer1);
}
if (buffer2) {
kc_common_delete_buffer(&buffer2);
}
if (array) {
kc_common_delete_buffer_array(&array, array_count);
}
if (persona) {
kc_persona_destruct(&persona);
}
printf("Cleanup completed\n");
}
Custom Allocation Tracking
// Optional: Track allocations for debugging memory leaks
#ifdef DEBUG_MEMORY
static unsigned int allocation_count = 0;
static unsigned int deallocation_count = 0;
void track_allocation() {
allocation_count++;
printf("DEBUG: Allocation count: %u\n", allocation_count);
}
void track_deallocation() {
deallocation_count++;
printf("DEBUG: Deallocation count: %u\n", deallocation_count);
}
void print_memory_stats() {
printf("Memory Statistics:\n");
printf(" Allocations: %u\n", allocation_count);
printf(" Deallocations: %u\n", deallocation_count);
printf(" Potential leaks: %u\n", allocation_count - deallocation_count);
}
// Wrapper functions for debugging
unsigned int debug_delete_buffer(char** buffer_ptr) {
if (buffer_ptr && *buffer_ptr) {
track_deallocation();
}
return kc_common_delete_buffer(buffer_ptr);
}
#endif
RAII-Style Helper Macros
// Macros to simplify cleanup patterns
#define CLEANUP_BUFFER(ptr) \
do { if (ptr) { kc_common_delete_buffer(&(ptr)); } } while(0)
#define CLEANUP_BUFFER_ARRAY(ptr, count) \
do { if (ptr) { kc_common_delete_buffer_array(&(ptr), (count)); } } while(0)
#define CLEANUP_OBJECT(ptr, destructor) \
do { if (ptr) { destructor(&(ptr)); } } while(0)
// Usage example with macros
void demonstrate_cleanup_macros() {
char* hash_result = NULL;
char** name_array = NULL;
kc_persona_t* persona = NULL;
unsigned int name_count = 0;
// Perform operations...
kc_gateway_hash(&hash_result, NULL, "test", 4);
kc_gateway_get_persona_names(gateway, &name_array, &name_count);
kc_gateway_create_persona(gateway, &persona, "test", 4, "test", 4, KC_SECURITY_LEVEL_MEDIUM, 1);
// Simple cleanup using macros
CLEANUP_BUFFER(hash_result);
CLEANUP_BUFFER_ARRAY(name_array, name_count);
CLEANUP_OBJECT(persona, kc_persona_destruct);
}
Memory Safety Guidelines
Always Free Allocated Memory
-
Every function that returns allocated memory must be paired with appropriate cleanup
-
Use the matching cleanup function for each allocation type
-
Check function documentation to determine if memory is allocated
Check Pointers Before Cleanup
// Safe cleanup pattern
if (buffer_ptr && *buffer_ptr) {
kc_common_delete_buffer(&buffer_ptr);
}
Handle Partial Failures
// Clean up even when some operations fail
char* buffer1 = NULL;
char* buffer2 = NULL;
if (operation1(&buffer1) == 0) {
// buffer1 allocated successfully
}
if (operation2(&buffer2) == 0) {
// buffer2 allocated successfully
}
// Always cleanup what was allocated
if (buffer1) kc_common_delete_buffer(&buffer1);
if (buffer2) kc_common_delete_buffer(&buffer2);
Related Functions
-
Initialization and Cleanup - Library lifecycle
-
Error Handling - Error management
-
Gateway Operations - Functions that allocate memory
See Also
-
C++ Gateway Class - Automatic memory management
-
{c-memory-management}[C Memory Management Best Practices]
-
{valgrind-guide}[Valgrind Memory Debugging Guide]