diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt index a85eed7eb..97dcf1f70 100644 --- a/docs/reference/glib/glib-sections.txt +++ b/docs/reference/glib/glib-sections.txt @@ -1393,6 +1393,11 @@ g_alloca0 g_newa g_newa0 + +g_aligned_alloc +g_aligned_alloc0 +g_aligned_free + g_memmove g_memdup diff --git a/glib/gmem.c b/glib/gmem.c index 605eac1ff..060e91af9 100644 --- a/glib/gmem.c +++ b/glib/gmem.c @@ -30,6 +30,25 @@ #include "gmem.h" +#if defined(HAVE_POSIX_MEMALIGN) && !defined(_XOPEN_SOURCE) +# define _XOPEN_SOURCE 600 +#endif + +#if defined(HAVE_MEMALIGN) || defined(HAVE__ALIGNED_MALLOC) +/* Required for _aligned_malloc() and _aligned_free() on Windows */ +#include +#endif + +#ifdef HAVE__ALIGNED_MALLOC +/* _aligned_malloc() takes parameters of aligned_malloc() in reverse order */ +# define aligned_alloc(alignment, size) _aligned_malloc (size, alignment) + +/* _aligned_malloc()'ed memory must be freed by _align_free() on MSVC */ +# define aligned_free(x) _aligned_free (x) +#else +# define aligned_free(x) free (x) +#endif + #include #include #include @@ -522,3 +541,141 @@ g_mem_profile (void) { g_warning (G_STRLOC ": memory profiling not supported"); } + +/** + * g_aligned_alloc: + * @n_blocks: the number of blocks to allocate + * @n_block_bytes: the size of each block in bytes + * @alignment: the alignment to be enforced, which must be a positive power of 2 + * and a multiple of `sizeof(void*)` + * + * This function is similar to g_malloc(), allocating (@n_blocks * @n_block_bytes) + * bytes, but care is taken to align the allocated memory to with the given + * alignment value. Additionally, it will detect possible overflow during + * multiplication. + * + * Aligned memory allocations returned by this function can only be + * freed using g_aligned_free(). + * + * Returns: (transfer full): the allocated memory + * + * Since: 2.72 + */ +gpointer +g_aligned_alloc (gsize n_blocks, + gsize n_block_bytes, + gsize alignment) +{ + gpointer res = NULL; + gsize real_size; + + if (G_UNLIKELY ((alignment == 0) || (alignment & (alignment - 1)) != 0)) + { + g_error ("%s: alignment %"G_GSIZE_FORMAT" must be a positive power of two", + G_STRLOC, alignment); + } + + if (G_UNLIKELY ((alignment % sizeof (void *)) != 0)) + { + g_error ("%s: alignment %"G_GSIZE_FORMAT" must be a multiple of %"G_GSIZE_FORMAT, + G_STRLOC, alignment, sizeof (void *)); + } + + if (SIZE_OVERFLOWS (n_blocks, n_block_bytes)) + { + g_error ("%s: overflow allocating %"G_GSIZE_FORMAT"*%"G_GSIZE_FORMAT" bytes", + G_STRLOC, n_blocks, n_block_bytes); + } + + real_size = n_blocks * n_block_bytes; + + if (G_UNLIKELY (real_size == 0)) + { + TRACE(GLIB_MEM_ALLOC((void*) NULL, (int) real_size, 0, 0)); + return NULL; + } + + /* We need to clear errno because posix_memalign() will use its return + * value in the same way memalign() and aligned_alloc() will set errno. + * Additionally, posix_memalign() will warn if its return value is left + * unassigned. + * + * We handle all possible return values (ENOMEM and EINVAL) with either + * precondition or postcondition checking. + */ + errno = 0; + +#if defined(HAVE_POSIX_MEMALIGN) + errno = posix_memalign (&res, alignment, real_size); +#elif defined(HAVE_ALIGNED_ALLOC) || defined(HAVE__ALIGNED_MALLOC) + /* real_size must be a multiple of alignment */ + if (real_size % alignment != 0) + { + gsize offset = real_size % alignment; + + if (G_MAXSIZE - real_size < (alignment - offset)) + { + g_error ("%s: overflow allocating %"G_GSIZE_FORMAT"+%"G_GSIZE_FORMAT" bytes", + G_STRLOC, real_size, (alignment - offset)); + } + + real_size += (alignment - offset); + } + + res = aligned_alloc (alignment, real_size); +#elif defined(HAVE_MEMALIGN) + res = memalign (alignment, real_size); +#else +# error "This platform does not have an aligned memory allocator." +#endif + + TRACE (GLIB_MEM_ALLOC((void*) res, (unsigned int) real_size, 0, 0)); + if (res) + return res; + + g_error ("%s: failed to allocate %"G_GSIZE_FORMAT" bytes", + G_STRLOC, real_size); + + return NULL; +} + +/** + * g_aligned_alloc0: + * @n_blocks: the number of blocks to allocate + * @n_block_bytes: the size of each block in bytes + * @alignment: the alignment to be enforced, which must be a positive power of 2 + * and a multiple of `sizeof(void*)` + * + * This function is similar to g_aligned_alloc(), but it will + * also clear the allocated memory before returning it. + * + * Returns: (transfer full): the allocated, cleared memory + * + * Since: 2.72 + */ +gpointer +g_aligned_alloc0 (gsize n_blocks, + gsize n_block_bytes, + gsize alignment) +{ + gpointer res = g_aligned_alloc (n_blocks, n_block_bytes, alignment); + + if (G_LIKELY (res != NULL)) + memset (res, 0, n_blocks * n_block_bytes); + + return res; +} + +/** + * g_aligned_free: + * @mem: (nullable): the memory to deallocate + * + * Frees the memory allocated by g_aligned_alloc(). + * + * Since: 2.72 + */ +void +g_aligned_free (gpointer mem) +{ + aligned_free (mem); +} diff --git a/glib/gmem.h b/glib/gmem.h index 47c4735ac..d29907a67 100644 --- a/glib/gmem.h +++ b/glib/gmem.h @@ -111,6 +111,17 @@ gpointer g_try_realloc_n (gpointer mem, gsize n_blocks, gsize n_block_bytes) G_GNUC_WARN_UNUSED_RESULT; +GLIB_AVAILABLE_IN_2_72 +gpointer g_aligned_alloc (gsize n_blocks, + gsize n_block_bytes, + gsize alignment) G_GNUC_WARN_UNUSED_RESULT G_GNUC_ALLOC_SIZE2(1,2); +GLIB_AVAILABLE_IN_2_72 +gpointer g_aligned_alloc0 (gsize n_blocks, + gsize n_block_bytes, + gsize alignment) G_GNUC_WARN_UNUSED_RESULT G_GNUC_ALLOC_SIZE2(1,2); +GLIB_AVAILABLE_IN_2_72 +void g_aligned_free (gpointer mem); + #if defined(glib_typeof) && GLIB_VERSION_MAX_ALLOWED >= GLIB_VERSION_2_58 #define g_clear_pointer(pp, destroy) \ G_STMT_START \ diff --git a/glib/tests/mem-overflow.c b/glib/tests/mem-overflow.c index 1654ab7fc..fd926854f 100644 --- a/glib/tests/mem-overflow.c +++ b/glib/tests/mem-overflow.c @@ -34,13 +34,14 @@ static gsize a = G_MAXSIZE / 10 + 10; static gsize b = 10; typedef char X[10]; -#define MEM_OVERFLOW_TEST(name, code) \ +#define MEM_OVERFLOW_TEST(name, code) MEM_OVERFLOW_TEST_FULL(name, code, g_free) +#define MEM_OVERFLOW_TEST_FULL(name, code, free_func) \ static void \ mem_overflow_ ## name (void) \ { \ gpointer p; \ code; \ - g_free (p); \ + free_func (p); \ exit (0); \ } @@ -68,6 +69,12 @@ MEM_OVERFLOW_TEST (new0_b, p = g_new0 (X, b)) MEM_OVERFLOW_TEST (renew_a, p = g_malloc (1); p = g_renew (X, p, a)) MEM_OVERFLOW_TEST (renew_b, p = g_malloc (1); p = g_renew (X, p, b)) +MEM_OVERFLOW_TEST_FULL (aligned_alloc_a, p = g_aligned_alloc (sizeof(X), a, 16), g_aligned_free) +MEM_OVERFLOW_TEST_FULL (aligned_alloc_b, p = g_aligned_alloc (sizeof(X), b, 16), g_aligned_free) + +MEM_OVERFLOW_TEST_FULL (aligned_alloc0_a, p = g_aligned_alloc0 (sizeof(X), a, 16), g_aligned_free) +MEM_OVERFLOW_TEST_FULL (aligned_alloc0_b, p = g_aligned_alloc0 (sizeof(X), b, 16), g_aligned_free) + static void mem_overflow_malloc_0 (void) { @@ -171,6 +178,12 @@ mem_overflow (void) CHECK_SUBPROCESS_PASS (malloc_0); CHECK_SUBPROCESS_PASS (realloc_0); + + CHECK_SUBPROCESS_FAIL (aligned_alloc_a); + CHECK_SUBPROCESS_PASS (aligned_alloc_b); + + CHECK_SUBPROCESS_FAIL (aligned_alloc0_a); + CHECK_SUBPROCESS_PASS (aligned_alloc0_b); } #ifdef __GNUC__ @@ -231,6 +244,10 @@ main (int argc, g_test_add_func ("/mem/overflow/subprocess/renew_b", mem_overflow_renew_b); g_test_add_func ("/mem/overflow/subprocess/malloc_0", mem_overflow_malloc_0); g_test_add_func ("/mem/overflow/subprocess/realloc_0", mem_overflow_realloc_0); + g_test_add_func ("/mem/overflow/subprocess/aligned_alloc_a", mem_overflow_aligned_alloc_a); + g_test_add_func ("/mem/overflow/subprocess/aligned_alloc_b", mem_overflow_aligned_alloc_b); + g_test_add_func ("/mem/overflow/subprocess/aligned_alloc0_a", mem_overflow_aligned_alloc0_a); + g_test_add_func ("/mem/overflow/subprocess/aligned_alloc0_b", mem_overflow_aligned_alloc0_b); #ifdef __GNUC__ g_test_add_func ("/mem/empty-alloc", empty_alloc); diff --git a/glib/tests/utils.c b/glib/tests/utils.c index 643978149..9dbdd2c8c 100644 --- a/glib/tests/utils.c +++ b/glib/tests/utils.c @@ -915,6 +915,100 @@ test_misc_mem (void) g_assert (a == NULL); } +static void +aligned_alloc_nz (void) +{ + gpointer a; + + /* Test an alignment that’s zero */ + a = g_aligned_alloc (16, sizeof(char), 0); + g_assert_null (a); + exit (0); +} + +static void +aligned_alloc_npot (void) +{ + gpointer a; + + /* Test an alignment that’s not a power of two */ + a = g_aligned_alloc (16, sizeof(char), 15); + g_assert_null (a); + exit (0); +} + +static void +aligned_alloc_nmov (void) +{ + gpointer a; + + /* Test an alignment that’s not a multiple of sizeof(void*) */ + a = g_aligned_alloc (16, sizeof(char), 4); + g_assert_null (a); + exit (0); +} + +static void +test_aligned_mem (void) +{ + gpointer a; + + g_test_summary ("Aligned memory allocator"); + + a = g_aligned_alloc (0, sizeof(int), 8); + g_assert_null (a); + + a = g_aligned_alloc0 (0, sizeof(int), 8); + g_assert_null (a); + + a = g_aligned_alloc (16, 0, 8); + g_assert_null (a); + +#define CHECK_SUBPROCESS_FAIL(name,msg) do { \ + { \ + g_test_message (msg); \ + g_test_trap_subprocess ("/utils/aligned-mem/subprocess/" #name, 0, 0); \ + g_test_trap_assert_failed (); \ + } \ + } while (0) + + CHECK_SUBPROCESS_FAIL (aligned_alloc_nz, "Alignment must not be zero"); + CHECK_SUBPROCESS_FAIL (aligned_alloc_npot, "Alignment must be a power of two"); + CHECK_SUBPROCESS_FAIL (aligned_alloc_nmov, "Alignment must be a multiple of sizeof(void*)"); +} + +static void +test_aligned_mem_alignment (void) +{ + gchar *p; + + g_test_summary ("Check that g_aligned_alloc() returns a correctly aligned pointer"); + + p = g_aligned_alloc (5, sizeof (*p), 256); + g_assert_nonnull (p); + g_assert_cmpuint (((guintptr) p) % 256, ==, 0); + + g_aligned_free (p); +} + +static void +test_aligned_mem_zeroed (void) +{ + gsize n_blocks = 10; + guint *p; + gsize i; + + g_test_summary ("Check that g_aligned_alloc0() zeroes out its allocation"); + + p = g_aligned_alloc0 (n_blocks, sizeof (*p), 16); + g_assert_nonnull (p); + + for (i = 0; i < n_blocks; i++) + g_assert_cmpuint (p[i], ==, 0); + + g_aligned_free (p); +} + static void test_nullify (void) { @@ -1084,6 +1178,12 @@ main (int argc, g_test_add_func ("/utils/take-pointer", test_take_pointer); g_test_add_func ("/utils/clear-source", test_clear_source); g_test_add_func ("/utils/misc-mem", test_misc_mem); + g_test_add_func ("/utils/aligned-mem", test_aligned_mem); + g_test_add_func ("/utils/aligned-mem/subprocess/aligned_alloc_nz", aligned_alloc_nz); + g_test_add_func ("/utils/aligned-mem/subprocess/aligned_alloc_npot", aligned_alloc_npot); + g_test_add_func ("/utils/aligned-mem/subprocess/aligned_alloc_nmov", aligned_alloc_nmov); + g_test_add_func ("/utils/aligned-mem/alignment", test_aligned_mem_alignment); + g_test_add_func ("/utils/aligned-mem/zeroed", test_aligned_mem_zeroed); g_test_add_func ("/utils/nullify", test_nullify); g_test_add_func ("/utils/atexit", test_atexit); g_test_add_func ("/utils/check-setuid", test_check_setuid); diff --git a/meson.build b/meson.build index a12be259c..7a6a344f0 100644 --- a/meson.build +++ b/meson.build @@ -653,8 +653,19 @@ if host_system == 'android' endif -# Check that posix_memalign() is usable; must use header -if host_system != 'windows' and cc.has_function('posix_memalign', prefix : '#include ') +if cc.has_function('memalign', prefix: '#include \n#include ') + glib_conf.set('HAVE_MEMALIGN', 1) +endif + +if cc.has_function('_aligned_malloc', prefix: '#include ') + glib_conf.set('HAVE__ALIGNED_MALLOC', 1) +endif + +if host_system != 'windows' and cc.has_function('aligned_alloc', prefix: '#include ') + glib_conf.set('HAVE_ALIGNED_ALLOC', 1) +endif + +if host_system != 'windows' and cc.has_function('posix_memalign', prefix: '#include ') glib_conf.set('HAVE_POSIX_MEMALIGN', 1) endif