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);

Avoid Double-Free

// Pointers are set to NULL after freeing
char* buffer = NULL;
get_data(&buffer);

kc_common_delete_buffer(&buffer); // buffer is now NULL
kc_common_delete_buffer(&buffer); // Safe - no operation performed

See Also

  • C++ Gateway Class - Automatic memory management

  • {c-memory-management}[C Memory Management Best Practices]

  • {valgrind-guide}[Valgrind Memory Debugging Guide]