I'm working on a program that reserves two big blocks of virtual memory for use as stacks for things whose size is not statically known. The program tracks pointers to tops of each stack, which I call stack_cursors, and making an allocation consists of calling a function that increments the stack_cursor by the size of the block one wants, commits the next page of memory if this causes it to cross a page boundary, and returns what the position of the cursor was before it was incremented. A function that needs to allocate something that persists beyond its own scope takes a pointer to a stack_cursor, while a function that needs to allocate things for internal use takes a stack_cursor directly, so that its incrementing of the stack_cursor isn't reflected on the caller's end, and the caller's subsequent uses of the stack can overwrite the function's. There are functions that do both internal-only allocations and persistent ones, hence the existence of two stacks.
A shortcoming of this scheme is that, though it commits new pages when a stack grows large enough to need them, it never decommits them if the stack shrinks again. As far as I can tell, whereas stack_cursor "rewinding," such as it is currently, is entirely automatic as long as I correctly pass the stack_cursor by value, if I wanted to support decommitting pages I would have to add a call that manually rewinds the stack_cursor at the end of the scope, to a position that I saved at the beginning of the scope. At that point, continuing to make the distinction between passing a stack_cursor by value versus by reference would only serve as insurance in case I forgot to call the rewind function. There are other reasons taking away that distinction and always passing by pointer is appealing.
It's the kind of repetitive pattern that suggests the need for an abstraction, but I don't really see a way to take the usual approach of "factor it into a function." Is this the kind of situation where metaprogramming could help? Some kind of syntactic extension to C that automates my more specialized stacks like C automates its own stack seems plausible to me, but I've never done that kind of thing before. I don't know how hard it typically is.