"Kutty Banerjee said:
Hi,
I ve got the following piece of code which does the role of allocating
aligned memory (not sure what aligned memory allocation is either).
void *
_align_calloc(size_t bytes, unsigned long alignment)
{
unsigned long ptr, buf;
ASSERT( alignment > 0 );
ptr = (unsigned long) calloc(bytes + alignment + sizeof(void *));
if (!ptr)
return NULL;
buf = (ptr + alignment + sizeof(void *)) & ~(unsigned long)(alignment -
1);
*(unsigned long *)(buf - sizeof(void *)) = ptr;
#ifdef DEBUG
/* mark the non-aligned area */
while ( ptr < buf - sizeof(void *) ) {
*(unsigned long *)ptr = 0xcdcdcdcd;
ptr += sizeof(unsigned long);
}
#endif
return (void *)buf;
}
Can somebody please explain what this code is doing? I m a little confused
with the memory being allocated to a non pointer data namely unsigned long
ptr ?
It is a case of a clueless programmer trying to return a pointer that is
aligned in a certain way. I'll give you an overview what he is trying to
do and where he gets it wrong:
malloc and calloc return pointers that are properly aligned for _any_ C
type. Now there might be reasons why you would want a pointer that is
aligned to multiples of 128 bytes, for example, and malloc won't do
that.
So what he does is allocate a pointer that is bigger, lets say a pointer
"char* p", and in the case of 128 byte alignment you would add some
value from 0 to 127 to make it aligned on a multiple of 128 bytes. But
if you did just that and returned the result pointer, then you wouldn't
be able to free the memory later: To be able to call free, you must have
the original pointer. So you store the original pointer just in front of
the pointer that you return.
Now your original author does a few remarkably stupid things. He makes
assumptions that are completely unwarranted in portable C and actually
wrong on more and more computers that are in actual use. No wonder you
are confused :-(
First, he assumes that "alignment" is not only greater than zero, but
also a power of two, AND at least as large as the alignment requirement
of a void* value. Why doesn't he write this in the ASSERT? To test
whether x is a power of two: If you write x and x-1 in binary, then if x
is a power of two no bit will be set both in x and x-1. If x is nonzero
and not a power of two, then the highest bit of x is also set in x-1. So
you could write
ASSERT(align >= sizeof (void *) && (align & (align-1)) != 0);
Next, he assumes that you can cast a void* to unsigned long and perform
pointer arithmetic on the unsigned long. Bullshit. Take an Athlon 64 and
you may be surprised to see that void* = 64 bit, unsigned long = 32 bit,
and casting void* to unsigned long produces a whole lot of garbage. Take
16 bit DOS with "huge" memory model, and adding to an unsigned long to
do pointer arithmetic will produce garbage. Try it on a Cray and heaven
knows what you get. But in fact it is very easy to do what he wants in a
much more portable way:
char* p = calloc (bytes + alignment + sizeof (void *));
char* q = p + sizeof (void *) + alignment;
q -= ((unsigned long) q) % alignment;
// If you want your code unreadable like the original author,
// then change the last line to
// q -= ((unsigned long) q) & (alignment - 1);
Now it doesn't matter what the relative sizes of pointers and unsigned
long are.
The next mistake that he makes is that you can store an unsigned long if
you have space for a void*. Bad mistake. Just as you have systems where
sizeof (void *) > sizeof (unsigned long), you have systems where it is
the other way round. Buy a new Macintosh and you will get a machine that
uses 64 bit integers and 32 bit pointers. So he has this pointer "buf",
subtracts four byte = sizeof (void *), and stores an eight byte unsigned
long. Congratulations. The last four bytes of that unsigned long will be
overwritten by the caller as soon as they use the memory. Crash when you
try to free the memory later on.
Have fun with this guy's code. Expect the worst. People who think they
can program and can't are the worst kind.