/* 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. * * Typically, reference counted strings can be used when parsing data from * files and storing them into data structures that are passed to various * callers: * * |[ * PersonDetails * * person_details_from_data (const char *data) * { * // Use g_autoptr() to simplify error cases * g_autoptr(GRefString) full_name = NULL; * g_autoptr(GRefString) address = NULL; * g_autoptr(GRefString) city = NULL; * g_autoptr(GRefString) state = NULL; * g_autoptr(GRefString) zip_code = NULL; * * // parse_person_details() is defined elsewhere; returns refcounted strings * if (!parse_person_details (data, &full_name, &address, &city, &state, &zip_code)) * return NULL; * * if (!validate_zip_code (zip_code)) * return NULL; * * // add_address_to_cache() and add_full_name_to_cache() are defined * // elsewhere; they add strings to various caches, using refcounted * // strings to avoid copying data over and over again * add_address_to_cache (address, city, state, zip_code); * add_full_name_to_cache (full_name); * * // person_details_new() is defined elsewhere; it takes a reference * // on each string * PersonDetails *res = person_details_new (full_name, * address, * city, * state, * zip_code); * * return res; * } * ]| * * In the example above, we have multiple functions taking the same strings * for different uses; with typical C strings, we'd have to copy the strings * every time the life time rules of the data differ from the life time of * the string parsed from the original buffer. With reference counted strings, * each caller can take a reference on the data, and keep it as long as it * needs to own the string. * * Reference counted strings can also be "interned" inside a global table * owned by GLib; while an interned string has at least a reference, creating * a new interned reference counted string with the same contents will return * a reference to the existing string instead of creating a new reference * counted string instance. Once the string loses its last reference, it will * be automatically removed from the global interned strings table. * * Since: 2.58 */ #include "config.h" #include "grefstring.h" #include "ghash.h" #include "gmessages.h" #include "grcbox.h" #include "gthread.h" #include /* A global table of refcounted strings; the hash table does not own * the strings, just a pointer to them. Strings are interned as long * as they are alive; once their reference count drops to zero, they * are removed from the table */ 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: (transfer full) (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, NULL); len = strlen (str); res = (char *) g_atomic_rc_box_dup (sizeof (char) * len + 1, str); return res; } /** * g_ref_string_new_len: * @str: (not nullable): a string * @len: length of @str to use, or -1 if @str is nul-terminated * * Creates a new reference counted string and copies the contents of @str * into it, up to @len bytes. * * Since this function does not stop at nul bytes, it is the caller's * responsibility to ensure that @str has at least @len addressable bytes. * * Returns: (transfer full) (not nullable): the newly created reference counted string * * Since: 2.58 */ char * g_ref_string_new_len (const char *str, gssize len) { char *res; g_return_val_if_fail (str != NULL, NULL); if (len < 0) return g_ref_string_new (str); /* allocate then copy as str[len] may not be readable */ res = (char *) g_atomic_rc_box_alloc ((gsize) len + 1); memcpy (res, str, len); res[len] = '\0'; return res; } /* interned_str_equal: variant of g_str_equal() that compares * pointers as well as contents; this avoids running strcmp() * on arbitrarily long strings, as it's more likely to have * g_ref_string_new_intern() being called on the same refcounted * string instance, than on a different string with the same * contents */ static gboolean interned_str_equal (gconstpointer v1, gconstpointer v2) { const char *str1 = v1; const char *str2 = v2; if (v1 == v2) return TRUE; return strcmp (str1, str2) == 0; } /** * 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: (transfer full) (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) { char *res; g_return_val_if_fail (str != NULL, NULL); G_LOCK (interned_ref_strings); if (G_UNLIKELY (interned_ref_strings == NULL)) interned_ref_strings = g_hash_table_new (g_str_hash, interned_str_equal); res = g_hash_table_lookup (interned_ref_strings, str); if (res != NULL) { /* We acquire the reference while holding the lock, to * avoid a potential race between releasing the lock on * the hash table and another thread releasing the reference * on the same string */ g_atomic_rc_box_acquire (res); G_UNLOCK (interned_ref_strings); return res; } res = g_ref_string_new (str); g_hash_table_add (interned_ref_strings, 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, NULL); return g_atomic_rc_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)) { 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); g_atomic_rc_box_release_full (str, remove_if_interned); } /** * g_ref_string_length: * @str: a reference counted string * * Retrieves the length of @str. * * Returns: the length of the given string, in bytes * * Since: 2.58 */ gsize g_ref_string_length (char *str) { g_return_val_if_fail (str != NULL, 0); return g_atomic_rc_box_get_size (str) - 1; }