Search code examples
arraysstructvala

Memory issues with fixed sized array of structs


I am trying to create a small fixed size list of string, int tuples. A fixed size array of structs seemed like the way to go, but when manipulating the array entries, I constantly run into memory errors. What I've tried so far:

public struct S {
    public string a;
    public int b;

    public S (string a, int b) {
        this.a = a;
        this.b = b;
    }
}

public class Test {
    public S arr[5];

    public static void main () {
        var test = new Test ();
        test.arr[0].a = "hi";
        test.arr[0].b = 5;
        /*   alternatively:   */
        //test.arr[0] = S ("hi", 5);
    }
}

I have looked into the compiled C code, but I am not really familiar with C. I read everything I found about vala structs and arrays of structs, but the little bit that's out there didn't enlighten me either.

The fixed size array seems to get initialized with "empty" structs, do I need to initialize it beyond that, somehow? What am I misunderstanding about arrays of structs here? Is there an alternative way to implement a fixed size list of string, int tuples? Are arrays of structs not suited for that?

Any help is greatly appreciated! It seems like such a simple task, but I've been struggling with it for days now :/ ...


Solution

  • First, you can make the C code quite a bit simpler by specific "Compact" on the class and disabling the type on the struct:

    [CCode(has_type_id = false)]
    public struct S {
        public string a;
        public int b;
    
        public S (string a, int b) {
            this.a = a;
            this.b = b;
        }
    }
    
    [Compact]
    public class Test {
        public S arr[5];
    
        public static void main () {
            var test = new Test ();
            test.arr[0].a = "hi";
            test.arr[0].b = 5;
            /*   alternatively:   */
            //test.arr[0] = S ("hi", 5);
        }
    }
    

    Not a full answer, but it seems like there is a problem in the compiler generated destruction code:

    void test_free (Test* self) {
        _vala_array_destroy (self->arr, 5, (GDestroyNotify) s_destroy);
        g_slice_free (Test, self);
    }
    
    
    static void _vala_array_destroy (gpointer array, gint array_length, GDestroyNotify destroy_func) {
        if ((array != NULL) && (destroy_func != NULL)) {
            int i;
            for (i = 0; i < array_length; i = i + 1) {
                if (((gpointer*) array)[i] != NULL) {
                    destroy_func (((gpointer*) array)[i]);
                }
            }
        }
    }
    

    Note how the array parameter (which is of type gpointer, but was casted from an S[], namely arr) is casted to a gpointer* before the destroy_func () is called on it.

    That would be fine if arr were a dynamic array, but it isn't.

    If I modify the compiler output by hand everything works fine:

    static void _vala_array_destroy (S* array, gint array_length, GDestroyNotify destroy_func) {
        if ((array != NULL) && (destroy_func != NULL)) {
            int i;
            for (i = 0; i < array_length; i = i + 1) {
                if (&array[i] != NULL) {
                    destroy_func (&array[i]);
                }
            }
        }
    }
    

    The destroy function (destroy_func aka s_destroy) is now called on a valid S* (the address of the struct inside the array).

    So it seems to me that you have discovered a compiler bug.

    PS: Using a dynamic array works just fine, I would either do that or use some higher level data type like a Gee.ArrayList instead of a static array.