I am trying to write a custom stack allocator for a game I am working on. To deal with memory alignment, I am using C11's stdalign.h
header; however, since I do not have prior experience with alignment issues, I'd like a expert opinion on whether I am doing it right.
The majority of the alignment stuff happens in alignup
and aligndown
macros. Specifically, in alignup
, I try to go up to the nearest multiple of alignof(max_align_t)
(unless we were already a multiple) by shooting past it by adding alignof(max_align_t) - 1
and then coming back using the bitwise AND. In aligndown
, I first go below the last multiple of alignof(max_align_t)
and then come up using alignup
.
NOTE: The stack is in a memory arena and it grows down in memory instead of going up.
#define alignup(p) ((void*)((((char*)(p)) + (alignof(max_align_t) - 1)) & (~ (alignof(max_align_t) - 1))))
#define aligndown(p) ((void*)(alignup((((char*)(p)) - (alignof(max_align_t) - 1)))))
struct Mem_Stack_Header {
size_t allocation_size;
size_t total_size;
};
// This contains the "head" of the stack. The stack grows down, instead of up.
extern void *stack_head;
void* memPush (size_t s)
{
void *head = stack_head;
void *mem = (char*)head - s;
mem = aligndown(mem);
struct Mem_Stack_Header *h = (struct Mem_Stack_Header*)((char*)mem - sizeof(*h));
h = aligndown(h);
h->allocation_size = s;
h->total_size = (uintptr_t)((char*)head - (char*)h);
stack_head = h;
return mem;
}
void memPop (void)
{
struct Mem_Stack_Header *h = stack_head;
stack_head = (char*)stack_head + h->total_size;
return;
}
Is this the correct way of doing this kind of stuff? Any improvements I can make?
1 Answer 1
Is this the correct way of doing this kind of stuff? Any improvements I can make?
The role of member
allocation_size
is unclear. It appears to represent the raw alignment withh->allocation_size = s;
.(削除) Later use withI recommend thatstack_head = (char*)stack_head + h->total_size;
could then result in an unalignedstack_head
. (削除ここまで)void* memPush (size_t s)
first takes
and increment it as needed to an aligned size and then save that value inallocation_size
. This will simplify subsequent code too especially the macroaligndown()
s = (s + alignof(max_align_t) - 1) & ~(alignof(max_align_t) - 1u);
There is no protection against pushing too much. I'd expect the size of memory available with
stack_head
to be provided andmemPush()
to code against overflow. In any case, OP should provide documentation concerning what happens when too much memory is requested.There is no clear functionality against popping too much. I'd expect either an error return or a perhaps the equivalent of the top allocation having size 0.
Some compilers will warn about mixed signed/unsigned math. As the return type from
alignof()
is some unsigned type, use unsigned constants throughout.//alignof(max_align_t) - 1 alignof(max_align_t) - 1u
The name
stack_head
is a global object that could easily collide with other code. Its name does not suggest that is is part of thememPop(), memPush()
functions. Recommend a name aligned with the funcitons likemem_stack_head
.A use of
memPush(0)
needs special consideration. I recommend that such usage returns the same asmemPush(alignof(max_align_t))
to provide unique allocations. In any case, OP should provide documentation concerning the special case.uintptr_t
is an optional type and not the best to use here.(char*)head - (char*)h
results in typeptrdiff_t
, simply cast it to the destination type. Alternative://h->total_size = (uintptr_t)((char*)head - (char*)h); h->total_size = (size_t)((char*)head - (char*)h);
Code could reduce explicit coding of cast
(char *)
by usingchar *head = stack_head;
Alternative idea to this line of code:
// struct Mem_Stack_Header *h = (struct Mem_Stack_Header*)((char*)mem - sizeof(*h)); struct Mem_Stack_Header *h = (struct Mem_Stack_Header*)mem - 1;
A simple test harness would help in evaluation of this code.
alignof
is not valid without#include <stdalign.h>
. Other include files missing too.
-
\$\begingroup\$ Regarding 1,
allocation_size
is essentiallyrequested_size
to track how much memory the client code asked for and would actually use (I've renamed it).total_size
is the size that includes actual memory allocated and it is used to deallocate (which means it will always be aligned properly since it was assigned to after aligning the pointers). \$\endgroup\$strNOcat– strNOcat2018年01月02日 05:54:35 +00:00Commented Jan 2, 2018 at 5:54 -
\$\begingroup\$ WRT 5, ignore issues about
stack_head
, it was added as a placeholder in this code, the actual code uses aEngine_State
struct which is kind of irrelevant to the question and would just add noise. Thanks for the other points. \$\endgroup\$strNOcat– strNOcat2018年01月02日 05:56:31 +00:00Commented Jan 2, 2018 at 5:56 -
\$\begingroup\$ Yes I agree #1 is erroneous with regard to
stack_head = (char*)stack_head + h->total_size;
\$\endgroup\$chux– chux2018年01月02日 05:59:04 +00:00Commented Jan 2, 2018 at 5:59 -
\$\begingroup\$ Can you please provide some feedback on whether the
alignup
andaligndown
macros are correct and whether this is right way to deal with alignment in custom allocators? That's the part of the code that I don't have any experience with and would like to be sure about. \$\endgroup\$strNOcat– strNOcat2018年01月02日 06:08:52 +00:00Commented Jan 2, 2018 at 6:08 -
\$\begingroup\$ Aside from #4, I do not see a problem with
aligndown()
. It appears functionally correct, yet could be simplified. Example the final(void*)
is not needed. Further, IMO, it is the wrong approach. Instead, insurestack_head
is aligned and adjustsize_t s
to an align multiple. \$\endgroup\$chux– chux2018年01月02日 06:16:17 +00:00Commented Jan 2, 2018 at 6:16
internal_function
? You don't seem to define it anywhere. \$\endgroup\$static
.static
has so many meanings in C, I defineinternal_function
,persistent_value
andglobal_variable
tostatic
to give some semantic context. \$\endgroup\$_Mem_Stack_Header
is reserved because it begins with an underscore immediately followed by a capital letter)? \$\endgroup\$memPush()
andvoid *mem = (char*)head - s;
as that is pointer arithmetic on an invalid pointer (stack_head == NULL
). VTC \$\endgroup\$