2

I practice in realization a memory manager in C.

I want the structure, that has a various length and self-described. So, I peep at a POSIX textbook something, like that:

struct layout
{
 uint32_t  size; // array size in bytes, include space after the struct
 uchar_t   data[1];
};

// But, is next line correct?
layout *val = malloc (array_memory_in_bytes + sizeof (uint32_t) - 1);
// Where does a static array keep the pointer for using it? 

If I have several these structures one-after-one in uninterrupted piece of memory, and I want be able to iterate through them. Can I write something, like that:

layout *val1 = pointer;
layout *val2 = val1 + val1.size + sizeof (val1.size);

Or can you recommend me a better approach?

Kirill Golikov
  • 1,354
  • 1
  • 13
  • 27
  • 1
    Seems fairly reasonable, though you probably want/need to add alignment between the objects. And use a flexible array member if you've got a C99 compiler. – doynax Mar 11 '15 at 21:51
  • 1
    Depending on the platform you may need to account for padding between 'size' and 'data'. If you can, you should explicitly define the packing of the structure to ensure there isn't any extra space. – josh poley Mar 11 '15 at 21:55
  • @doynax, Thank you for the term 'flexible array member', I found the answer for my question http://stackoverflow.com/a/247040/864113 – Kirill Golikov Mar 11 '15 at 21:59
  • @joshpoley, the line 'struct layout *val = malloc( sizeof *val + (number_of_bytes - 1)); /* non flexible */' does not depend the way of packing – Kirill Golikov Mar 11 '15 at 22:00
  • 1
    Regarding "Where does a static array keep the pointer for using it?" , it doesn't. Such a pointer is an rvalue that's created as needed. – M.M Mar 11 '15 at 22:05
  • @MattMcNabb, Thank you. Now it is clear, this thing worried me very much. – Kirill Golikov Mar 11 '15 at 22:09
  • @so-olitary This code is broken with regard to packing: `val1.size + sizeof(val1.size);` – josh poley Mar 11 '15 at 22:38

2 Answers2

4

The Standard C version of this is called flexible array member and it looks like:

struct layout
{
    uint32_t size;
    uchar_t data[];
};

// allocate one of these blocks (in a function)
struct layout *val = malloc( sizeof *val + number_of_bytes );
val->size = number_of_bytes;

The code val1->data + val1->size will get you a pointer one-past-the-end of the space you just malloc'd.

However you cannot iterate off the end of one malloc'd block and hope to hit another malloc'd block. To implement this idea you would have to malloc a large block and then place various struct layout objects throughout it, being careful about alignment.

In this approach, it's probably best to also store an index of where each struct layout is. In theory you could go through the list from the start each time, adding on size and then doing your alignment adjustment; but that would be slow and also it would mean you could not cope with a block in the middle being freed and re-"allocated".

If this is meant to be a drop-in replacement for malloc then there are in fact two alignment considerations:

  • alignment for struct layout
  • data must be aligned for any possible type

