tags:

views:

161

answers:

3

In K&R2 they implement memory allocator in chapter 8. For each block there is header defined something like this (from memory, code may be non-exact):

union header_t
{
  struct 
  {
     unsigned size;
     unsigned* next; 
  };

  long align;
};

They do this to "make sure that the header is properly aligned on the boundaries of long". But doesn't the union align by the largest member? The struct is larger than the single align member, so the header will be aligned on the struct's multiples anyway, no?

What about when the struct is even larger (say it has many members). Is this alignment union trick always required in when write a memory allocator?

A: 

The size of these datatypes is platform dependent. I guess that the two unsigned members of the struct are 16 bits and the long align is 32 bits or two 32 bit members in the struct and 64 bits for the long.

Alignment boundaries would be either 32 or 64 bits

Holger Kretzschmar
+1  A: 

"The struct is larger than the single align member".

First, says who? What if, on your implementation, unsigned and unsigned* are each 32 bits, and long is 128 bits (or more realistically, 16 bits and 64 bits)?

Second, so what? Even if the struct is at least as big as long, it doesn't mean that it must have at least as big an alignment as long. If unsigned and unsigned* have no alignment requirements, then the struct has no alignment requirements. But perhaps long does.

Steve Jessop
+2  A: 

The data type in K&R is:

union header
{
  struct 
  {
     union header *ptr; 
     unsigned size;
  } s;

  Align x;
};

Let's say we had this instead:

union header_t
{
  struct 
  {
     union header_t *next; 
     unsigned size;
  } s;
};

(By the way, you need a name for the struct variable and also change the pointer inside the struct to type union header_t *, because the data structure is a linked list.)

K&R's malloc() implementation reserves a chunk of space, and then uses this to maintain free store. When this malloc is called, it finds on the free list a place that has enough space, and returns a pointer to that tail end of it.

In particular, the relevant part of the code is:

typedef union header Header;
static Header base;
Header *p = &base;
...
p += p->s.size;
return (void *)(p+1);

Note that we are returning p+1 (cast to void *), so we have to make sure that this pointer is aligned for any data type. Since p itself is pointing to a Header *, we have to make sure that when we add sizeof(Header) to an aligned pointer, we get another aligned pointer back (remember, p+1 points to sizeof(Header) bytes from p). This requirement means that Header has to be aligned for all types of data.

The struct inside Header might not be aligned for the widest type possible. To make sure that our Header type is so aligned, we add a member to the union that we know to be maximally aligned, i.e., is the widest type on a given machine. K&R assume that this type is long. Also, note that it does not matter if the size of Header is greater than the size of Align type. The assumption here is that Align type here is a type with the strictest alignment requirements, not that it be huge.

Interestingly, we need to assume a 'maximally aligned' type because the C standard requires alignment for any type from pointers returned by malloc, but does not specify a portable way to find out what that alignment is going to be. If the standard did specify such a type, one could have used that type instead of long for Align.

Alok
but this is only because there's doubt about the total size of the 's' struct? had it had 10 members, the `long A` would not be necessary?
zaharpopov
Not necessarily. Let's say `long` is 4 bytes, and the `struct`'s size is 5 bytes. You will still need the `long` member in the `union`.
Alok
so why 'long' and not 'double' which is presumably 8-bytes on 32-bit machines opposed to 4 bytes of 'long'?
zaharpopov
Alok
"the C standard requires alignment for any type from pointers returned by malloc". Ish. It requires alignment for any type which fits in the allocation. So if there's an 8-aligned type, with size 8, `malloc(4)` may not be 8-aligned. I once saw an amusing public argument online between GCC and glibc. The problem was that glibc doesn't know whether the compiler uses SIMD types or not. Even if the standard could tell you the highest alignment, that wouldn't help glibc because it might be compiled without SIMD. And anyway, 16-aligning everything wastes memory.
Steve Jessop
... so IIRC the solution is to act as though SIMD types aren't really built-in types (even if they are), and let the user take special measures to allocate aligned memory. Systems already have functions to allocate super-aligned memory anyway, because loaders use it to align code on cache line boundaries.
Steve Jessop
@Steve: All I can find in my copy of n1256 is *The pointer returned if the allocation succeeds is suitably aligned so that it may be assigned to a pointer to any type of object and then used to access such an object or an array of such objects in the space allocated(until the space is explicitly deallocated).* (7.20.3p1). But thanks for the comment, it seems like it's a useful thing for `malloc` to do; I am not sure if it's standards-compliant.
Alok
Mmm, OK. C++ spells it out for new. I'm not sure whether that C standardese is or is not intended to mean that the pointer in my example has to be 8-aligned. Clearly it's impossible to align it, or for that matter do anything else to it, so that it "may be assigned to a pointer to the 8-aligned tpye and then used to access such an object in the space allocated". Alignment doesn't enter in to it, it's the access to an object larger than the allocated space that's invalid. But you're probably right, and the intent is that it be aligned.
Steve Jessop
I agree. I don't see why anything can go wrong when for example if the strictest alignment is 8 bytes and I ask for 2 bytes; and `malloc` returns something that's aligned on a 2-byte boundary, not necessarily 8-bytes. As you said, the pointer can't be used to address anything bigger than 2 bytes anyway. But I couldn't find anything in the C standard that allows this. Thanks for your comments!
Alok