malloc woes

malloc woes

Post by aegi » Sat, 13 Aug 2005 09:26:19


With malloc, we can allocate an object of some particular size
and if the size falls within proper parameter ranges for the size of
more primitive object types, then we can use the object with differing
object types.

i.e., if the size is large enough, the following works:
(sizeof(int) <= sizeof(double))

int *p;
double *p1;

p = malloc(sizeof *p1);

*p = 10;

printf("%d\n", *p);

p1 = (double *)p;

*p1 = 3.14;

printf("%f\n", *p1);


Agree so far? Moving on...

6.2.5#26 says the following works:

struct a {
int foo;
long bar;
};

struct b {
long foo;
int bar;
};

struct a *p;
struct b *p1;

p = malloc(sizeof *p);

p1 = (struct b *)p;



Now for my question. Given both guarantees, does this mean
I can portably dereference 'p1' in the second scenario
and use it effectively(assign values to memebers, read values from its
members in expressions)? Or does the theoretical case of containing 'x'
amount of padding between members invalidate any guarantee given with
respect to the alignment and use of an object allocated with malloc
between differing object types?
 
 
 

malloc woes

Post by kuype » Sat, 13 Aug 2005 11:56:43


In actual practice, that should work fine, but in principle the ONLY
thing that is guaranteed about the value stored in 'p1' at this point
is that when you convert it back to 'int*' it will compare equal to the
value stored in 'p'. It is NOT guaranteed to point at the same location
in memory as 'p' itself. It is therefore not guaranteed to be a
dereferenceable pointer value.


All that 6.2.5p26 guarantees is that 'p' and 'p1' have the same
representation and alignment. Therefore, if (struct b*)p points at the
same location as 'p' itself, then the value of 'p1' must contain a
representation that would also be a valid representation of the value
of 'p'. However, the standard fails to specify where (struct b*)p
points. In particular, the standard doesn't specify that (struct b*)p
points at the same location as 'p' (which is, of course, what every
real implementation of C does). It is not guaranteed to be a
dereferenceable pointer value. The only thing that the standard
guarantees about that pointer value is that converting it back to
(struct a*) will produce a pointer value that is equivalent to 'p'.


There's a second, and far more realistic issue. The amount of padding
in a 'struct a' is not necessarily the same as the amount of padding in
a struct 'b'. This is something that DOES happen on real
implementations. Therefore, there's no guarantee that sizeof(struct
b)<=sizeof(struct a). Therefore, the memory allocated by your call to
malloc() might not be big enough to contain a 'struct b'. Of course, in
real life struct a is the one more likely to have the larger amount of
padding.

The following approach avoids both of the issues I've raised:

union {
struct a one;
struct b two;
} combo; /* Guaranteed big enough to hold either structure. */

void *pv = malloc(sizeof combo);
struct a *pa = (struct a*)pv;
struct b *pb = (struct b*)pb;

If pv is non-null, you can safely use either pa or pa to write to the
memory that was allocated, and whichever one you used to write to that
memory most recently, can also be used to read back what you wrote.

 
 
 

malloc woes

Post by kuype » Sat, 13 Aug 2005 19:45:16


...

That was, of course, intended to say:

struct b *pb = (struct b*)pv;
 
 
 

malloc woes

Post by Douglas A. » Thu, 18 Aug 2005 01:17:29

You can reuse the pointer returned by malloc for any
object type, provided that the size of the original
malloc request is known to be at least as large as
the size of the object. Alignment is guaranteed,
and the type is not inherent in the allocated
storage but rather is "impressed" upon it every time
that data is assigned into it. (It's actually just
the particular value in that type's representation
that is stored.) As always, you can read data from
an object using a given type only if the last write
to the object (including initialization) was of the
same type, or of a "punnable" type (such as using
unsigned int to read a positive value written as
type int). There is also a special dispensation
which allows bytes of any object to be read using
type unsigned char.

Don't forget about union types. A union type allows
you to easily specify the required size of a malloc
that can accommodate an object of any of the union's
member types.