From d5efa78a12dbceec1a884248a870745a293c7a20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Tue, 13 Dec 2022 19:27:34 +0100 Subject: [PATCH] garray: Add g_ptr_array_new_take_null_terminated() Similar to g_ptr_array_new_take() but it also computes the length of a zero-terminated array. --- docs/reference/glib/glib-sections.txt.in | 1 + glib/garray.c | 46 ++++++ glib/garray.h | 3 + glib/tests/array-test.c | 172 +++++++++++++++++++++++ 4 files changed, 222 insertions(+) diff --git a/docs/reference/glib/glib-sections.txt.in b/docs/reference/glib/glib-sections.txt.in index 93931e7e0..ff2e12ea9 100644 --- a/docs/reference/glib/glib-sections.txt.in +++ b/docs/reference/glib/glib-sections.txt.in @@ -2716,6 +2716,7 @@ g_ptr_array_copy g_ptr_array_new_full g_ptr_array_new_null_terminated g_ptr_array_new_take +g_ptr_array_new_take_null_terminated g_ptr_array_set_free_func g_ptr_array_is_null_terminated g_ptr_array_ref diff --git a/glib/garray.c b/glib/garray.c index 57c621e53..1adc0aa70 100644 --- a/glib/garray.c +++ b/glib/garray.c @@ -1197,6 +1197,52 @@ g_ptr_array_new_take (gpointer *data, return array; } +/** + * g_ptr_array_new_take_null_terminated: (skip) + * @data: (array zero-terminated=1) (transfer full) (nullable): an array + * of pointers, %NULL terminated, or %NULL for an empty array + * @element_free_func: (nullable): a function to free elements on @array + * destruction or %NULL + * + * Creates a new #GPtrArray with @data as pointers, computing the length of it + * and setting the reference count to 1. + * + * This avoids having to copy such data manually. + * + * The length is calculated by iterating through @data until the first %NULL + * element is found. + * + * It also sets @element_free_func for freeing each element when the array is + * destroyed either via g_ptr_array_unref(), when g_ptr_array_free() is called + * with @free_segment set to %TRUE or when removing elements. + * + * Do not use it if the @data length is greater than %G_MAXUINT. #GPtrArray + * stores the length of its data in #guint, which may be shorter than + * #gsize. + * + * Returns: (transfer full): A new #GPtrArray + * + * Since: 2.76 + */ +GPtrArray * +g_ptr_array_new_take_null_terminated (gpointer *data, + GDestroyNotify element_free_func) +{ + GPtrArray *array; + gsize len = 0; + + if (data != NULL) + { + for (gsize i = 0; data[i] != NULL; ++i) + len += 1; + } + + array = g_ptr_array_new_take (g_steal_pointer (&data), len, element_free_func); + ((GRealPtrArray *)array)->null_terminated = TRUE; + + return array; +} + /** * g_ptr_array_steal: * @array: a #GPtrArray. diff --git a/glib/garray.h b/glib/garray.h index 97a2b0575..dd0af1798 100644 --- a/glib/garray.h +++ b/glib/garray.h @@ -162,6 +162,9 @@ GLIB_AVAILABLE_IN_2_74 GPtrArray* g_ptr_array_new_null_terminated (guint reserved_size, GDestroyNotify element_free_func, gboolean null_terminated); +GLIB_AVAILABLE_IN_2_76 +GPtrArray* g_ptr_array_new_take_null_terminated (gpointer *data, + GDestroyNotify element_free_func); GLIB_AVAILABLE_IN_ALL gpointer* g_ptr_array_free (GPtrArray *array, gboolean free_seg); diff --git a/glib/tests/array-test.c b/glib/tests/array-test.c index 944f5ad84..75c846169 100644 --- a/glib/tests/array-test.c +++ b/glib/tests/array-test.c @@ -1154,6 +1154,174 @@ pointer_array_new_take_with_free_func (void) g_free (old_pdata_copy); } +static void +pointer_array_new_take_null_terminated (void) +{ + const size_t array_size = 10000; + GPtrArray *gparray; + gpointer *pdata; + gpointer *old_pdata_copy; + gsize len; + + gparray = g_ptr_array_new_null_terminated (array_size, NULL, TRUE); + g_assert_true (g_ptr_array_is_null_terminated (gparray)); + + for (size_t i = 0; i < array_size; i++) + g_ptr_array_add (gparray, GUINT_TO_POINTER (i + 1)); + + assert_ptr_array_null_terminated (gparray, TRUE); + pdata = g_ptr_array_steal (gparray, &len); + g_assert_cmpuint (array_size, ==, len); + g_assert_nonnull (pdata); + g_clear_pointer (&gparray, g_ptr_array_unref); + + old_pdata_copy = g_memdup2 (pdata, len * sizeof (gpointer)); + gparray = g_ptr_array_new_take_null_terminated (g_steal_pointer (&pdata), NULL); + g_assert_true (g_ptr_array_is_null_terminated (gparray)); + assert_ptr_array_null_terminated (gparray, TRUE); + g_assert_cmpuint (gparray->len, ==, array_size); + + g_assert_cmpuint (GPOINTER_TO_UINT (g_ptr_array_index (gparray, 0)), ==, 1); + g_assert_cmpuint (GPOINTER_TO_UINT (g_ptr_array_index (gparray, 10)), ==, 11); + + g_assert_cmpmem (old_pdata_copy, array_size * sizeof (gpointer), + gparray->pdata, array_size * sizeof (gpointer)); + + g_ptr_array_add (gparray, GUINT_TO_POINTER (55)); + assert_ptr_array_null_terminated (gparray, TRUE); + + g_ptr_array_insert (gparray, 0, GUINT_TO_POINTER (33)); + assert_ptr_array_null_terminated (gparray, TRUE); + + g_assert_cmpuint (gparray->len, ==, array_size + 2); + g_assert_cmpuint (GPOINTER_TO_UINT (g_ptr_array_index (gparray, 0)), ==, 33); + g_assert_cmpuint ( + GPOINTER_TO_UINT (g_ptr_array_index (gparray, gparray->len - 1)), ==, 55); + + g_ptr_array_remove_index (gparray, 0); + g_assert_cmpuint (gparray->len, ==, array_size + 1); + assert_ptr_array_null_terminated (gparray, TRUE); + + g_ptr_array_remove_index (gparray, gparray->len - 1); + g_assert_cmpuint (gparray->len, ==, array_size); + assert_ptr_array_null_terminated (gparray, TRUE); + + g_assert_cmpmem (old_pdata_copy, array_size * sizeof (gpointer), + gparray->pdata, array_size * sizeof (gpointer)); + + g_ptr_array_unref (gparray); + g_free (old_pdata_copy); +} + +static void +pointer_array_new_take_null_terminated_empty (void) +{ + GPtrArray *gparray; + const gpointer *data = (gpointer []) { NULL }; + + gparray = g_ptr_array_new_take_null_terminated ( + g_memdup2 (data, sizeof (gpointer)), NULL); + g_assert_true (g_ptr_array_is_null_terminated (gparray)); + assert_ptr_array_null_terminated (gparray, TRUE); + g_assert_cmpuint (gparray->len, ==, 0); + + g_clear_pointer (&gparray, g_ptr_array_unref); + + gparray = g_ptr_array_new_take_null_terminated (NULL, NULL); + g_assert_true (g_ptr_array_is_null_terminated (gparray)); + assert_ptr_array_null_terminated (gparray, TRUE); + g_assert_cmpuint (gparray->len, ==, 0); + + g_clear_pointer (&gparray, g_ptr_array_unref); +} + +static void +pointer_array_new_take_null_terminated_with_free_func (void) +{ + const size_t array_size = 10000; + GPtrArray *gparray; + gpointer *pdata; + gpointer *old_pdata_copy; + gsize len; + + gparray = g_ptr_array_new_null_terminated (array_size, g_free, TRUE); + g_assert_true (g_ptr_array_is_null_terminated (gparray)); + + for (size_t i = 0; i < array_size; i++) + g_ptr_array_add (gparray, g_strdup_printf ("%" G_GSIZE_FORMAT, i)); + + assert_ptr_array_null_terminated (gparray, TRUE); + + pdata = g_ptr_array_steal (gparray, &len); + g_assert_cmpuint (array_size, ==, len); + g_assert_nonnull (pdata); + g_clear_pointer (&gparray, g_ptr_array_unref); + + old_pdata_copy = g_memdup2 (pdata, len * sizeof (gpointer)); + gparray = g_ptr_array_new_take_null_terminated (g_steal_pointer (&pdata), g_free); + g_assert_true (g_ptr_array_is_null_terminated (gparray)); + assert_ptr_array_null_terminated (gparray, TRUE); + g_assert_cmpuint (gparray->len, ==, array_size); + + g_assert_cmpstr ((const char *) g_ptr_array_index (gparray, 0), ==, "0"); + g_assert_cmpstr ((const char *) g_ptr_array_index (gparray, 101), ==, "101"); + + g_assert_cmpmem (old_pdata_copy, array_size * sizeof (gpointer), + gparray->pdata, array_size * sizeof (gpointer)); + + g_ptr_array_add (gparray, g_strdup_printf ("%d", 55)); + assert_ptr_array_null_terminated (gparray, TRUE); + + g_ptr_array_insert (gparray, 0, g_strdup_printf ("%d", 33)); + assert_ptr_array_null_terminated (gparray, TRUE); + + g_assert_cmpuint (gparray->len, ==, array_size + 2); + g_assert_cmpstr ((const char *) g_ptr_array_index (gparray, 0), ==, "33"); + g_assert_cmpstr ( + (const char *) g_ptr_array_index (gparray, gparray->len - 1), ==, "55"); + + g_ptr_array_remove_index (gparray, 0); + g_assert_cmpuint (gparray->len, ==, array_size + 1); + assert_ptr_array_null_terminated (gparray, TRUE); + + g_ptr_array_remove_index (gparray, gparray->len - 1); + g_assert_cmpuint (gparray->len, ==, array_size); + assert_ptr_array_null_terminated (gparray, TRUE); + + g_assert_cmpmem (old_pdata_copy, array_size * sizeof (gpointer), + gparray->pdata, array_size * sizeof (gpointer)); + + g_ptr_array_unref (gparray); + g_free (old_pdata_copy); +} + +static void +pointer_array_new_take_null_terminated_from_gstrv (void) +{ + GPtrArray *gparray; + char *joined; + + gparray = g_ptr_array_new_take_null_terminated ( + (gpointer) g_strsplit ("A.dot.separated.string", ".", -1), g_free); + + g_assert_cmpstr ( + (const char *) g_ptr_array_index (gparray, 0), ==, "A"); + g_assert_cmpstr ( + (const char *) g_ptr_array_index (gparray, 1), ==, "dot"); + g_assert_cmpstr ( + (const char *) g_ptr_array_index (gparray, 2), ==, "separated"); + g_assert_cmpstr ( + (const char *) g_ptr_array_index (gparray, 3), ==, "string"); + + g_assert_null (g_ptr_array_index (gparray, 4)); + + joined = g_strjoinv (".", (char **) gparray->pdata); + g_assert_cmpstr (joined, ==, "A.dot.separated.string"); + + g_ptr_array_unref (gparray); + g_free (joined); +} + static void pointer_array_ref_count (gconstpointer test_data) { @@ -2331,6 +2499,10 @@ main (int argc, char *argv[]) g_test_add_func ("/pointerarray/new-take/empty", pointer_array_new_take_empty); g_test_add_func ("/pointerarray/new-take/overflow", pointer_array_new_take_overflow); g_test_add_func ("/pointerarray/new-take/with-free-func", pointer_array_new_take_with_free_func); + g_test_add_func ("/pointerarray/new-take-null-terminated", pointer_array_new_take_null_terminated); + g_test_add_func ("/pointerarray/new-take-null-terminated/empty", pointer_array_new_take_null_terminated_empty); + g_test_add_func ("/pointerarray/new-take-null-terminated/with-free-func", pointer_array_new_take_null_terminated_with_free_func); + g_test_add_func ("/pointerarray/new-take-null-terminated/from-gstrv", pointer_array_new_take_null_terminated_from_gstrv); g_test_add_data_func ("/pointerarray/ref-count/not-null-terminated", GINT_TO_POINTER (0), pointer_array_ref_count); g_test_add_data_func ("/pointerarray/ref-count/null-terminated", GINT_TO_POINTER (1), pointer_array_ref_count); g_test_add_func ("/pointerarray/free-func", pointer_array_free_func);