Question

I'm using Word-Net, an old C library developed by Princeton University back in the nineties. The library is written in C, and only reveals the headers but not its actual implementation.

The only structure I use is:

SynsetPtr

And the two functions I call are:

findtheinfo_ds

traceptrs_ds

Both those functions return a SynsetPtr.

Howevever, when the SynsetPtr represents a sense list, I have to free it using

free_syns

Whereas, when the SynsetPtr is used to traverse a linked list (a hierarchical tree), I have to free it using

free_synset

The Documentation is not really clear when to call which, and why.

This is quickly becoming a nightmare for me. I have spent three days working my way slowly through leaks, double frees, memory allocations and such.

So I was wondering, is there a way for me, to wrap around those functions, or the actual Structure, and make C++ manage the memory? Ideally I would want them free'd when there are no more references to them, as is the case with std::shared_ptr.

Is this possible, knowing that Synset_Ptr has no destructor, but a dealloc function has to be called?

Alternatively, could I wrap around the two functions that create (allocate) those structures, somehow book-keep the objects, and destroy them when no references to them remain?

I would really appreciate any help whatsoever!

EDIT:

This is the exact declaration of SynsetPtr in wn.h

/* Structure for data file synset */
typedef struct ss {
    long hereiam;       /* current file position */
    int sstype;         /* type of ADJ synset */
    int fnum;           /* file number that synset comes from */
    char *pos;          /* part of speech */
    int wcount;         /* number of words in synset */
    char **words;       /* words in synset */
    int *lexid;         /* unique id in lexicographer file */
    int *wnsns;         /* sense number in wordnet */
    int whichword;      /* which word in synset we're looking for */
    int ptrcount;       /* number of pointers */
    int *ptrtyp;        /* pointer types */
    long *ptroff;       /* pointer offsets */
    int *ppos;          /* pointer part of speech */
    int *pto;           /* pointer 'to' fields */
    int *pfrm;          /* pointer 'from' fields */
    int fcount;         /* number of verb frames */
    int *frmid;         /* frame numbers */
    int *frmto;         /* frame 'to' fields */
    char *defn;         /* synset gloss (definition) */
    unsigned int key;       /* unique synset key */

    /* these fields are used if a data structure is returned
       instead of a text buffer */

    struct ss *nextss;      /* ptr to next synset containing searchword */
    struct ss *nextform;    /* ptr to list of synsets for alternate
                   spelling of wordform */
    int searchtype;     /* type of search performed */
    struct ss *ptrlist;     /* ptr to synset list result of search */
    char *headword;     /* if pos is "s", this is cluster head word */
    short headsense;        /* sense number of headword */
} Synset;

typedef Synset *SynsetPtr;


/* Primary search algorithm for use with programs (returns data structure) */
extern SynsetPtr findtheinfo_ds(char *, int, int, int); 

/* Recursive search algorithm to trace a pointer tree and return results
   in linked list of data structures. */
SynsetPtr traceptrs_ds(SynsetPtr, int, int, int);

/* Free a synset linked list allocated by findtheinfo_ds() */
extern void free_syns(SynsetPtr);   

/* Free a synset */
extern void free_synset(SynsetPtr); 

And that is essentially all I know.

EDIT 2:

Even though I've used the two answers below, unfortunatelly, the functions are still leaking bytes.

That only seems to happen with:

traceptrs_ds ( ptr, SIMPTR, ADJ, 0 )

The documentation has very little information about adjective synonyms (-synsa) or other types (-synsn, -synsv).

However, I managed to iterate most of them, simply by following the ptr->ptrlist && ptr->nextss;

traceptr_ds iterates ALL of them, but I cannot find a way to avoid the leak, even when using a minified test prog.

Thanks to the whoever helped, very much appreciated.

Was it helpful?

Solution 2

I know how to solve this problem for unique ownership, using unique_ptr's nifty feature where its managed type becomes Deleter::pointer, instead of T*, if the former type exists.

Assuming you don't have the definition of Synset, or whatever type SynsetPtr points to, the problem with using a shared_ptr is that it doesn't have the same facility of switching the managed type, and if you create a shared_ptr<SynsetPtr>, the constructor will expect a SynsetPtr*, but your C API functions don't return that type. And I don't know for sure whether using shared_ptr<remove_pointer<SynsetPtr>::type> will compile if you don't have the definition of the type that dereferencing a SynsetPtr yields.

This might work, but I'm not sure.

std::shared_ptr<std::remove_pointer<SynsetPtr>::type>
make_findtheinfo_ds(char *searchstr, int pos, int ptr_type, int sense_num)
{
    return std::shared_ptr<std::remove_pointer<SynsetPtr>::type>
               (findtheinfo_ds(searchstr, pos, ptr_type, sense_num),
                free_syns);
}

std::shared_ptr<std::remove_pointer<SynsetPtr>::type>
make_traceptrs_ds(SynsetPtr synptr, int ptr_type, int pos, int depth)
{
    return std::shared_ptr<std::remove_pointer<SynsetPtr>::type>
               (traceptrs_ds(synptr, ptr_type, pos, depth),
                free_synset);
}

Going the unique ownership route, I'd make a couple of factory functions that return unique_ptrs managing SynsetPtrs.

We need 2 separate deleters for the different kinds of SynsetPtrs

struct sense_list_del
{
    using pointer = SynsetPtr;
    void operator()(SynsetPtr p)
    {
        free_syns(p);
    }
};

struct linked_list_del
{
    using pointer = SynsetPtr;
    void operator()(SynsetPtr p)
    {
        free_synset(p);
    }
};

std::unique_ptr<SynsetPtr, sense_list_del>
make_findtheinfo_ds(char *searchstr, int pos, int ptr_type, int sense_num)
{
    return std::unique_ptr<SynsetPtr, sense_list_del>
               (findtheinfo_ds(searchstr, pos, ptr_type, sense_num));
}

std::unique_ptr<SynsetPtr, linked_list_del>
make_traceptrs_ds(SynsetPtr synptr, int ptr_type, int pos, int depth)
{
    return std::unique_ptr<SynsetPtr, linked_list_del>
               (traceptrs_ds(synptr, ptr_type, pos, depth));
}

OTHER TIPS

You can use std::shared_ptr for this purpose, as you can provide the deleter to use to free the pointer.

std::shared_ptr<Synset> findTheInfo(...) {
    std::shared_ptr<Synset> sp(findtheinfo(...), free_syns);
    return sp;
}
std::shared_ptr<Synset> tracePtrs(...) {
    std::shared_ptr<Synset> sp(traceptrs(...), free_synset);
    return sp;
}

Now, if they really represent different things, you might want to spend a bit more effort and provide two types that wrap each use and provide the appropriate interface. Does it make sense to treat both as the same type when transversing a list and a tree might be completely different?

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top