Yes, the code is wrong, valgrind is right. As usual
When you make a function call, the stack pointer will be adjusted by some number of bytes.
This effectively reserves space on the stack for local variables, the return address, parameters, etc.
The integer z is a local variable, so it's located on the stack. Probably somewhere around the address to return to, etc. &z gives the address of z as you expect.
When add() returns, the stack pointer is changed back to where it was before add() was called. The local variables, etc are still there. You can still access that address and get the correct answer. However, the next time you call a function, z will get overwritten by something else, because its parameters and local variables may occupy the same space. It works right now, but will surely break later.