If there are very many of these, it gets very hard for the compiler. It has to allocate all these arrays and not to lose the track where they are. Since the stack pointer is moving during the allocation process, it's not that easy to find storage for all the corresponding. Probably the best idea is to maintain a linked list of allocated arrays in the stack, and then when everything is done you can walk through the list, extract the pointers and store them at fixed locations relative the stack frame base. Such a PITA to implement for the compiler, and then nobody uses it anyway I don't know how GCC does multiple arrays, but you can compile and look.
Actually, pretty sure the stack pointer is pretty much irrelevant during execution of a function. It's the base pointer that anchors the functions arguments and locals. The compiler simply allocates ALL variables a slot on the stack at compile time, regardless of where in the function they occur, at what scope depth, etc., which includes stack-allocated arrays — they get a pointer variable up-front like any other array. The compiler then basically does the equivalent of inlining a malloca call, to allocate the actual array. Since the base pointer anchors the function arguments and locals, the stack pointer can do what it likes, and you simply subtract as much space as you need off the stack pointer, and use that as the base of your array.
Seriously, in the middle of a function, pick a small random number, subtract it from the stack pointer, and everything keeps working just fine. You can do it as often as you like (as long as you don't run out of stack space, of course), and all you've done is wasted some space on your stack.
It works because the compiler does essentially the exact same thing itself to allocate a functions own local variables. The caller basically pushes whatever it needs to save, followed by the arguments to the function it's calling, that function then pushes the current base pointer, and sets it to the current stack pointer, before subtracting the space it wants to allocate for it's own local variables. Done. On the way back out, it can simply ditch the current stack pointer, setting it back to the base pointer, load the previous base pointer back off the stack, and return back to the caller, which simply adds the space taken by those arguments back onto the stack pointer (rather than fiddling around with popping them off again). No need to keep track of anything much, other than the functions own arguments and locals.