mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2025-01-12 15:36:17 +01:00
Add refcounted data
It is useful to provide a "reference counted allocation" API that can add reference counting semantics to any memory allocation. This allows turning data structures that usually are placed on the stack into memory that can be placed on the heap without: - adding a public reference count field - implementing copy/free semantics This mechanism is similar to Rust's Rc<Box<T>> combination of traits, and uses a Valgrind-friendly overallocation mechanism to store the reference count into a private data segment, like we do with GObject's private instance data.
This commit is contained in:
parent
884c4f4eb6
commit
c5d2417d07
@ -120,6 +120,7 @@
|
||||
<xi:include href="gvariant-varargs.xml"/>
|
||||
<xi:include href="gvariant-text.xml"/>
|
||||
<xi:include href="xml/refcount.xml"/>
|
||||
<xi:include href="xml/rcbox.xml"/>
|
||||
</chapter>
|
||||
|
||||
<chapter id="deprecated">
|
||||
|
@ -3466,3 +3466,15 @@ g_atomic_ref_count_inc
|
||||
g_atomic_ref_count_dec
|
||||
g_atomic_ref_count_compare
|
||||
</SECTION>
|
||||
|
||||
<SECTION>
|
||||
<FILE>rcbox</FILE>
|
||||
g_rc_box_alloc
|
||||
g_rc_box_alloc0
|
||||
g_rc_box_new
|
||||
g_rc_box_new0
|
||||
g_rc_box_dup
|
||||
g_rc_box_acquire
|
||||
g_rc_box_release
|
||||
g_rc_box_release_full
|
||||
</SECTION>
|
||||
|
@ -149,6 +149,7 @@ libglib_2_0_la_SOURCES = \
|
||||
gquark.c \
|
||||
gqueue.c \
|
||||
grand.c \
|
||||
grcbox.c \
|
||||
grefcount.c \
|
||||
gregex.c \
|
||||
gscanner.c \
|
||||
@ -286,6 +287,7 @@ glibsubinclude_HEADERS = \
|
||||
gquark.h \
|
||||
gqueue.h \
|
||||
grand.h \
|
||||
grcbox.h \
|
||||
grefcount.h \
|
||||
gregex.h \
|
||||
gscanner.h \
|
||||
|
@ -69,6 +69,7 @@
|
||||
#include <glib/gquark.h>
|
||||
#include <glib/gqueue.h>
|
||||
#include <glib/grand.h>
|
||||
#include <glib/grcbox.h>
|
||||
#include <glib/grefcount.h>
|
||||
#include <glib/gregex.h>
|
||||
#include <glib/gscanner.h>
|
||||
|
398
glib/grcbox.c
Normal file
398
glib/grcbox.c
Normal file
@ -0,0 +1,398 @@
|
||||
/* grcbox.h: Reference counted data
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "grcbox.h"
|
||||
|
||||
#include "gmessages.h"
|
||||
#include "grefcount.h"
|
||||
|
||||
#ifdef ENABLE_VALGRIND
|
||||
#include "valgrind.h"
|
||||
#endif
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/**
|
||||
* SECTION:rcbox
|
||||
* @Title: Reference counted data
|
||||
* @Short_description: Allocated memory with reference counting semantics
|
||||
*
|
||||
* A "reference counted box", or "RcBox", is an opaque wrapper data type
|
||||
* that is guaranteed to be as big as the size of a given data type, and
|
||||
* which augments the given data type with reference counting semantics
|
||||
* for its memory management.
|
||||
*
|
||||
* RcBox is useful if you have a plain old data type, like a structure
|
||||
* typically placed on the stack, and you wish to provide additional API
|
||||
* to use it on the heap, without necessarily implementing copy/free
|
||||
* semantics, or your own reference counting.
|
||||
*
|
||||
* The typical use is:
|
||||
*
|
||||
* |[<!-- language="C" -->
|
||||
* typedef struct {
|
||||
* float x, y;
|
||||
* } Point;
|
||||
*
|
||||
* Point *
|
||||
* point_new (float x, float y)
|
||||
* {
|
||||
* Point *res = g_rc_box_new (Point);
|
||||
*
|
||||
* res->x = x;
|
||||
* res->y = y;
|
||||
*
|
||||
* return res;
|
||||
* }
|
||||
* ]|
|
||||
*
|
||||
* Every time you wish to acquire a reference on the memory, you should
|
||||
* call g_rc_box_acquire(); similarly, when you wish to release a reference
|
||||
* you should call g_rc_box_release():
|
||||
*
|
||||
* |[<!-- language="C" -->
|
||||
* Point *
|
||||
* point_ref (Point *p)
|
||||
* {
|
||||
* return g_rc_box_acquire (p);
|
||||
* }
|
||||
*
|
||||
* void
|
||||
* point_unref (Point *p)
|
||||
* {
|
||||
* g_rc_box_release (p);
|
||||
* }
|
||||
* ]|
|
||||
*
|
||||
* If you have additional memory allocated inside the structure, you can
|
||||
* use g_rc_box_release_full(), which takes a function pointer, which
|
||||
* will be called if the reference released was the last:
|
||||
*
|
||||
* |[<!-- language="C" -->
|
||||
* typedef struct {
|
||||
* char *name;
|
||||
* char *address;
|
||||
* char *city;
|
||||
* char *state;
|
||||
* int age;
|
||||
* } Person;
|
||||
*
|
||||
* void
|
||||
* person_clear (Person *p)
|
||||
* {
|
||||
* g_free (p->name);
|
||||
* g_free (p->address);
|
||||
* g_free (p->city);
|
||||
* g_free (p->state);
|
||||
* }
|
||||
*
|
||||
* void
|
||||
* person_unref (Person *p)
|
||||
* {
|
||||
* g_rc_box_release_full (p, (GDestroyNotify) person_clear);
|
||||
* }
|
||||
* ]|
|
||||
*
|
||||
* If you wish to transfer the ownership of a reference counted data
|
||||
* type without increasing the reference count, you can use g_steal_pointer():
|
||||
*
|
||||
* |[<!-- language="C" -->
|
||||
* Person *p = g_rc_box_new (Person);
|
||||
*
|
||||
* fill_person_details (p);
|
||||
*
|
||||
* add_person_to_database (db, g_steal_pointer (&p));
|
||||
* ]|
|
||||
*
|
||||
* The reference counting operations on data allocated using g_rc_box_alloc(),
|
||||
* g_rc_box_new(), and g_rc_box_dup() are not thread safe; it is your code's
|
||||
* responsibility to ensure that references are acquired are released on the
|
||||
* same thread.
|
||||
*
|
||||
* Since: 2.58.
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
grefcount ref_count;
|
||||
|
||||
gsize mem_size;
|
||||
|
||||
#ifndef G_DISABLE_ASSERT
|
||||
/* A "magic" number, used to perform additional integrity
|
||||
* checks on the allocated data
|
||||
*/
|
||||
guint32 magic;
|
||||
#endif
|
||||
} GRcBox;
|
||||
|
||||
#define G_RC_BOX_MAGIC 0x44ae2bf0
|
||||
#define G_RC_BOX_SIZE sizeof (GRcBox)
|
||||
#define G_RC_BOX(p) (GRcBox *) (((char *) (p)) - G_RC_BOX_SIZE)
|
||||
|
||||
/* We use the same alignment as GTypeInstance and GNU libc's malloc */
|
||||
#define STRUCT_ALIGNMENT (2 * sizeof (gsize))
|
||||
#define ALIGN_STRUCT(offset) ((offset + (STRUCT_ALIGNMENT - 1)) & -STRUCT_ALIGNMENT)
|
||||
|
||||
static gpointer
|
||||
g_rc_box_alloc_full (gsize block_size,
|
||||
gboolean clear)
|
||||
{
|
||||
gsize private_size = G_RC_BOX_SIZE;
|
||||
gsize real_size = private_size + block_size;
|
||||
char *allocated;
|
||||
GRcBox *real_box;
|
||||
|
||||
#ifdef ENABLE_VALGRIND
|
||||
if (RUNNING_ON_VALGRIND)
|
||||
{
|
||||
/* When running under Valgrind we massage the memory allocation
|
||||
* to include a pointer at the tail end of the block; the pointer
|
||||
* is then set to the start of the block. This trick allows
|
||||
* Valgrind to keep track of the over-allocation and not be
|
||||
* confused when passing the pointer around
|
||||
*/
|
||||
private_size += ALIGN_STRUCT (1);
|
||||
|
||||
if (clear)
|
||||
allocated = g_malloc0 (real_size + sizeof (gpointer));
|
||||
else
|
||||
allocated = g_malloc (real_size + sizeof (gpointer));
|
||||
|
||||
*(gpointer *) (allocated + private_size + block_size) = allocated + ALIGN_STRUCT (1);
|
||||
|
||||
VALGRIND_MALLOCLIKE_BLOCK (allocated + private_size, block_size + sizeof (gpointer), 0, TRUE);
|
||||
VALGRIND_MALLOCLIKE_BLOCK (allocated + ALIGN_STRUCT (1), private_size - ALIGN_STRUCT (1), 0, TRUE);
|
||||
}
|
||||
else
|
||||
#endif /* ENABLE_VALGRIND */
|
||||
{
|
||||
if (clear)
|
||||
allocated = g_malloc0 (real_size);
|
||||
else
|
||||
allocated = g_malloc (real_size);
|
||||
}
|
||||
|
||||
real_box = (GRcBox *) allocated;
|
||||
real_box->mem_size = block_size;
|
||||
#ifndef G_DISABLE_ASSERT
|
||||
real_box->magic = G_RC_BOX_MAGIC;
|
||||
#endif
|
||||
g_ref_count_init (&real_box->ref_count);
|
||||
|
||||
return allocated + private_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* g_rc_box_alloc:
|
||||
* @block_size: the size of the allocation
|
||||
*
|
||||
* Allocates @block_size bytes of memory, and adds reference
|
||||
* counting semantics to it.
|
||||
*
|
||||
* The data will be freed when its reference count drops to
|
||||
* zero.
|
||||
*
|
||||
* Returns: a pointer to the allocated memory
|
||||
*
|
||||
* Since: 2.58
|
||||
*/
|
||||
gpointer
|
||||
g_rc_box_alloc (gsize block_size)
|
||||
{
|
||||
g_return_val_if_fail (block_size > 0, NULL);
|
||||
|
||||
return g_rc_box_alloc_full (block_size, FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* g_rc_box_alloc0:
|
||||
* @block_size: the size of the allocation
|
||||
*
|
||||
* Allocates @block_size bytes of memory, and adds reference
|
||||
* counting semantics to it.
|
||||
*
|
||||
* The contents of the returned data is set to 0's.
|
||||
*
|
||||
* The data will be freed when its reference count drops to
|
||||
* zero.
|
||||
*
|
||||
* Returns: a pointer to the allocated memory
|
||||
*
|
||||
* Since: 2.58
|
||||
*/
|
||||
gpointer
|
||||
g_rc_box_alloc0 (gsize block_size)
|
||||
{
|
||||
g_return_val_if_fail (block_size > 0, NULL);
|
||||
|
||||
return g_rc_box_alloc_full (block_size, TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* g_rc_box_new:
|
||||
* @type: the type to allocate, typically a structure name
|
||||
*
|
||||
* A convenience macro to allocate reference counted data with
|
||||
* the size of the given @type.
|
||||
*
|
||||
* This macro calls g_rc_box_alloc() with `sizeof (@type)` and
|
||||
* casts the returned pointer to a pointer of the given @type,
|
||||
* avoiding a type cast in the source code.
|
||||
*
|
||||
* This macro cannot return %NULL, as the minimum allocation
|
||||
* size from `sizeof (@type)` is 1 byte.
|
||||
*
|
||||
* Returns: (not nullable): a pointer to the allocated memory,
|
||||
* cast to a pointer for the given @type
|
||||
*
|
||||
* Since: 2.58
|
||||
*/
|
||||
|
||||
/**
|
||||
* g_rc_box_new0:
|
||||
* @type: the type to allocate, typically a structure name
|
||||
*
|
||||
* A convenience macro to allocate reference counted data with
|
||||
* the size of the given @type, and set its contents to 0.
|
||||
*
|
||||
* This macro calls g_rc_box_alloc0() with `sizeof (@type)` and
|
||||
* casts the returned pointer to a pointer of the given @type,
|
||||
* avoiding a type cast in the source code.
|
||||
*
|
||||
* This macro cannot return %NULL, as the minimum allocation
|
||||
* size from `sizeof (@type)` is 1 byte.
|
||||
*
|
||||
* Returns: (not nullable): a pointer to the allocated memory,
|
||||
* cast to a pointer for the given @type
|
||||
*
|
||||
* Since: 2.58
|
||||
*/
|
||||
|
||||
/**
|
||||
* g_rc_box_dup:
|
||||
* @mem_block: (not nullable): a pointer to reference counted data
|
||||
*
|
||||
* Allocates a new block of data with reference counting
|
||||
* semantics, and copies the contents of @mem_block into
|
||||
* it.
|
||||
*
|
||||
* Returns: (not nullable): a pointer to the allocated memory
|
||||
*
|
||||
* Since: 2.58
|
||||
*/
|
||||
gpointer
|
||||
(g_rc_box_dup) (gpointer mem_block)
|
||||
{
|
||||
GRcBox *real_box = G_RC_BOX (mem_block);
|
||||
gpointer res;
|
||||
|
||||
g_return_val_if_fail (mem_block != NULL, NULL);
|
||||
#ifndef G_DISABLE_ASSERT
|
||||
g_return_val_if_fail (real_box->magic == G_RC_BOX_MAGIC, NULL);
|
||||
#endif
|
||||
|
||||
res = g_rc_box_alloc_full (real_box->mem_size, FALSE);
|
||||
memcpy (res, mem_block, real_box->mem_size);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* g_rc_box_acquire:
|
||||
* @mem_block: (not nullable): a pointer to reference counted data
|
||||
*
|
||||
* Acquires a reference on the data pointed by @mem_block.
|
||||
*
|
||||
* Returns: (not nullabl): a pointer to the data, with its reference
|
||||
* count increased
|
||||
*
|
||||
* Since: 2.58
|
||||
*/
|
||||
gpointer
|
||||
(g_rc_box_acquire) (gpointer mem_block)
|
||||
{
|
||||
GRcBox *real_box = G_RC_BOX (mem_block);
|
||||
|
||||
g_return_val_if_fail (mem_block != NULL, NULL);
|
||||
#ifndef G_DISABLE_ASSERT
|
||||
g_return_val_if_fail (real_box->magic == G_RC_BOX_MAGIC, NULL);
|
||||
#endif
|
||||
|
||||
g_ref_count_inc (&real_box->ref_count);
|
||||
|
||||
return mem_block;
|
||||
}
|
||||
|
||||
/**
|
||||
* g_rc_box_release:
|
||||
* @mem_block: (not nullable): a pointer to reference counted data
|
||||
*
|
||||
* Releases a reference on the data pointed by @mem_block.
|
||||
*
|
||||
* If the reference was the last one, it will free the
|
||||
* resources allocated for @mem_block.
|
||||
*
|
||||
* Since: 2.58
|
||||
*/
|
||||
void
|
||||
g_rc_box_release (gpointer mem_block)
|
||||
{
|
||||
GRcBox *real_box = G_RC_BOX (mem_block);
|
||||
|
||||
g_return_if_fail (mem_block != NULL);
|
||||
#ifndef G_DISABLE_ASSERT
|
||||
g_return_if_fail (real_box->magic == G_RC_BOX_MAGIC);
|
||||
#endif
|
||||
|
||||
if (g_ref_count_dec (&real_box->ref_count))
|
||||
g_free (real_box);
|
||||
}
|
||||
|
||||
/**
|
||||
* g_rc_box_release_full:
|
||||
* @mem_block: (not nullabl): a pointer to reference counted data
|
||||
* @clear_func: (not nullable): a function to call when clearing the data
|
||||
*
|
||||
* Releases a reference on the data pointed by @mem_block.
|
||||
*
|
||||
* If the reference was the last one, it will call @clear_func
|
||||
* to clear the contents of @mem_block, and then will free the
|
||||
* resources allocated for @mem_block.
|
||||
*
|
||||
* Since: 2.58
|
||||
*/
|
||||
void
|
||||
g_rc_box_release_full (gpointer mem_block,
|
||||
GDestroyNotify clear_func)
|
||||
{
|
||||
GRcBox *real_box = G_RC_BOX (mem_block);
|
||||
|
||||
g_return_if_fail (mem_block != NULL);
|
||||
g_return_if_fail (clear_func != NULL);
|
||||
#ifndef G_DISABLE_ASSERT
|
||||
g_return_if_fail (real_box->magic == G_RC_BOX_MAGIC);
|
||||
#endif
|
||||
|
||||
if (g_ref_count_dec (&real_box->ref_count))
|
||||
{
|
||||
clear_func (mem_block);
|
||||
g_free (real_box);
|
||||
}
|
||||
}
|
55
glib/grcbox.h
Normal file
55
glib/grcbox.h
Normal file
@ -0,0 +1,55 @@
|
||||
/* grcbox.h: Reference counted data
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined (__GLIB_H_INSIDE__) && !defined (GLIB_COMPILATION)
|
||||
#error "Only <glib.h> can be included directly."
|
||||
#endif
|
||||
|
||||
#include <glib/gmem.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
GLIB_AVAILABLE_IN_2_58
|
||||
gpointer g_rc_box_alloc (gsize block_size) G_GNUC_MALLOC G_GNUC_ALLOC_SIZE(1);
|
||||
GLIB_AVAILABLE_IN_2_58
|
||||
gpointer g_rc_box_alloc0 (gsize block_size) G_GNUC_MALLOC G_GNUC_ALLOC_SIZE(1);
|
||||
GLIB_AVAILABLE_IN_2_58
|
||||
gpointer g_rc_box_dup (gpointer mem_block) G_GNUC_MALLOC G_GNUC_ALLOC_SIZE(1);
|
||||
GLIB_AVAILABLE_IN_2_58
|
||||
gpointer g_rc_box_acquire (gpointer mem_block);
|
||||
GLIB_AVAILABLE_IN_2_58
|
||||
void g_rc_box_release (gpointer mem_block);
|
||||
GLIB_AVAILABLE_IN_2_58
|
||||
void g_rc_box_release_full (gpointer mem_block,
|
||||
GDestroyNotify clear_func);
|
||||
|
||||
#define g_rc_box_new(type) \
|
||||
((type *) g_rc_box_alloc (sizeof (type)))
|
||||
#define g_rc_box_new0(type) \
|
||||
((type *) g_rc_box_alloc0 (sizeof (type)))
|
||||
|
||||
#if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) && !defined(_cplusplus)
|
||||
/* Type check to avoid assigning references to different types */
|
||||
# define g_rc_box_acquire(mem_block) ((__typeof__(mem_block)) (g_rc_box_acquire) (mem_block))
|
||||
/* Type check to avoid duplicating data to different types */
|
||||
# define g_rc_box_dup(mem_block) ((__typeof__(mem_block)) (g_rc_box_dup) (mem_block))
|
||||
#endif
|
||||
|
||||
G_END_DECLS
|
@ -76,6 +76,7 @@ glib_sub_headers = files(
|
||||
'gquark.h',
|
||||
'gqueue.h',
|
||||
'grand.h',
|
||||
'grcbox.h',
|
||||
'grefcount.h',
|
||||
'gregex.h',
|
||||
'gscanner.h',
|
||||
@ -160,6 +161,7 @@ glib_sources = files(
|
||||
'gquark.c',
|
||||
'gqueue.c',
|
||||
'grand.c',
|
||||
'grcbox.c',
|
||||
'grefcount.c',
|
||||
'gregex.c',
|
||||
'gscanner.c',
|
||||
|
@ -89,6 +89,7 @@ test_programs = \
|
||||
protocol \
|
||||
queue \
|
||||
rand \
|
||||
rcbox \
|
||||
rec-mutex \
|
||||
regex \
|
||||
rwlock \
|
||||
|
@ -46,6 +46,7 @@ glib_tests = [
|
||||
'protocol',
|
||||
'queue',
|
||||
'rand',
|
||||
'rcbox',
|
||||
'rec-mutex',
|
||||
'refcount',
|
||||
'refcount-macro',
|
||||
|
82
glib/tests/rcbox.c
Normal file
82
glib/tests/rcbox.c
Normal file
@ -0,0 +1,82 @@
|
||||
#include <glib.h>
|
||||
|
||||
typedef struct {
|
||||
float x, y;
|
||||
} Point;
|
||||
|
||||
static Point *global_point;
|
||||
|
||||
static void
|
||||
test_rcbox_new (void)
|
||||
{
|
||||
Point *a = g_rc_box_new (Point);
|
||||
|
||||
g_assert_nonnull (a);
|
||||
|
||||
g_rc_box_release (a);
|
||||
|
||||
a = g_rc_box_new0 (Point);
|
||||
g_assert_nonnull (a);
|
||||
g_assert_cmpfloat (a->x, ==, 0.f);
|
||||
g_assert_cmpfloat (a->y, ==, 0.f);
|
||||
|
||||
g_rc_box_release (a);
|
||||
}
|
||||
|
||||
static void
|
||||
point_clear (Point *p)
|
||||
{
|
||||
g_assert_nonnull (p);
|
||||
|
||||
g_assert_cmpfloat (p->x, ==, 42.0f);
|
||||
g_assert_cmpfloat (p->y, ==, 47.0f);
|
||||
|
||||
g_assert_true (global_point == p);
|
||||
global_point = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
test_rcbox_release_full (void)
|
||||
{
|
||||
Point *p = g_rc_box_new (Point);
|
||||
|
||||
g_assert_nonnull (p);
|
||||
global_point = p;
|
||||
|
||||
p->x = 42.0f;
|
||||
p->y = 47.0f;
|
||||
|
||||
g_assert_true (g_rc_box_acquire (p) == p);
|
||||
|
||||
g_rc_box_release_full (p, (GDestroyNotify) point_clear);
|
||||
g_assert_nonnull (global_point);
|
||||
g_assert_true (p == global_point);
|
||||
|
||||
g_rc_box_release_full (p, (GDestroyNotify) point_clear);
|
||||
g_assert_null (global_point);
|
||||
}
|
||||
|
||||
static void
|
||||
test_rcbox_dup (void)
|
||||
{
|
||||
Point *a = g_rc_box_new (Point);
|
||||
Point *b = g_rc_box_dup (a);
|
||||
|
||||
g_assert_true (a != b);
|
||||
|
||||
g_rc_box_release (a);
|
||||
g_rc_box_release (b);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc,
|
||||
char *argv[])
|
||||
{
|
||||
g_test_init (&argc, &argv, NULL);
|
||||
|
||||
g_test_add_func ("/rcbox/new", test_rcbox_new);
|
||||
g_test_add_func ("/rcbox/dup", test_rcbox_dup);
|
||||
g_test_add_func ("/rcbox/release-full", test_rcbox_release_full);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
Loading…
Reference in New Issue
Block a user