0

I am trying to implement something that requires a structure like this:

struct abc
{  
  int size;
  struct abc *links[size];
}

Here, I want size to change at runtime, not just merely be different for each instance of abc, and instances of abc have varying number of links depending on the program. How do I create/manage/allocate memory for such a data structure? Is it even possible in C?

Aaron McDaid
  • 26,501
  • 9
  • 66
  • 88
Gitmo
  • 2,366
  • 5
  • 25
  • 31
  • Is it required that `size` change at run time or is it fixed at compile time? – hmjd Dec 21 '11 at 20:51
  • Yes it can change. Think of it as some kind of a tree data structure with varying number of child nodes.. – Gitmo Dec 21 '11 at 20:56
  • You can also use [flexible array members](http://stackoverflow.com/a/247038/745924) – Piotr Praszmo Dec 21 '11 at 21:01
  • @Banthar, I just added that as an answer before I saw your comment. You should have added an answer! :-) – Aaron McDaid Dec 21 '11 at 21:15
  • I've just realized that most of our answers are wrong. @Gitmo requires that the size of the array be changeable at runtime (see the comment just above Banthar's). I think only Francois's is the correct answer. I'm gonna delete my own answer. – Aaron McDaid Dec 23 '11 at 21:09
  • @Gitmo, You said "Think of it as some kind of a tree data structure with varying number of child nodes". You don't just mean that each node will have a different number of children? You mean that the number of nodes can change at runtime? I think that's what you need? – Aaron McDaid Dec 23 '11 at 22:19

5 Answers5

3

Easyest way would be to change your structure like this:

struct abc
{  
  int size;
  struct abc **links;
}

and calloc the array dynamically:

abc.links = (struct abc **)calloc(abc.size, sizeof(void *));

Then you can address the elements of the links array this way:

struct abc a,b,c, ...;
int n;
...
a.links = (struct abc **)calloc(a.size, sizeof(void *);
a.links[n] = &b;
struct abc *z = a.links[n];

Be careful of not trying to access elements outside the boundaries of the calloc'ed array or you will get memory exceptions and unexpected behavior. Remember also to free(abc.links) for each allocated structure to prevent memory leaks...

Hope this helps

Francois
  • 330
  • 1
  • 7
  • No this is not the way it is foreseen to be dealt with in the C standard. – Jens Gustedt Dec 21 '11 at 23:39
  • @JensGustedt: Both Francois' and your answers have merits, and tradeoffs. This answer allows passing `struct abc` by value better. – u0b34a0f6ae Dec 22 '11 at 01:53
  • It may not be approved by the theoretical standard, but it works very well in production code :) – Francois Dec 23 '11 at 17:01
  • (Ignore that comment a moment ago, I meant it for a different comment). While I'm here though, I want to ask @JensGustedt what problem there might be? As far as I can see Francois's answer is perfect. Have I missed some problem? – Aaron McDaid Dec 23 '11 at 19:13
  • @AaronMcDaid, I suppose you have found my answer in the mean time. Decomposing into several allocations if not necessary is always suboptimal. First, such allocations introduce more possibilities for errors: the code is more complicated and more difficult to maintain. Then performance might also be an issue, since this method here introduces an additional indirection where a direct allocation can be resolved directly with pointer arithmetic. – Jens Gustedt Dec 23 '11 at 20:44
  • I agree. Maybe I shouldn't have said it was 'perfect'. But the code I can see it is correct. – Aaron McDaid Dec 23 '11 at 21:05
3

The C standard (C99 and following) foresees the following construct to deal with such a situation, called flexible array member:

struct abc
{  
  size_t size;
  struct abc *links[];
};

You'd allocate such a beast with malloc:

