GString: Support on-stack use

Move the preallocation into the GString struct,
and add g_string_init and g_string_clear to
enable on-stack use of GString.
diff --git a/docs/reference/glib/glib-sections.txt.in b/docs/reference/glib/glib-sections.txt.in
index 6795a14..4fdfa57 100644
--- a/docs/reference/glib/glib-sections.txt.in
+++ b/docs/reference/glib/glib-sections.txt.in
@@ -2652,6 +2652,8 @@
 g_string_set_size
 g_string_free
 g_string_free_to_bytes
+g_string_init
+g_string_free
 
 <SUBSECTION>
 g_string_up
diff --git a/glib/gstring.c b/glib/gstring.c
index 8967ce1..86869d9 100644
--- a/glib/gstring.c
+++ b/glib/gstring.c
@@ -59,6 +59,10 @@
  * characters in the data.  Conceptually then, #GString is like a
  * #GByteArray with the addition of many convenience methods for text,
  * and a guaranteed nul terminator.
+ *
+ * GString can be used as a heap-allocated object, using g_string_new()
+ * and g_string_free(), or as a stack-allocated struct, using g_string_init()
+ * and g_string_clear().
  */
 
 /**
@@ -70,6 +74,7 @@
  *   terminating nul byte.
  * @allocated_len: the number of bytes that can be stored in the
  *   string before it needs to be reallocated. May be larger than @len.
+ * @buf: preallocated memory for @str
  *
  * The GString struct contains the public fields of a GString.
  */
@@ -82,6 +87,9 @@
   if G_UNLIKELY ((G_MAXSIZE - string->len - 1) < len)
     g_error ("adding %" G_GSIZE_FORMAT " to string would overflow", len);
 
+  if (string->str == NULL)
+    g_string_init (string);
+
   string->allocated_len = g_nearest_pow (string->len + len + 1);
   /* If the new size is bigger than G_MAXSIZE / 2, only allocate enough
    * memory for this string and don't over-allocate.
@@ -89,7 +97,14 @@
   if (string->allocated_len == 0)
     string->allocated_len = string->len + len + 1;
 
-  string->str = g_realloc (string->str, string->allocated_len);
+  if (string->str != string->buf)
+    string->str = g_realloc (string->str, string->allocated_len);
+  else
+    {
+      string->str = g_malloc (string->allocated_len);
+      memcpy (string->str, string->buf, string->len);
+      string->str[string->len] = 0;
+    }
 }
 
 static inline void
@@ -101,6 +116,23 @@
 }
 
 /**
+ * g_string_init:
+ * @string: an uninitialized #GString
+ *
+ * Initializes @string.
+ *
+ * This function should be used to initialize a stack-allocated
+ * GString struct.
+ *
+ * Since: 2.76
+ */
+void
+(g_string_init) (GString *string)
+{
+  g_string_init_inline (string);
+}
+
+/**
  * g_string_sized_new: (constructor)
  * @dfl_size: the default size of the space allocated to hold the string
  *
@@ -116,12 +148,8 @@
 {
   GString *string = g_slice_new (GString);
 
-  string->allocated_len = 0;
-  string->len   = 0;
-  string->str   = NULL;
-
-  g_string_expand (string, MAX (dfl_size, 64));
-  string->str[0] = 0;
+  g_string_init (string);
+  g_string_maybe_expand (string, dfl_size);
 
   return string;
 }
@@ -190,6 +218,29 @@
 }
 
 /**
+ * g_string_clear:
+ * @string: (transfer full): a #GString
+ * @free_segment: if %TRUE, the actual character data is freed as well
+ *
+ * Clears a stack-allocated GString struct.
+ *
+ * If @free_segment is %TRUE it also frees the character data.
+ * If it's %FALSE, the caller gains ownership of the buffer and
+ * must free it after use with g_free().
+ *
+ * Returns: (nullable): the character data of @string
+ *    (i.e. %NULL if @free_segment is %TRUE)
+ *
+ * Since: 2.76
+ */
+char *
+(g_string_clear) (GString  *string,
+                  gboolean  free_segment)
+{
+  return g_string_clear_inline (string, free_segment);
+}
+
+/**
  * g_string_free:
  * @string: (transfer full): a #GString
  * @free_segment: if %TRUE, the actual character data is freed as well
@@ -210,13 +261,7 @@
 
   g_return_val_if_fail (string != NULL, NULL);
 
-  if (free_segment)
-    {
-      g_free (string->str);
-      segment = NULL;
-    }
-  else
-    segment = string->str;
+  segment = g_string_clear (string, free_segment);
 
   g_slice_free (GString, string);
 
diff --git a/glib/gstring.h b/glib/gstring.h
index 7b84553..f615bd3 100644
--- a/glib/gstring.h
+++ b/glib/gstring.h
@@ -35,10 +35,13 @@
 #include <glib/gunicode.h>
 #include <glib/gbytes.h>
 #include <glib/gutils.h>  /* for G_CAN_INLINE */
