Request: clarification of when and how structs are copied (e.g., GLib.Value)
(Please let me know if I should ask this question in a different forum.)
This is not a defect report, but something about struct
code generation that confuses me. In case this is an XY problem, the underlying question is "how do I safely wrap heap-allocated storage in GLib.Value
s without risking double-frees?".
The below test program uses GLib.Value
structs. GLib.Value
has [CCode(copy_function = "g_value_copy")]
(vapi/gobject-2.0.vapi
). However, the generated C code only sometimes uses g_value_copy()
, and sometimes uses direct assignment. Why? Are struct
s defined in a VAPI required to be safely copyable by simple assignment? I am concerned that I might wrap my own heap-allocated type in a Value
and the copy function would not be invoked on value copy, leading to a double-free when the two Value instances are cleared from scope.
I would appreciate any clarification you could provide. https://wiki.gnome.org/Projects/Vala/Manual/Structs does not currently have any detail, and I can't find much in the bindings pages either. (Of course, I may simply be looking in the wrong place.) Thank you!
Edit after writing, I stumbled across a relevant FAQ entry. It says "Structs are always shallow copied before they are passed to C functions." Does this mean that I can't assume copy_function
will be called appropriately for structs? Thanks!
Sample program
int main(string[] args)
{
Gst.init(ref args);
GLib.Value v1, v2;
v1 = Value(typeof(string));
v1.set_string("Hello, world!");
v2 = v1;
print("%s\n", Gst.Value.serialize(v2));
return 0;
}
Generated C code (excerpts)
Tested with master
as of writing (3f2a66e9). See <===
markers.
/* testvalues.c generated by valac 0.50.1.79-3f2a6, the Vala compiler
* generated from testvalues.vala, do not modify */
<cut>
gint
_vala_main (gchar** args,
gint args_length1)
{
GValue v1 = {0};
GValue v2 = {0};
GValue _tmp0_ = {0};
GValue _tmp1_;
GValue _tmp2_;
GValue _tmp3_ = {0};
GValue _tmp4_;
gchar* _tmp5_;
gchar* _tmp6_;
gint result = 0;
gst_init ((gint*) (&args_length1), &args);
g_value_init (&_tmp0_, G_TYPE_STRING);
G_IS_VALUE (&v1) ? (g_value_unset (&v1), NULL) : NULL;
v1 = _tmp0_;
g_value_set_string (&v1, "Hello, world!");
_tmp1_ = v1; // <======= direct copy of struct value (unexpected)
_tmp2_ = _tmp1_;
if (G_IS_VALUE (&_tmp2_)) {
g_value_init (&_tmp3_, G_VALUE_TYPE (&_tmp2_));
g_value_copy (&_tmp2_, &_tmp3_); // <======= use of copy_function (expected)
} else {
_tmp3_ = _tmp2_;
}
G_IS_VALUE (&v2) ? (g_value_unset (&v2), NULL) : NULL;
v2 = _tmp3_; // <==== even though we filled _tmp3_ using the copy_function, here we directly assign.
_tmp4_ = v2;
_tmp5_ = gst_value_serialize (&_tmp4_);
_tmp6_ = _tmp5_;
g_print ("%s\n", _tmp6_);
_g_free0 (_tmp6_);
result = 0;
G_IS_VALUE (&v2) ? (g_value_unset (&v2), NULL) : NULL;
G_IS_VALUE (&v1) ? (g_value_unset (&v1), NULL) : NULL;
return result;
}
<cut>