struct abc * a = malloc(sizeof(struct abc) + sizeof(struct abc*[n]));
a.size = n;
for(size_t i = 0; i < n; ++i) a.links[i] = 0;
.
free(a);
Jens Gustedt
  • 76,821
  • 6
  • 102
  • 177
  • 1
    You should probably note that only C99 supports this. MSVC would never allow this. – Dave Dec 22 '11 at 06:11
  • @Dave, true, MSVC only implements a historic version of C, one probably shouldn't use that anymore, there are alternatives even for windows systems. The new ISO standard for C, C11, continues to have flexible array members as mandatory elements of the language. – Jens Gustedt Dec 22 '11 at 09:45
  • @Dave, sure. C99 dates from 1999 and has been amended two times since. And now, C11 has been published last week. That MS pouts in their corner and refuses to upgrade to modern times is just their problem, and nobody else's. Anybody who wants to program with modern C should chose a decent compiler that implements modern standards. – Jens Gustedt Dec 22 '11 at 10:34
  • 2
    As long as they're writing for a popular platform, on which popular compilers exist, sure; but C99 isn't yet the paragon of portability that ANSI C is. – Dave Dec 22 '11 at 14:02
  • @Dave, MS with its own compilers is just the last not to implement the major C99 features, I think. – Jens Gustedt Dec 22 '11 at 18:40
  • @JensGustedt, you could add a note that if your compiler doesn't like this, then the user should put a small size, such as 0 or 1, inside the brackets. Definitely `[]` is the best answer if your compiler supports it. But those with older compilers can workaround it quite nicely. – Aaron McDaid Dec 23 '11 at 19:30
1

You can do this, but it's not automatic. I would do it with some kind of initialization routine:

struct *abc abc_init(int size)
{
    struct abc *abc = calloc(1, sizeof(*abc));
    abc->size = size;
    abc->links = calloc(1, sizeof(*(abc->links)) * size);

    return abc;
}

Then you could also have a free routine:

void abc_free(struct abc *abc)
{
    for (int i = 0; i < abc->size; i++) {
        abc_free(abc->links[i]);
    }
    free(abc->links);
    free(abc);
}

Note that abc_free() calls itself for each element in links in this example, so you have to watch our for recursion here. Also note that all error checking (e.g. NULL checking) is missing here, for compactness.

These routines simplify creating and freeing your structs, then all you need to do is access/set them.

Dan Fego
  • 13,644
  • 6
  • 48
  • 59
0

There is a possibility if you don't want to have to remember to call free(abc.links) and if size won't change during the lifetime of the object:

struct abc
{  
    int size;
    struct abc *links[0]; // use 1 if your compiler complains
}

(Note: This special zero-length array must be the last member of the struct)

How can a zero-length array help, you might ask? It works if you allocate abc with

 struct abc * a = malloc(sizeof(abc)+ size * sizeof(struct abc *));

This deliberately creates too much memory such that we can then use a->links[3] or a->link[4] safely (as long as we remember to respect the size. We can then simply use free(a), we don't need to (and cannot) free(a->links);.

In either case, don't forget to free each of the a->links[0] and a->links[1] and so on to a->links[a->size - 1] before attempting to free a.

Aaron McDaid
  • 26,501
  • 9
  • 66
  • 88
  • 1
    The correct way to do this in modern C is with `[]` for `links`. – Jens Gustedt Dec 21 '11 at 23:38
  • This is an interesting approach that I had not seen before. Do we know for sure it would work with all compilers/environments? IMHO, memory allocation and management like this is one of the forte of object orientation where you can encapsulate the inner workings inside the class and not have the "consumer" worry about it. A nice separation of concerns :) – Francois Dec 23 '11 at 17:04
  • This is well-defined behaviour and will work anywhere (note the recent clarification I've made). The `[]` method is primarily to help force us to make sure this member is the last member. – Aaron McDaid Dec 23 '11 at 19:15
0

There are people who will write this:

struct abc
{  
  size_t size;
  struct abc *links[1];
};

struct abc* abc_create(size_t size)
{
   struct abc* abc = calloc(1, sizeof(struct abc) + (size - 1) * sizeof(struct abc*));
   abc->size = size;
   return abc;
 }
Neil
  • 54,642
  • 8
  • 60
  • 72