Keith Thompson said:
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.
THAT'S a pretty common usage...
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).
Ah yes, in another post my actual working code was listed as:
(*csv_efileb)->buffer=(void *)(*csv_efileb+1);
when dealing with a pointer to a pointer passed as an argument...
It could really happen. I'll construct an example similar to what you
wrote above:
Well, in the interim I answered my own question, and more. The
issue for most systems today is "self-alignment" for all types, and the
bottom line as I take it is that you can't have a larger size for your
"data" than the largest size in the struct. (If you had a couple of
unsigned chars in the struct, and a "data buffer" of doubles, you'd
be hosed.) Soooo, I'm quite sure the following would create a
big mess on my machine (though all the examples I've given would
work on most all machines):
#include <stdlib.h>
struct contiguous_data {
unsigned data_type;
unsigned data_size;
double *data;
};
Yeah, your data is bigger than the biggest size in the struct itself.
NG (that means "No(t) Good" or "No Go").
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.
As I take it, if you are dealing with chars as "data", you're pretty
much OK in like 99% of the cases...
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).
Well, as it turns out, I learned many things today, and one of them
is that my compiler, although obstensibly NOT "C99" compliant, offers
the "flexible array member" (SHOULD BE: "indeterminate array as the
last member of a struct") feature "as a special extension to the ANSI
standard". I should have known, because they have all kinds of goofy
stuff like that in there, but I first experimented by changing some working
code as follows:
typedef struct {
unsigned type;
unsigned long size;
unsigned cols;
unsigned rows;
char *buffer;
} CSV_EFILEB;
to
typedef struct {
unsigned type;
unsigned long size;
unsigned cols;
unsigned rows;
char buffer[];
} CSV_EFILEB;
And VOILA (that's like Italian or something, sorry), the whole thing
worked slicker than a bannana slug trail!
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.)
Yeah, but for the sake of pure expediancy and correctness, I guess
I should just use "indeterminate arrays as a final member of struct",
aside from any potential "backwards-compatibility" issues...
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.
Sure, but the struct hack is more convenient, even if it's of somewhat
questionable validity.
Yes, it does...and I'll be experimenting a little more to see just how
"flexible" it really is...stuff like arrays of void pointers to be cast into
different struct types, you know...
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.)
I think they also allowed possible "pathological" POTENTIAL
alignment issues, though again, not with type char as "data"...
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.
That's SOOOOOOO much work...