diff --git a/docs/reference/glib/glib-docs.xml b/docs/reference/glib/glib-docs.xml index 9d253ee5f..e0e8ba233 100644 --- a/docs/reference/glib/glib-docs.xml +++ b/docs/reference/glib/glib-docs.xml @@ -122,6 +122,7 @@ + diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt index 2a4d1f281..dd851bc9e 100644 --- a/docs/reference/glib/glib-sections.txt +++ b/docs/reference/glib/glib-sections.txt @@ -3490,3 +3490,11 @@ g_arc_box_acquire g_arc_box_release g_arc_box_release_full + +
+refstring +g_ref_string_new +g_ref_string_new_intern +g_ref_string_acquire +g_ref_string_release +
diff --git a/glib/Makefile.am b/glib/Makefile.am index 352ecb0ec..c7adc1f0e 100644 --- a/glib/Makefile.am +++ b/glib/Makefile.am @@ -153,6 +153,7 @@ libglib_2_0_la_SOURCES = \ grcbox.c \ grcboxprivate.h \ grefcount.c \ + grefstring.c \ gregex.c \ gscanner.c \ gscripttable.h \ @@ -291,6 +292,7 @@ glibsubinclude_HEADERS = \ grand.h \ grcbox.h \ grefcount.h \ + grefstring.h \ gregex.h \ gscanner.h \ gsequence.h \ diff --git a/glib/glib-autocleanups.h b/glib/glib-autocleanups.h index 9f86bd99c..ce1690b78 100644 --- a/glib/glib-autocleanups.h +++ b/glib/glib-autocleanups.h @@ -87,3 +87,4 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(GVariantDict, g_variant_dict_unref) G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(GVariantDict, g_variant_dict_clear) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GVariantType, g_variant_type_free) G_DEFINE_AUTO_CLEANUP_FREE_FUNC(GStrv, g_strfreev, NULL) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (GRefString, g_ref_string_release) diff --git a/glib/glib.h b/glib/glib.h index 309366281..94a11fb62 100644 --- a/glib/glib.h +++ b/glib/glib.h @@ -71,6 +71,7 @@ #include #include #include +#include #include #include #include diff --git a/glib/grefstring.c b/glib/grefstring.c new file mode 100644 index 000000000..cc62c8011 --- /dev/null +++ b/glib/grefstring.c @@ -0,0 +1,175 @@ +/* grefstring.c: Reference counted strings + * + * Copyright 2018 Emmanuele Bassi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +/** + * SECTION:refstring + * @Title: Reference counted strings + * @Short_description: Strings with reference counted memory management + * + * Reference counted strings are normal C strings that have been augmented + * with a reference counter to manage their resources. You allocate a new + * reference counted string and acquire and release references as needed, + * instead of copying the string among callers; when the last reference on + * the string is released, the resources allocated for it are freed. + * + * Since: 2.58 + */ + +#include "config.h" + +#include "grefstring.h" + +#include "ghash.h" +#include "gmessages.h" +#include "grcbox.h" +#include "gthread.h" + +#include + +G_LOCK_DEFINE_STATIC (interned_ref_strings); +static GHashTable *interned_ref_strings; + +/** + * g_ref_string_new: + * @str: (not nullable): a NUL-terminated string + * + * Creates a new reference counted string and copies the contents of @str + * into it. + * + * Returns: (not nullable): the newly created reference counted string + * + * Since: 2.58 + */ +char * +g_ref_string_new (const char *str) +{ + char *res; + gsize len; + + g_return_val_if_fail (str != NULL && *str != '\0', NULL); + + len = strlen (str); + + res = (char *) g_arc_box_dup (sizeof (char) * len + 1, str); + res[len] = '\0'; + + return res; +} + +/** + * g_ref_string_new_intern: + * @str: (not nullable): a NUL-terminated string + * + * Creates a new reference counted string and copies the content of @str + * into it. + * + * If you call this function multiple times with the same @str, or with + * the same contents of @str, it will return a new reference, instead of + * creating a new string. + * + * Returns: (not nullable): the newly created reference counted string, or + * a new reference to an existing string + * + * Since: 2.58 + */ +char * +g_ref_string_new_intern (const char *str) +{ + gsize len; + char *res; + + g_return_val_if_fail (str != NULL && *str != '\0', NULL); + + G_LOCK (interned_ref_strings); + + if (G_UNLIKELY (interned_ref_strings == NULL)) + interned_ref_strings = g_hash_table_new_full (g_str_hash, g_str_equal, + (GDestroyNotify) g_arc_box_release, + NULL); + + res = g_hash_table_lookup (interned_ref_strings, str); + if (res != NULL) + { + G_UNLOCK (interned_ref_strings); + return g_arc_box_acquire (res); + } + + len = strlen (str); + res = (char *) g_arc_box_dup (sizeof (char) * len + 1, str); + res[len] = '\0'; + + g_hash_table_add (interned_ref_strings, g_arc_box_acquire (res)); + + G_UNLOCK (interned_ref_strings); + + return res; +} + +/** + * g_ref_string_acquire: + * @str: a reference counted string + * + * Acquires a reference on a string. + * + * Returns: the given string, with its reference count increased + * + * Since: 2.58 + */ +char * +g_ref_string_acquire (char *str) +{ + g_return_val_if_fail (str != NULL && *str != '\0', NULL); + + return g_arc_box_acquire (str); +} + +static void +remove_if_interned (gpointer data) +{ + char *str = data; + + G_LOCK (interned_ref_strings); + + if (G_LIKELY (interned_ref_strings != NULL)) + { + if (g_hash_table_contains (interned_ref_strings, str)) + g_hash_table_remove (interned_ref_strings, str); + + if (g_hash_table_size (interned_ref_strings) == 0) + g_clear_pointer (&interned_ref_strings, g_hash_table_destroy); + } + + G_UNLOCK (interned_ref_strings); +} + +/** + * g_ref_string_release: + * @str: a reference counted string + * + * Releases a reference on a string; if it was the last reference, the + * resources allocated by the string are freed as well. + * + * Since: 2.58 + */ +void +g_ref_string_release (char *str) +{ + g_return_if_fail (str != NULL && *str != '\0'); + + g_arc_box_release_full (str, remove_if_interned); +} diff --git a/glib/grefstring.h b/glib/grefstring.h new file mode 100644 index 000000000..f2156fef4 --- /dev/null +++ b/glib/grefstring.h @@ -0,0 +1,38 @@ +/* grefstring.h: Reference counted strings + * + * Copyright 2018 Emmanuele Bassi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#pragma once + +#include "gmem.h" +#include "gmacros.h" + +G_BEGIN_DECLS + +GLIB_AVAILABLE_IN_2_58 +char * g_ref_string_new (const char *str); +GLIB_AVAILABLE_IN_2_58 +char * g_ref_string_new_intern (const char *str); + +GLIB_AVAILABLE_IN_2_58 +char * g_ref_string_acquire (char *str); +GLIB_AVAILABLE_IN_2_58 +void g_ref_string_release (char *str); + +typedef char GRefString; + +G_END_DECLS diff --git a/glib/meson.build b/glib/meson.build index 1a3f44797..c05c69406 100644 --- a/glib/meson.build +++ b/glib/meson.build @@ -78,6 +78,7 @@ glib_sub_headers = files( 'grand.h', 'grcbox.h', 'grefcount.h', + 'grefstring.h', 'gregex.h', 'gscanner.h', 'gsequence.h', @@ -164,6 +165,7 @@ glib_sources = files( 'grand.c', 'grcbox.c', 'grefcount.c', + 'grefstring.c', 'gregex.c', 'gscanner.c', 'gsequence.c', diff --git a/glib/tests/Makefile.am b/glib/tests/Makefile.am index 0d524d57f..71ac15517 100644 --- a/glib/tests/Makefile.am +++ b/glib/tests/Makefile.am @@ -91,6 +91,7 @@ test_programs = \ rand \ rcbox \ rec-mutex \ + refstring \ regex \ rwlock \ scannerapi \ diff --git a/glib/tests/autoptr.c b/glib/tests/autoptr.c index c8c130d5c..5b3bce71c 100644 --- a/glib/tests/autoptr.c +++ b/glib/tests/autoptr.c @@ -422,6 +422,13 @@ test_strv (void) g_assert (val != NULL); } +static void +test_refstring (void) +{ + g_autoptr(GRefString) str = g_ref_string_new ("hello, world"); + g_assert_nonnull (str); +} + static void mark_freed (gpointer ptr) { @@ -539,6 +546,7 @@ main (int argc, gchar *argv[]) g_test_add_func ("/autoptr/g_variant_dict", test_g_variant_dict); g_test_add_func ("/autoptr/g_variant_type", test_g_variant_type); g_test_add_func ("/autoptr/strv", test_strv); + g_test_add_func ("/autoptr/refstring", test_refstring); g_test_add_func ("/autoptr/autolist", test_autolist); g_test_add_func ("/autoptr/autoslist", test_autoslist); diff --git a/glib/tests/meson.build b/glib/tests/meson.build index 3c20bad39..7c49d579c 100644 --- a/glib/tests/meson.build +++ b/glib/tests/meson.build @@ -50,6 +50,7 @@ glib_tests = [ 'rec-mutex', 'refcount', 'refcount-macro', + 'refstring', 'regex', 'rwlock', 'scannerapi', diff --git a/glib/tests/refstring.c b/glib/tests/refstring.c new file mode 100644 index 000000000..0181be945 --- /dev/null +++ b/glib/tests/refstring.c @@ -0,0 +1,70 @@ +/* refstring.c: Reference counted strings + * + * Copyright 2018 Emmanuele Bassi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include +#include + +/* test_refstring_base: Test the base API of GRefString */ +static void +test_refstring_base (void) +{ + char *s = g_ref_string_new ("hello, world"); + + g_test_message ("s = '%s' (%p)", s, s); + g_assert_cmpint (strcmp (s, "hello, world"), ==, 0); + g_assert_cmpint (strlen (s), ==, strlen ("hello, world")); + + g_ref_string_release (s); +} + +/* test_refstring_intern: Test the interning API of GRefString */ +static void +test_refstring_intern (void) +{ + char *s = g_ref_string_new_intern ("hello, world"); + char *p; + + g_test_message ("s = '%s' (%p)", s, s); + g_assert_cmpstr (s, ==, "hello, world"); + + p = g_ref_string_new_intern ("hello, world"); + g_test_message ("p = s = '%s' (%p)", p, p); + g_assert_true (s == p); + + g_ref_string_release (p); + + p = g_ref_string_new_intern ("goodbye, world"); + g_test_message ("p = '%s' (%p)", p, p); + g_assert_false (s == p); + + g_ref_string_release (p); + g_ref_string_release (s); +} + +int +main (int argc, + char *argv[]) +{ + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/refstring/base", test_refstring_base); + g_test_add_func ("/refstring/intern", test_refstring_intern); + + return g_test_run (); +} +