It goes pretty quick on the machines I've used so far to have an indirect branch with overlapping moves. Here's what that looks like in "pure c" for gcc and clang. https://github.com/jart/cosmopolitan/blob/master/libc/str/me... memcpy is hard to implement in pure c99 because aliasing pointers to non-char types can trigger ubsan errors. With rep movsb it's a good idea to check cpuid for erms support. In that case it'll be fastest for bigger moves but is still slower for small moves. See the chart at https://justine.lol/cosmopolitan/index.html which compares various methods. Also take into consideration that if you call zpl_memcpy() the compiler doesn't know that's memcpy() so it can't perform some of its inlining magic at the call site. So if you want things like null propagation it might be better to do that as a macro.
Regarding array.c I implemented a bunch of similar routines myself and eventually chose to not worry about capacity and just call realloc every time. I'm not sure if the standard specifies this, but I'm pretty sure every realloc implementation in practice, under the hood, should track the capacity and grow it at two power sizes. So even if you're just constantly appending by one char the cost should amortize itself out to be cheap in the long run.