/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Copyright by The HDF Group. * * Copyright by the Board of Trustees of the University of Illinois. * * All rights reserved. * * * * This file is part of HDF5. The full HDF5 copyright notice, including * * terms governing use, modification, and redistribution, is contained in * * the COPYING file, which can be found at the root of the source code * * distribution tree, or in https://www.hdfgroup.org/licenses. * * If you do not have access to either file, you may request a copy from * * help@hdfgroup.org. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /*------------------------------------------------------------------------- * * Created: H5MM.c * Jul 10 1997 * Robb Matzke * * Purpose: Memory management functions * *------------------------------------------------------------------------- */ /****************/ /* Module Setup */ /****************/ /***********/ /* Headers */ /***********/ #include "H5private.h" /* Generic Functions */ #include "H5Eprivate.h" /* Error handling */ #include "H5MMprivate.h" /* Memory management */ /****************/ /* Local Macros */ /****************/ #if defined H5_MEMORY_ALLOC_SANITY_CHECK #define H5MM_SIG_SIZE 4 #define H5MM_HEAD_GUARD_SIZE 8 #define H5MM_TAIL_GUARD_SIZE 8 #define H5MM_BLOCK_FROM_BUF(mem) \ ((H5MM_block_t *)((void *)((unsigned char *)mem - (offsetof(H5MM_block_t, b) + H5MM_HEAD_GUARD_SIZE)))) #endif /* H5_MEMORY_ALLOC_SANITY_CHECK */ /******************/ /* Local Typedefs */ /******************/ #if defined H5_MEMORY_ALLOC_SANITY_CHECK /* Memory allocation "block", wrapped around each allocation */ struct H5MM_block_t; /* Forward declaration for typedef */ typedef struct H5MM_block_t { unsigned char sig[H5MM_SIG_SIZE]; /* Signature for the block, to indicate it was allocated with H5MM* interface */ struct H5MM_block_t *next; /* Pointer to next block in the list of allocated blocks */ struct H5MM_block_t *prev; /* Pointer to previous block in the list of allocated blocks */ union { struct { size_t size; /* Size of allocated block */ hbool_t in_use; /* Whether the block is in use or is free */ } info; double _align; /* Align following buffer (b) to double boundary (unused) */ } u; unsigned char b[]; /* Buffer for caller (includes header and footer) */ } H5MM_block_t; #endif /* H5_MEMORY_ALLOC_SANITY_CHECK */ /********************/ /* Local Prototypes */ /********************/ #if defined H5_MEMORY_ALLOC_SANITY_CHECK static hbool_t H5MM__is_our_block(void *mem); static void H5MM__sanity_check_block(const H5MM_block_t *block); static void H5MM__sanity_check(void *mem); #endif /* H5_MEMORY_ALLOC_SANITY_CHECK */ /*********************/ /* Package Variables */ /*********************/ /*****************************/ /* Library Private Variables */ /*****************************/ /*******************/ /* Local Variables */ /*******************/ #if defined H5_MEMORY_ALLOC_SANITY_CHECK /* Constant strings for block signature, head & tail guards */ static const char H5MM_block_signature_s[H5MM_SIG_SIZE] = {'H', '5', 'M', 'M'}; static const char H5MM_block_head_guard_s[H5MM_HEAD_GUARD_SIZE] = {'D', 'E', 'A', 'D', 'B', 'E', 'E', 'F'}; static const char H5MM_block_tail_guard_s[H5MM_TAIL_GUARD_SIZE] = {'B', 'E', 'E', 'F', 'D', 'E', 'A', 'D'}; /* Flag to indicate the the interface has been initialized */ static hbool_t H5MM_init_s = FALSE; /* Head of the list of allocated blocks */ static H5MM_block_t H5MM_block_head_s; /* Statistics about block allocations */ static unsigned long long H5MM_total_alloc_bytes_s = 0; static size_t H5MM_curr_alloc_bytes_s = 0; static size_t H5MM_peak_alloc_bytes_s = 0; static size_t H5MM_max_block_size_s = 0; static size_t H5MM_total_alloc_blocks_count_s = 0; static size_t H5MM_curr_alloc_blocks_count_s = 0; static size_t H5MM_peak_alloc_blocks_count_s = 0; #endif /* H5_MEMORY_ALLOC_SANITY_CHECK */ #if defined H5_MEMORY_ALLOC_SANITY_CHECK /*------------------------------------------------------------------------- * Function: H5MM__is_our_block * * Purpose: Try to determine if a memory buffer has been allocated through * the H5MM* interface, instead of the system's malloc() routines. * * Return: Success: TRUE/FALSE * Failure: (Can't fail) * * Programmer: Quincey Koziol * Dec 30 2015 * *------------------------------------------------------------------------- */ static hbool_t H5MM__is_our_block(void *mem) { H5MM_block_t *block = H5MM_BLOCK_FROM_BUF(mem); return (0 == HDmemcmp(block->sig, H5MM_block_signature_s, H5MM_SIG_SIZE)); } /*------------------------------------------------------------------------- * Function: H5MM__sanity_check_block * * Purpose: Check a block wrapper around a buffer to validate it. * * Return: N/A (void) * * Programmer: Quincey Koziol * Dec 30 2015 * *------------------------------------------------------------------------- */ static void H5MM__sanity_check_block(const H5MM_block_t *block) { HDassert(block->u.info.size > 0); HDassert(block->u.info.in_use); /* Check for head & tail guards, if not head of linked list */ if (block->u.info.size != SIZET_MAX) { HDassert(0 == HDmemcmp(block->b, H5MM_block_head_guard_s, H5MM_HEAD_GUARD_SIZE)); HDassert(0 == HDmemcmp(block->b + H5MM_HEAD_GUARD_SIZE + block->u.info.size, H5MM_block_tail_guard_s, H5MM_TAIL_GUARD_SIZE)); } } /*------------------------------------------------------------------------- * Function: H5MM__sanity_check * * Purpose: Check a buffer to validate it (just calls * H5MM__sanity_check_block after finding block for buffer) * * Return: N/A (void) * * Programmer: Quincey Koziol * Dec 30 2015 * *------------------------------------------------------------------------- */ static void H5MM__sanity_check(void *mem) { H5MM_block_t *block = H5MM_BLOCK_FROM_BUF(mem); H5MM__sanity_check_block(block); } /*------------------------------------------------------------------------- * Function: H5MM_sanity_check_all * * Purpose: Sanity check all current memory allocations. * * Return: N/A (void) * * Programmer: Quincey Koziol * Jan 5 2016 * *------------------------------------------------------------------------- */ void H5MM_sanity_check_all(void) { H5MM_block_t *curr = NULL; curr = H5MM_block_head_s.next; while (curr != &H5MM_block_head_s) { H5MM__sanity_check_block(curr); curr = curr->next; } /* end while */ } /* end H5MM_sanity_check_all() */ /*------------------------------------------------------------------------- * Function: H5MM_final_sanity_check * * Purpose: Final sanity checks on memory allocation. * * Return: N/A (void) * * Programmer: Quincey Koziol * Jan 1 2016 * *------------------------------------------------------------------------- */ void H5MM_final_sanity_check(void) { HDassert(0 == H5MM_curr_alloc_bytes_s); HDassert(0 == H5MM_curr_alloc_blocks_count_s); HDassert(H5MM_block_head_s.next == &H5MM_block_head_s); HDassert(H5MM_block_head_s.prev == &H5MM_block_head_s); #ifdef H5MM_PRINT_MEMORY_STATS HDfprintf(stderr, "%s: H5MM_total_alloc_bytes_s = %llu\n", __func__, H5MM_total_alloc_bytes_s); HDfprintf(stderr, "%s: H5MM_peak_alloc_bytes_s = %zu\n", __func__, H5MM_peak_alloc_bytes_s); HDfprintf(stderr, "%s: H5MM_max_block_size_s = %zu\n", __func__, H5MM_max_block_size_s); HDfprintf(stderr, "%s: H5MM_total_alloc_blocks_count_s = %zu\n", __func__, H5MM_total_alloc_blocks_count_s); HDfprintf(stderr, "%s: H5MM_peak_alloc_blocks_count_s = %zu\n", __func__, H5MM_peak_alloc_blocks_count_s); #endif /* H5MM_PRINT_MEMORY_STATS */ } #endif /* H5_MEMORY_ALLOC_SANITY_CHECK */ /*------------------------------------------------------------------------- * Function: H5MM_malloc * * Purpose: Similar to the C89 version of malloc(). * * On size of 0, we return a NULL pointer instead of the * standard-allowed 'special' pointer since that's more * difficult to check as a return value. This is still * considered an error condition since allocations of zero * bytes usually indicate problems. * * Return: Success: Pointer to new memory * Failure: NULL * * Programmer: Quincey Koziol * Nov 8 2003 * *------------------------------------------------------------------------- */ void * H5MM_malloc(size_t size) { void *ret_value = NULL; /* Use FUNC_ENTER_NOAPI_NOINIT_NOERR here to avoid performance issues */ FUNC_ENTER_NOAPI_NOINIT_NOERR #if defined H5_MEMORY_ALLOC_SANITY_CHECK /* Initialize block list head singleton */ if (!H5MM_init_s) { H5MM_memcpy(H5MM_block_head_s.sig, H5MM_block_signature_s, H5MM_SIG_SIZE); H5MM_block_head_s.next = &H5MM_block_head_s; H5MM_block_head_s.prev = &H5MM_block_head_s; H5MM_block_head_s.u.info.size = SIZET_MAX; H5MM_block_head_s.u.info.in_use = TRUE; H5MM_init_s = TRUE; } /* end if */ #endif /* H5_MEMORY_ALLOC_SANITY_CHECK */ if (size) { #if defined H5_MEMORY_ALLOC_SANITY_CHECK H5MM_block_t *block; size_t alloc_size = sizeof(H5MM_block_t) + size + H5MM_HEAD_GUARD_SIZE + H5MM_TAIL_GUARD_SIZE; if (NULL != (block = (H5MM_block_t *)HDmalloc(alloc_size))) { /* Set up block */ H5MM_memcpy(block->sig, H5MM_block_signature_s, H5MM_SIG_SIZE); block->next = H5MM_block_head_s.next; H5MM_block_head_s.next = block; block->next->prev = block; block->prev = &H5MM_block_head_s; block->u.info.size = size; block->u.info.in_use = TRUE; H5MM_memcpy(block->b, H5MM_block_head_guard_s, H5MM_HEAD_GUARD_SIZE); H5MM_memcpy(block->b + H5MM_HEAD_GUARD_SIZE + size, H5MM_block_tail_guard_s, H5MM_TAIL_GUARD_SIZE); /* Update statistics */ H5MM_total_alloc_bytes_s += size; H5MM_curr_alloc_bytes_s += size; if (H5MM_curr_alloc_bytes_s > H5MM_peak_alloc_bytes_s) H5MM_peak_alloc_bytes_s = H5MM_curr_alloc_bytes_s; if (size > H5MM_max_block_size_s) H5MM_max_block_size_s = size; H5MM_total_alloc_blocks_count_s++; H5MM_curr_alloc_blocks_count_s++; if (H5MM_curr_alloc_blocks_count_s > H5MM_peak_alloc_blocks_count_s) H5MM_peak_alloc_blocks_count_s = H5MM_curr_alloc_blocks_count_s; /* Set buffer to return */ ret_value = block->b + H5MM_HEAD_GUARD_SIZE; } /* end if */ else ret_value = NULL; #else /* H5_MEMORY_ALLOC_SANITY_CHECK */ ret_value = HDmalloc(size); #endif /* H5_MEMORY_ALLOC_SANITY_CHECK */ } /* end if */ else ret_value = NULL; FUNC_LEAVE_NOAPI(ret_value) } /* end H5MM_malloc() */ /*------------------------------------------------------------------------- * Function: H5MM_calloc * * Purpose: Similar to the C89 version of calloc(), except this * routine just takes a 'size' parameter. * * On size of 0, we return a NULL pointer instead of the * standard-allowed 'special' pointer since that's more * difficult to check as a return value. This is still * considered an error condition since allocations of zero * bytes usually indicate problems. * * * Return: Success: Pointer to new memory * Failure: NULL * * Programmer: Quincey Koziol * Nov 8 2003 * *------------------------------------------------------------------------- */ void * H5MM_calloc(size_t size) { void *ret_value = NULL; /* Use FUNC_ENTER_NOAPI_NOINIT_NOERR here to avoid performance issues */ FUNC_ENTER_NOAPI_NOINIT_NOERR if (size) { #if defined H5_MEMORY_ALLOC_SANITY_CHECK if (NULL != (ret_value = H5MM_malloc(size))) HDmemset(ret_value, 0, size); #else /* H5_MEMORY_ALLOC_SANITY_CHECK */ ret_value = HDcalloc((size_t)1, size); #endif /* H5_MEMORY_ALLOC_SANITY_CHECK */ } /* end if */ else ret_value = NULL; FUNC_LEAVE_NOAPI(ret_value) } /* end H5MM_calloc() */ /*------------------------------------------------------------------------- * Function: H5MM_realloc * * Purpose: Similar semantics as C89's realloc(). Specifically, the * following calls are equivalent: * * H5MM_realloc(NULL, size) <==> H5MM_malloc(size) * H5MM_realloc(ptr, 0) <==> H5MM_xfree(ptr) * H5MM_realloc(NULL, 0) <==> NULL * * Note that the (NULL, 0) combination is undefined behavior * in the C standard. * * Return: Success: Ptr to new memory if size > 0 * NULL if size is zero * Failure: NULL (input buffer is unchanged on failure) * * Programmer: Robb Matzke * Jul 10 1997 * *------------------------------------------------------------------------- */ void * H5MM_realloc(void *mem, size_t size) { void *ret_value = NULL; /* Use FUNC_ENTER_NOAPI_NOINIT_NOERR here to avoid performance issues */ FUNC_ENTER_NOAPI_NOINIT_NOERR if (NULL == mem && 0 == size) /* Not defined in the standard, return NULL */ ret_value = NULL; else { #if defined H5_MEMORY_ALLOC_SANITY_CHECK if (size > 0) { if (mem) { if (H5MM__is_our_block(mem)) { H5MM_block_t *block = H5MM_BLOCK_FROM_BUF(mem); size_t old_size = block->u.info.size; H5MM__sanity_check(mem); ret_value = H5MM_malloc(size); H5MM_memcpy(ret_value, mem, MIN(size, old_size)); H5MM_xfree(mem); } /* end if */ else ret_value = HDrealloc(mem, size); } else ret_value = H5MM_malloc(size); } else ret_value = H5MM_xfree(mem); #else /* H5_MEMORY_ALLOC_SANITY_CHECK */ ret_value = HDrealloc(mem, size); /* Some platforms do not return NULL if size is zero. */ if (0 == size) ret_value = NULL; #endif /* H5_MEMORY_ALLOC_SANITY_CHECK */ } /* end else */ FUNC_LEAVE_NOAPI(ret_value) } /* end H5MM_realloc() */ /*------------------------------------------------------------------------- * Function: H5MM_xstrdup * * Purpose: Duplicates a string, including memory allocation. * NULL is an acceptable value for the input string. * * Return: Success: Pointer to a new string (NULL if s is NULL). * Failure: NULL * * Programmer: Robb Matzke * Jul 10 1997 *------------------------------------------------------------------------- */ char * H5MM_xstrdup(const char *s) { char *ret_value = NULL; FUNC_ENTER_NOAPI(NULL) #if defined H5_MEMORY_ALLOC_SANITY_CHECK if (s) { if (NULL == (ret_value = (char *)H5MM_malloc(HDstrlen(s) + 1))) HGOTO_ERROR(H5E_RESOURCE, H5E_NOSPACE, NULL, "memory allocation failed") HDstrcpy(ret_value, s); } #else if (s) if (NULL == (ret_value = HDstrdup(s))) HGOTO_ERROR(H5E_RESOURCE, H5E_NOSPACE, NULL, "string duplication failed") #endif done: FUNC_LEAVE_NOAPI(ret_value) } /* end H5MM_xstrdup() */ /*------------------------------------------------------------------------- * Function: H5MM_strdup * * Purpose: Duplicates a string, including memory allocation. * NULL is NOT an acceptable value for the input string. * * If the string to be duplicated is the NULL pointer, then * an error will be raised. * * Return: Success: Pointer to a new string * Failure: NULL * * Programmer: Robb Matzke * Jul 10 1997 *------------------------------------------------------------------------- */ char * H5MM_strdup(const char *s) { char *ret_value = NULL; FUNC_ENTER_NOAPI(NULL) if (!s) HGOTO_ERROR(H5E_ARGS, H5E_BADVALUE, NULL, "NULL string not allowed") #if defined H5_MEMORY_ALLOC_SANITY_CHECK if (NULL == (ret_value = (char *)H5MM_malloc(HDstrlen(s) + 1))) HGOTO_ERROR(H5E_RESOURCE, H5E_NOSPACE, NULL, "memory allocation failed") HDstrcpy(ret_value, s); #else if (NULL == (ret_value = HDstrdup(s))) HGOTO_ERROR(H5E_RESOURCE, H5E_NOSPACE, NULL, "string duplication failed") #endif done: FUNC_LEAVE_NOAPI(ret_value) } /* end H5MM_strdup() */ /*------------------------------------------------------------------------- * Function: H5MM_xfree * * Purpose: Just like free(3) except null pointers are allowed as * arguments, and the return value (always NULL) can be * assigned to the pointer whose memory was just freed: * * thing = H5MM_xfree (thing); * * Return: Success: NULL * Failure: never fails * * Programmer: Robb Matzke * Jul 10 1997 * *------------------------------------------------------------------------- */ void * H5MM_xfree(void *mem) { /* Use FUNC_ENTER_NOAPI_NOINIT_NOERR here to avoid performance issues */ FUNC_ENTER_NOAPI_NOINIT_NOERR if (mem) { #if defined H5_MEMORY_ALLOC_SANITY_CHECK if (H5MM__is_our_block(mem)) { H5MM_block_t *block = H5MM_BLOCK_FROM_BUF(mem); /* Run sanity checks on this block and its neighbors */ H5MM__sanity_check(mem); H5MM__sanity_check_block(block->next); H5MM__sanity_check_block(block->prev); /* Update statistics */ H5MM_curr_alloc_bytes_s -= block->u.info.size; H5MM_curr_alloc_blocks_count_s--; /* Reset block info */ HDmemset(block->sig, 0, H5MM_SIG_SIZE); block->next->prev = block->prev; block->prev->next = block->next; block->next = NULL; block->prev = NULL; block->u.info.in_use = FALSE; /* Free the block (finally!) */ HDfree(block); } else HDfree(mem); #else /* H5_MEMORY_ALLOC_SANITY_CHECK */ HDfree(mem); #endif /* H5_MEMORY_ALLOC_SANITY_CHECK */ } /* end if */ FUNC_LEAVE_NOAPI(NULL) } /* end H5MM_xfree() */ /*------------------------------------------------------------------------- * Function: H5MM_xfree_const * * Purpose: H5MM_xfree() wrapper that handles const pointers without * warnings. Used for freeing buffers that should be regarded * as const in use but need to be freed when no longer needed. * * Return: Success: NULL * Failure: never fails * *------------------------------------------------------------------------- */ void * H5MM_xfree_const(const void *mem) { /* Use FUNC_ENTER_NOAPI_NOINIT_NOERR here to avoid performance issues */ FUNC_ENTER_NOAPI_NOINIT_NOERR /* Cast through uintptr_t to de-const memory */ H5MM_xfree((void *)(uintptr_t)mem); FUNC_LEAVE_NOAPI(NULL) } /* end H5MM_xfree_const() */ /*------------------------------------------------------------------------- * Function: H5MM_memcpy * * Purpose: Like memcpy(3) but with sanity checks on the parameters, * particularly buffer overlap. * * Return: Success: pointer to dest * Failure: NULL * * Programmer: Dana Robinson * Spring 2019 * *------------------------------------------------------------------------- */ void * H5MM_memcpy(void *dest, const void *src, size_t n) { void *ret = NULL; /* Use FUNC_ENTER_NOAPI_NOINIT_NOERR here to avoid performance issues */ FUNC_ENTER_NOAPI_NOINIT_NOERR HDassert(dest); HDassert(src); /* Check for buffer overlap */ HDassert((char *)dest >= (const char *)src + n || (const char *)src >= (char *)dest + n); /* Copy */ ret = HDmemcpy(dest, src, n); FUNC_LEAVE_NOAPI(ret) } /* end H5MM_memcpy() */ /*------------------------------------------------------------------------- * Function: H5MM_get_alloc_stats * * Purpose: Gets the memory allocation statistics for the library, if the * H5_MEMORY_ALLOC_SANITY_CHECK macro is defined. If the macro is not * defined, zeros are returned. These statistics are global for the * entire library. * * Parameters: * H5_alloc_stats_t *stats; OUT: Memory allocation statistics * * Return: Success: non-negative * Failure: negative * * Programmer: Quincey Koziol * Saturday, March 7, 2020 * *------------------------------------------------------------------------- */ herr_t H5MM_get_alloc_stats(H5_alloc_stats_t *stats) { FUNC_ENTER_NOAPI_NOERR #if defined H5_MEMORY_ALLOC_SANITY_CHECK if (stats) { stats->total_alloc_bytes = H5MM_total_alloc_bytes_s; stats->curr_alloc_bytes = H5MM_curr_alloc_bytes_s; stats->peak_alloc_bytes = H5MM_peak_alloc_bytes_s; stats->max_block_size = H5MM_max_block_size_s; stats->total_alloc_blocks_count = H5MM_total_alloc_blocks_count_s; stats->curr_alloc_blocks_count = H5MM_curr_alloc_blocks_count_s; stats->peak_alloc_blocks_count = H5MM_peak_alloc_blocks_count_s; } /* end if */ #else /* H5_MEMORY_ALLOC_SANITY_CHECK */ if (stats) HDmemset(stats, 0, sizeof(H5_alloc_stats_t)); #endif /* H5_MEMORY_ALLOC_SANITY_CHECK */ FUNC_LEAVE_NOAPI(SUCCEED) } /* end H5MM_get_alloc_stats() */