+#include <glib/gstrfuncs.h>
 #include <string.h>
 
 G_BEGIN_DECLS
 
+#define G_STRING_PREALLOC 64
+
 typedef struct _GString         GString;
 
 struct _GString
@@ -46,6 +49,7 @@
   gchar  *str;
   gsize len;
   gsize allocated_len;
+  char buf[G_STRING_PREALLOC];
 };
 
 GLIB_AVAILABLE_IN_ALL
@@ -163,10 +167,55 @@
                                           const gchar     *reserved_chars_allowed,
                                           gboolean         allow_utf8);
 
+GLIB_AVAILABLE_IN_2_76
+void         g_string_init            (GString            *string);
+
+GLIB_AVAILABLE_IN_2_76
+char *       g_string_clear           (GString            *string,
+                                       gboolean            free_segment);
+
 #ifndef __GTK_DOC_IGNORE__
 
 #ifdef G_CAN_INLINE
 G_ALWAYS_INLINE
+static inline void
+g_string_init_inline (GString *string)
+{
+  string->str = string->buf;
+  string->len = 0;
+  string->allocated_len = G_STRING_PREALLOC;
+  string->str[0] = 0;
+}
+#define g_string_init(gstr) g_string_init_inline (gstr)
+
+G_ALWAYS_INLINE
+static inline char *
+g_string_clear_inline (GString  *string,
+                       gboolean  free_segment)
+{
+  char *segment;
+
+  if (free_segment)
+    {
+      if (string->str != string->buf)
+        g_free (string->str);
+      segment = NULL;
+    }
+  else
+    {
+      if (string->str != string->buf)
+        segment = string->str;
+      else
+        segment = (char *) g_memdup2 (string->str, string->len + 1);
+    }
+
+  g_string_init (string);
+
+  return segment;
+}
+#define g_string_clear(gstr, free_segment) g_string_clear_inline (gstr, free_segment)
+
+G_ALWAYS_INLINE
 static inline GString*
 g_string_append_c_inline (GString *gstring,
                           gchar    c)
@@ -216,7 +265,6 @@
   gstring->str[gstring->len] = '\0';
   return gstring;
 }
-
 #define g_string_truncate(gstr,len) g_string_truncate_inline (gstr, len)
 
 #if G_GNUC_CHECK_VERSION (2, 0)
diff --git a/glib/tests/string.c b/glib/tests/string.c
index 23f4401..3136f69 100644
--- a/glib/tests/string.c
+++ b/glib/tests/string.c
@@ -629,6 +629,44 @@
     }
 }
 
+static void
+test_string_on_stack (void)
+{
+  GString string;
+  char *s;
+
+  g_string_init (&string);
+
+  g_assert_cmpstr (string.str, ==, "");
+  g_assert_cmpint (string.len, ==, 0);
+  g_assert_true (string.str == string.buf);
+
+  g_string_append_printf (&string, "Three %s", "cheese");
+  g_assert_cmpstr (string.str, ==, "Three cheese");
+  g_string_append (&string, " and a big banana");
+  g_assert_cmpstr (string.str, ==, "Three cheese and a big banana");
+
+  g_string_assign (&string, "On a hot summer night, would you offer your throat to the wolf with red roses?");
+  g_assert_cmpstr (string.str, ==, "On a hot summer night, would you offer your throat to the wolf with red roses?");
+
+  g_string_clear (&string, TRUE);
+
+  g_assert_cmpstr (string.str, ==, "");
+  g_assert_cmpint (string.len, ==, 0);
+  g_assert_true (string.str == string.buf);
+
+  g_string_append_printf (&string, "Three %s", "cheese");
+  g_assert_cmpstr (string.str, ==, "Three cheese");
+  g_string_append (&string, " and a big banana");
+  g_assert_cmpstr (string.str, ==, "Three cheese and a big banana");
+
+  s = (g_string_clear) (&string, FALSE);
+
+  g_assert_cmpstr (s, ==, "Three cheese and a big banana");
+
+  g_free (s);
+}
+
 int
 main (int   argc,
       char *argv[])
@@ -655,6 +693,7 @@
   g_test_add_func ("/string/test-string-set-size", test_string_set_size);
   g_test_add_func ("/string/test-string-to-bytes", test_string_to_bytes);
   g_test_add_func ("/string/test-string-replace", test_string_replace);
+  g_test_add_func ("/string/test-string-on-stack", test_string_on_stack);
 
   return g_test_run();
 }