4

I have a structure

typedef struct foo {
    int lengthOfArray1;
    int lengthOfArray2;
    int* array1;
    int* array2;
} foo;

I need to allocate enough memory for the entire structure and its array's contents. So assuming each array had a length of 5...

foo* bar = (foo*)malloc(sizeof(foo) + (sizeof(int) * 5) + (sizeof(int) * 5));

I now have to point array1 and array2 to the correct location in that allocated buffer:

bar->array1 = (int*)(&bar->lengthOfArray2 + sizeof(int));
bar->array2 = (int*)(bar->array1 + lengthOfArray2);

Is this correct?

Edit #1

Just to clear up any confusion: I am trying to keep the memory in one block, and not three.

Edit #2

I cannot use C99 as the MSVC 2010 compiler does not support it (http://stackoverflow.com/questions/6688895/does-microsoft-visual-studio-2010-supports-c99).

alk
  • 69,737
  • 10
  • 105
  • 255
LunchMarble
  • 5,079
  • 9
  • 64
  • 94
  • In one step? NO, is not correct, only the last member can be allocated in this manner using a flexible array (C99), take a look to http://en.wikipedia.org/wiki/Data_padding – David Ranieri Oct 14 '12 at 07:12
  • This could very well be done in one step. Please see certain answers below. @DavidRF – alk Oct 14 '12 at 15:16

3 Answers3

4

You have to allocate the sizeof the structure. You then have to allocate the array of ints with their respective sizes.

foo* bar = malloc(sizeof(foo));

/* somewhere in here the array lengths are set then... */

bar->array1 = malloc(sizeof(int) * bar->lengthOfArray1);
bar->array2 = malloc(sizeof(int) * bar->lengthOfArray2);
Duck
  • 26,924
  • 5
  • 64
  • 92
  • 2
    The key to this answer is that there are two steps, first allocating the struct, then allocating the memory pointed to by the structure. You cannot allocate both the struct and the array memory at the same time. – Chris Mansley Oct 14 '12 at 05:14
  • Personally, I find it better practice to write: bar->arrayX = malloc(sizeof(*bar->arrayX) * bar->lengthOfArrayX); If you should decide to change the type from int to something else. Produces the same code. – Pat Oct 14 '12 at 10:15
  • 1
    Following your approach you create three independend memory regions which might make the handling (copying, clearing) of the logical structur `foo` including its dynamical array members more complex than necessary. – alk Oct 14 '12 at 15:24
  • Yes, we can ... ;-) @ChrisMansley – alk Oct 14 '12 at 16:03
  • 2
    @alk - prior to my answer I looked at OPs previous questions and 21 hours ago he was unaware that compilers may pad structs. I think this answer was the appropriate one in those circumstances and, indeed, 99.9% of circumstances. – Duck Oct 14 '12 at 20:45
3

Following the OP's approach this should do the job:

/* Defining these types allows to change the types without having the need to modify the code. */
typedef int Foo_ArrayElement1_t;
typedef int Foo_ArrayElement2_t;

typedef struct Foo_s {
    size_t lengthOfArray1; /* 'size_t' is the type of choice for array/memory dimensions. */
    size_t lengthOfArray2;
    Foo_ArrayElement1_t * array1;
    Foo_ArrayElement2_t * array2;
} Foo_t;

/*
 * Allocates memory to hold a structure of type Foo_t including size for 
 * 's1' elements referenced by 'array1' and 's2' elements by 'array2'.
 * The arrays' elements are set to 0.
 *
 * Returns a pointer to the freshly allocated memory or NULL if the memory could not 
 * be allocated.
 */
Foo_t * Foo_CreateAndInit(size_t s1, size_t s2)
{
  /* At once allocate all 'Foo_t' (including the memory Foo_t's array pointers shall point to). */
  Foo_t * pfoo = calloc(1,
      sizeof(*pfoo) +
      s1 * sizeof(*(pfoo->array1) + 
      s2 * sizeof(*(pfoo->array2)));
  if (pfoo)
  {
    pfoo->lengthOfArray1 = s1;
    pfoo->lengthOfArray2 = s2;

    /* The first array starts right after foo. */
    pfoo->array1 = (Foo_ArrayElement1_t *) (pfoo + 1); 

    /* The second array starts right after s1 elements of where the first array starts. */
    pfoo->array2 = (Foo_ArrayElement2_t *) (pfoo->array1 + s1); /* That casting here is not 
        necessaryas long as 'Foo_t.array1' and 'Foo_t.array2' point to the same type but makes 
        the code work even if those types were changed to be different. */
  }

  return pfoo;
}

...

Foo_t * foo = Foo_CreateAndInit(5, 5);
alk
  • 69,737
  • 10
  • 105
  • 255
  • I looked up size_t, it seems to be used in C99 which I cannot use. – LunchMarble Oct 14 '12 at 15:50
  • From my knowledge `size_t` existed looong before `C99`. Anyway, array sizes do not need to be `signed` at least. So just use what ever type is used by the arguments of `malloc()`/`calloc()` on your platform. @StormKiernan – alk Oct 14 '12 at 15:52
  • did you intend for: sizeof(*pfoo)? Or sizeof(Foo_t)? – LunchMarble Oct 14 '12 at 16:04
  • 1
    `sizeof(*pfoo)` and `sizeof(Foo_t)` return the same result. The first version is resistant against changes in the declaration of `pfoo`, as it simply says: "*Tell me how large is what I'm pointing to, no matter what it is*". This construct makes it easier to maintain the code. As changing the declaration of `pfoo` does **not** make it necessary to change the code for allocation. – alk Oct 14 '12 at 16:08
  • Good solution, but are you sure you can not have padding bytes between foo and the allocation for ints? (excuse my poor english) – David Ranieri Oct 15 '12 at 07:53
  • Why should there be any padding? And if there were some bytes added, then to the types involved in this game. An such additonal bytes would then be recognozed by `sizeof` used to calculate the offsets, wouldn't they? @DavidRF – alk Oct 15 '12 at 08:02
  • 1
    The compiler does not know there will be `int`s or anything else after the `Foo_t`. @DavidRF – alk Oct 15 '12 at 08:16
  • Yes, as allocate at once the `Foo_t` and the data pointed to by its members `array1` and `array2` are held in one continous memory block. There might be padding applied to `Foo_t`, but as already mentioned this extra space will be taken into account by `sizeof Foo_t`, as its part of Foo_t. @DavidRF – alk Oct 15 '12 at 08:39
1

With a little extra memory (only for C99):

#include <stdio.h>
#include <stdlib.h>

typedef struct foo {
    int lengthOfArray1;
    int lengthOfArray2;
    int *array1;
    int *array2;
    int array[];
} foo;

int main(void)
{
    foo *bar = malloc(sizeof(foo) + (sizeof(int) * 10));

    bar->array1 = &bar->array[0];
    bar->array2 = &bar->array[5]; /* 5 or lengthOfArray1 */

    return 0;
}
David Ranieri
  • 39,972
  • 7
  • 52
  • 94
  • You can make this work for any compiler by using `int array[1]` and subtracting 1 in the `malloc` (the "old" way to do a flexible array member). – nneonneo Oct 14 '12 at 07:49
  • @nneonneo, yes but as c-faq says it's not clear if it's legal or portable http://c-faq.com/struct/structhack.html – David Ranieri Oct 14 '12 at 07:59