Bill Reid said:
Keith Thompson said:
Of course, you can allocate a "data" pointer contiguously in a struct
in any event, right?
typedef struct {
unsigned data_type;
unsigned data_size;
unsigned *data;
} contiguous_data_struct;
contiguous_data_struct *my_contiguous_data_struct;
void create_struct(unsigned data_type,unsigned data_size) {
my_contiguous_data_struct=
malloc(sizeof(contiguous_data_struct)+(data_size*sizeof(unsigned));
my_contiguous_data_struct->data_type=data_type;
my_contiguous_data_struct->data_size=data_size;
my_contiguous_data_struct->data=
my_contiguous_data_struct+sizeof(contiguous_data_struct);
}
And now you may write the data to my_contiguous_data_struct->data(++).
[...]
I see two problems with this.
Actually, as it turns out, there were at least a couple more, but
who's counting?
First, there's no guarantee that
my_contiguous_data_struct+sizeof(contiguous_data_struct)
s/be (void *)my_contiguous_data_struct+1
It took me a while to realize that "s/be" meant "should be". It's
worth the effort to use whole words.
And it should really be (void*)(my_contiguous_data_struct+1);
otherwise you're adding 1 to a value of type void*, which is illegal
(but gcc will accept it with no warning by default -- another reason
why that extension is a bad idea).
Now this confuses me...when you say "no guarantee" that the
struct memory (?) is "properly aligned", what specifically are you
talking about? The struct itself is aligned (within itself!), so doesn't
that just leave any possible "alignment" of (in this case) a fundamental
type? Is this just a pathological theoretical possibility, or something
that could really happen?
It could really happen. I'll construct an example similar to what you
wrote above:
#include <stdlib.h>
struct contiguous_data {
unsigned data_type;
unsigned data_size;
double *data;
};
struct contiguous_data *create_struct(unsigned data_type, unsigned data_size)
{
struct contiguous_data *result
= malloc(sizeof(struct contiguous_data) +
data_size * sizeof(double));
if (result == NULL) {
return NULL;
}
result->data_type = data_type;
result->data_size = data_size;
result->data = (double*)(result + 1);
return result;
}
Suppose types unsigned and (double*) are 4 bytes, requiring 4-byte
alignment, and double is 8 bytes, requiring 8-byte alignment.
Assuming struct contiguous_data has no gaps, its size is 12 bytes;
let's say it requires 4-byte alignment. And suppose we call
create_struct() with data_size == 2.
Then create_struct() will malloc() 12 + 2*8 bytes, or 28 bytes. The
base address of the malloc()ed block is guaranteed to be properly
aligned for any type. We treat the first 12 bytes as a struct
contiguous_data object, which is fine. We then treat the last 16
bytes, starting at offset 12, as an array of 2 doubles -- but since 12
is not a multiple of 8, it's not properly aligned to hold doubles.
Misalignment might be less likely in your original example, but
it's still possible.
If you used the "struct hack", you'd declare:
struct contiguous_data {
unsigned data_type;
unsigned data_size;
double data[1];
};
(or "double data[];" if you use a C99 flexible array member). The
compiler knows the required alignment of type double, so it inserts
whatever padding is necessary. (We dropped the pointer, so it happens
to be aligned anyway, but we could easily have an example where
padding is necessary.)
In your example, you placed the follow-on data manually without
allowing for alignment issues. The compiler didn't have a chance to
align it properly.
Exactly, like memcpy() is a problem...but I openly stated that there
were "caveats" concerning this "scheme"...I was just pointing out that
you can "solve" the issue of "contiguous" memory and multiple free()s
using it, that's all...
Sure, but the struct hack is more convenient, even if it's of somewhat
questionable validity.
And if I don't have a C99 compiler, then that "hack" is just fine in all
cases to use?
Maybe. Probably.
Question 2.6 in the FAQ says:
Despite its popularity, the technique is also somewhat notorious:
Dennis Ritchie has called it ``unwarranted chumminess with the C
implementation,'' and an official interpretation has deemed that
it is not strictly conforming with the C Standard, although it
does seem to work under all known implementations. (Compilers
which check array bounds carefully might issue warnings.)
If you don't trust the struct hack, you can always just allocate the
data separately:
#include <stdlib.h>
struct contiguous_data {
unsigned data_type;
unsigned data_size;
double *data;
};
struct contiguous_data *create_struct(unsigned data_type, unsigned data_size)
{
struct contiguous_data *result = malloc(sizeof *result);
if (result == NULL) {
return NULL;
}
result->data_type = data_type;
result->data_size = data_size;
result->data = malloc(data_size * sizeof(double));
if (result->data == NULL) {
free(result);
return NULL;
}
return result;
}
This will require two calls to free() to deallocate the allocated
memory.