سؤال

OK, so here's the culprit method :

class FunctionDecl
{
    // More code...

    override void execute()
    {
        //...
        writeln("Before setting... " ~ name);
        Glob.functions.set(name,this);
        writeln("After setting." ~ name);
        //...
    }
}

And here's what happens :

  • If omit the writeln("After setting." ~ name); line, the program crashes, just at this point
  • If I keep it in (using the name attribute is the key, not the writeln itself), it works just fine.

So, I suppose this is automatically garbage collected? Why is that? (A pointer to some readable reference related to GC and D would be awesome)

How can I solve that?


UPDATE :

Just tried a GC.disable() at the very beginning of my code. And... automagically, everything works again! So, that was the culprit as I had suspected. The thing is : how is this solvable without totally eliminating Garbage Collection?


UPDATE II :

Here's the full code of functionDecl.d - "unnecessary" code omitted :

//================================================
// Imports
//================================================

// ...

//================================================
// C Interface for Bison
//================================================

extern (C) 
{
    void* FunctionDecl_new(char* n, Expressions i, Statements s) { return cast(void*)(new FunctionDecl(to!string(n),i,s)); }
    void* FunctionDecl_newFromReference(char* n, Expressions i, Expression r) { return cast(void*)(new FunctionDecl(to!string(n),i,r)); }
}

//================================================
// Functions
//================================================

class FunctionDecl : Statement
{
    // .. class variables ..

    this(string n, Expressions i, Statements s)
    {
        this(n, new Identifiers(i), s);
    }

    this(string n, Expressions i, Expression r)
    {
        this(n, new Identifiers(i), r);
    }

    this(string n, Identifiers i, Statements s)
    {
        // .. implementation ..
    }

    this(string n, Identifiers i, Expression r)
    {
        // .. implementation ..
    }

        // .. other unrelated methods ..

    override void execute()
    {
        if (Glob.currentModule !is null) parentModule = Glob.currentModule.name;

        Glob.functions.set(name,this);
    }
}

Now as for what Glob.functions.set(name,this); does :

  • Glob is an instance holding global definitions
  • function is the class instance dealing with defined functions (it comes with a FunctionDecl[] list
  • set simply does that : list ~= func;

P.S. I'm 99% sure it has something to do with this one : Super-weird issue triggering "Segmentation Fault", though I'm still not sure what went wrong this time...

هل كانت مفيدة؟

المحلول

I think the problem is that the C function is allocating the object, but D doesn't keep a reference. If FunctionDecl_new is called back-to-back in a tight memory environment, here's what would happen:

  1. the first one calls, creating a new object. That pointer goes into the land of C, where the D GC can't see it.
  2. The second one goes, allocating another new object. Since memory is tight (as far as the GC pool is concerned), it tries to run a collection cycle. It finds the object from (1), but cannot find any live pointers to it, so it frees it.
  3. The C function uses that freed object, causing the segfault.

The segfault won't always run because if there's memory to spare, the GC won't free the object when you allocate the second one, it will just use its free memory instead of collecting. That's why omitting the writeln can get rid of the crash: the ~ operator allocates, which might just put you over the edge of that memory line, triggering a collection (and, of course, running the ~ gives the gc a chance to run in the first place. If you never GC allocate, you never GC collect either - the function looks kinda like gc_allocate() { if(memory_low) gc_collect(); return GC_malloc(...); })

There's three solutions:

  • Immediately store a reference in the FunctionDecl_new function in a D structure, before returning:

    FunctionDecl[] fdReferences;
    
    void* FunctionDecl_new(...) {
         auto n = new FunctionDecl(...);
         fdReferences ~= n; // keep the reference for later so the GC can see it
         return cast(void*) n;
    }
    
  • Call GC.addRoot on the pointer right before you return it to C. (I don't like this solution, I think the array is better, a lot simpler.)

  • Use malloc to create the object to give to C:

    void* FunctionDecl_new(...) {
        import std.conv : emplace;
        import core.stdc.stdlib : malloc;
        enum size = __traits(classInstanceSize, FunctionDecl);
    
        auto memory = malloc(size)[0 .. size]; // need to slice so we know the size
    
        auto ref = emplace!FunctionDecl(memory, /* args to ctor */); // create the object in the malloc'd block
    
        return memory.ptr; // give the pointer to C
    }
    

Then, of course, you ought to free the pointer when you know it is no longer going to be used, though if you don't, it isn't really wrong.


The general rule I follow btw is any memory that crosses language barriers for storage (usage is different) ought to be allocated similarly to what that language expects: So if you pass data to C or C++, allocate it in a C fashion, e.g. with malloc. This will lead to the least surprising friction as it gets stored.

If the object is just being temporarily used, it is fine to pass a plain pointer to it, since a temp usage isn't stored or freed by the receiving function so there's less danger there. Your reference will still exist too, if nothing else, on the call stack.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top