You can do this by starting the stacks on either ends of the array and moving inwards. What that means is that the first element pushed to stack 1 would live in arr[0], and the first element pushed to stack 2 would live in arr[n-1].
We will keep two stack pointers, each pointing to the top of their respective stacks. The pointer for the first stack would be initialized as -1, and the pointer for the second stack would be initialized as n.
Then, each time you push into the first stack, you would increase its stack pointer, and each time you push into the second stack you'd decrease its stack pointer. In this implementation, checking for overflow becomes as simple as checking if those pointers meet/cross each other in the middle.
Pop is still O(1) since we're keeping track of both pointers and we can do constant time lookups (for eg, arr[pointer1] would give us the top element in the first stack). We'd then return that value and increase or decrease the current pointer based on whether the first or the second stack was popped.