The simplest way to cope with this is to align struct layout for any possible type also. This could look like (note: #include <stdint.h> required):

struct layout
{
    uint64_t size;    // may as well use 64 bits since they're there
    _Alignas(max_align_t) uchar_t data[];
};

An alternative might be to keep size at 32-bit and throw in a pragma pack to prevent padding; then you'll need to use some extra complexity to make sure that the struct layout is placed 4 bytes before a max_align_t-byte boundary, and so on. I'd suggest doing it the easy way first and get your code running; then later you can go back and try this change in order to save a few bytes of memory if you want.


Alternative approaches:

  • Keep each instance of a struct layout plus its trailing data in a separate allocation.
  • Change data be a pointer to malloc'd space; then you could keep all of the struct layout objects in an array.
Community
  • 1
  • 1
M.M
  • 138,810
  • 21
  • 208
  • 365
  • I described my thoughts incorrect. Of course, the struct layouts are placed one by one in a uninterrapted memory block. – Kirill Golikov Mar 11 '15 at 21:50
  • 1
    If you do this, you must take alignment into account: each struct should be aligned on a 4 byte boundary in order for the `uint32_t size` member to be properly aligned. Either keep the sizes as multiple of 4 or add some extra padding between structs. – chqrlie Mar 11 '15 at 21:55
  • @chqrlie, if I will use 'sizeof(struct header)' every time, I consider the aligment. I think. – Kirill Golikov Mar 11 '15 at 22:05
  • 1
    @so-olitary the problem is that the number of bytes in `data` might not be a multiple of the alignment; so you will have to insert some padding bytes before you place another `struct layout` – M.M Mar 11 '15 at 22:07
  • 1
    @so-olitary: using `sizeof(struct layout)` does not really help for alignment because you add the array length without aligning it. It actually makes things worse because in the pre-C99 version, `struct header` includes 3 bytes of padding after the 1 byte array. You should use `offsetof(struct layout, data) + align4(number_of_bytes)` with this definition of `align4`: `#define align4(n) (((n)+3)&~3)`. Of course, you must use your own allocator to create this pseudo array of structs, not a separate `malloc` for each one. – chqrlie Mar 11 '15 at 22:12
  • 1
    @chqrlie in the Standard C version, `val1->data + align4(number_of_bytes)` is simpler. Also you can use the `alignof` operator introduced in C11 instead of hardcoding alignment 4. (the platform may have less strict alignment requirements) – M.M Mar 11 '15 at 22:16
  • 2
    Another consideration is that `malloc` is specified as returning space aligned for any possible type. If this memory manager is meant to be a drop-in replacement for `malloc` then you will actually have to do some shenanigans to get `val1->data` correctly aligned for the maximum possible alignment of any type on the system. – M.M Mar 11 '15 at 22:18
  • @MattMcNabb, Oh! It is a problem. I started this story to make my life simpler. If I can't just get any data by 'val1->data' pointer. All of this are unsuitable. If I just add 3 empty bytes (uchar_t s) in the middle of a structure, can it solve the problem (if I know, that this program will be compiled and executed in the same computer)? – Kirill Golikov Mar 11 '15 at 22:27
  • @so-olitary updated to include alignment for `data[]`. I'd suggest against inserting miscellaneous bytes. If your compiler doesn't support C11 but does have anonymous unions, then they can be used to achieve the desired layout. – M.M Mar 11 '15 at 22:49
2

The general idea will work, but that specific struct will only work if the most-severe boundary alignment case is an int.

A memory manager, particularly one that might be a back-end for an implementation of malloc(), must know what that worst-case boundary is. The actual start of data must be on that boundary in order to satisfy the general requirement that the allocated memory be suitably aligned for the storage of any data type.

The easiest way to get that done is to make the length allocation header described by the layout struct and the actual allocation sizes all multiples of that alignment unit.

No matter what, you can't describe the start of data as a struct member and have the size of that struct be the size of the header. C doesn't support zero-length fields. You should use something to put that array on boundary, and use the offsetof() macro from <stddef.h>.

Personally, I'd use a union, based on both old habits and occasional use of Visual C++ for C. But uint32_t is a C99 type and if you also have C11 support you can use _Alignas(). With that, your struct could look something like:

#define ALIGN_TYPE double /* if this is the worst-case type */
#define ALIGN_UNIT ((sizeof)(ALIGN_TYPE))
#define ALIGN_SIZE(n) (((size_t)(n) + ALIGN_UNIT - 1) & ~(ALIGN_UNIT-1))

typedef struct layout
{
    size_t size; /* or use uint32_t if you prefer */
    _Alignas(ALIGN_UNIT) char data[1];

} layout;

#define HEADER_SIZE (offsetof(layout, data))

That makes most everything symbolic except for the worst-case alignment type. You'd allocate the combined header plus data array with:

layout *ptr = (layout*) malloc(HEADER_SIZE + ALIGN_SIZE(number_of_bytes));
ptr->size = HEADER_SIZE;

The ALIGN_SIZE type really isn't a symbolic constant, though, unless C99/C11 changed the definition of sizeof. You can't use to compute ordinary array dimensions, for example. You can hard code a literal number, like 8 for a typical double, if that's a problem. Beware that long double has a problematical size (10 bytes) on many x86 implementations. If you're going to base the allocation unit on a type, then long double might not be your best choice.

Mike Housky
  • 3,959
  • 1
  • 17
  • 31